diff --git a/.eslintignore b/.eslintignore index aa8b769dfede..3d966d096add 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,3 +10,4 @@ docs/vendor/** docs/assets/** web/gtm.js **/.expo/** +src/libs/SearchParser/searchParser.js diff --git a/.eslintrc.js b/.eslintrc.js index 27014cf9dd7b..cb1219533278 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,7 +8,7 @@ const restrictedImportPaths = [ '', "For 'useWindowDimensions', please use '@src/hooks/useWindowDimensions' instead.", "For 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from '@components/Pressable' instead.", - "For 'StatusBar', please use '@src/libs/StatusBar' instead.", + "For 'StatusBar', please use '@libs/StatusBar' instead.", "For 'Text', please use '@components/Text' instead.", "For 'ScrollView', please use '@components/ScrollView' instead.", ].join('\n'), @@ -59,8 +59,21 @@ const restrictedImportPaths = [ }, { name: 'expensify-common', - importNames: ['Device'], - message: "Do not import Device directly, it's known to make VSCode's IntelliSense crash. Please import the desired module from `expensify-common/dist/Device` instead.", + importNames: ['Device', 'ExpensiMark'], + message: [ + '', + "For 'Device', do not import it directly, it's known to make VSCode's IntelliSense crash. Please import the desired module from `expensify-common/dist/Device` instead.", + "For 'ExpensiMark', please use '@libs/Parser' instead.", + ].join('\n'), + }, + { + name: 'lodash/memoize', + message: "Please use '@src/libs/memoize' instead.", + }, + { + name: 'lodash', + importNames: ['memoize'], + message: "Please use '@src/libs/memoize' instead.", }, ]; @@ -93,7 +106,7 @@ module.exports = { 'plugin:@typescript-eslint/recommended-type-checked', 'plugin:@typescript-eslint/stylistic-type-checked', 'plugin:you-dont-need-lodash-underscore/all', - 'prettier', + 'plugin:prettier/recommended', ], plugins: ['@typescript-eslint', 'jsdoc', 'you-dont-need-lodash-underscore', 'react-native-a11y', 'react', 'testing-library', 'eslint-plugin-react-compiler'], ignorePatterns: ['lib/**'], diff --git a/.github/actions/composite/buildAndroidE2EAPK/action.yml b/.github/actions/composite/buildAndroidE2EAPK/action.yml index 47e13f6313a0..cfce0d3ec7d8 100644 --- a/.github/actions/composite/buildAndroidE2EAPK/action.yml +++ b/.github/actions/composite/buildAndroidE2EAPK/action.yml @@ -53,7 +53,7 @@ runs: distribution: "oracle" java-version: "17" - - uses: ruby/setup-ruby@a05e47355e80e57b9a67566a813648fa67d92011 + - uses: ruby/setup-ruby@v1.187.0 with: ruby-version: "2.7" bundler-cache: true @@ -72,9 +72,11 @@ runs: - name: Build APK run: npm run ${{ inputs.PACKAGE_SCRIPT_NAME }} shell: bash + env: + RUBYOPT: '-rostruct' - name: Upload APK - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + uses: actions/upload-artifact@v4 with: name: ${{ inputs.ARTIFACT_NAME }} path: ${{ inputs.APP_OUTPUT_PATH }} diff --git a/.github/actions/javascript/bumpVersion/index.js b/.github/actions/javascript/bumpVersion/index.js index 93ea47bed2ae..c8360931845a 100644 --- a/.github/actions/javascript/bumpVersion/index.js +++ b/.github/actions/javascript/bumpVersion/index.js @@ -1928,7 +1928,7 @@ class SemVer { do { const a = this.build[i] const b = other.build[i] - debug('prerelease compare', i, a, b) + debug('build compare', i, a, b) if (a === undefined && b === undefined) { return 0 } else if (b === undefined) { diff --git a/.github/scripts/createDocsRoutes.ts b/.github/scripts/createDocsRoutes.ts index fc3f376a1774..a8ac3d511ff9 100644 --- a/.github/scripts/createDocsRoutes.ts +++ b/.github/scripts/createDocsRoutes.ts @@ -5,6 +5,7 @@ import type {ValueOf} from 'type-fest'; type Article = { href: string; title: string; + order?: number; }; type Section = { @@ -60,11 +61,12 @@ function toTitleCase(str: string): string { /** * @param filename - The name of the file */ -function getArticleObj(filename: string): Article { +function getArticleObj(filename: string, order?: number): Article { const href = filename.replace('.md', ''); return { href, title: toTitleCase(href.replaceAll('-', ' ')), + order, }; } @@ -90,6 +92,12 @@ function pushOrCreateEntry(hubs: Hub[], hub: string, } } +function getOrderFromArticleFrontMatter(path: string): number | undefined { + const frontmatter = fs.readFileSync(path, 'utf8').split('---')[1]; + const frontmatterObject = yaml.load(frontmatter) as Record; + return frontmatterObject.order as number | undefined; +} + /** * Add articles and sections to hubs * @param hubs - The hubs inside docs/articles/ for a platform @@ -113,7 +121,8 @@ function createHubsWithArticles(hubs: string[], platformName: ValueOf { - articles.push(getArticleObj(subArticle)); + const order = getOrderFromArticleFrontMatter(`${docsDir}/articles/${platformName}/${hub}/${section}/${subArticle}`); + articles.push(getArticleObj(subArticle, order)); }); pushOrCreateEntry(routeHubs, hub, 'sections', { diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 624c00de6831..5030ea1c2f2b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -28,6 +28,24 @@ jobs: - name: 🚀 Push tags to trigger staging deploy 🚀 run: git push --tags + + - name: Warn deployers if staging deploy failed + if: ${{ failure() }} + uses: 8398a7/action-slack@v3 + with: + status: custom + custom_payload: | + { + channel: '#deployer', + attachments: [{ + color: "#DB4545", + pretext: ``, + text: `💥 NewDot staging deploy failed. 💥`, + }] + } + env: + GITHUB_TOKEN: ${{ github.token }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} deployProduction: runs-on: ubuntu-latest @@ -65,6 +83,24 @@ jobs: PR_LIST: ${{ steps.getReleasePRList.outputs.PR_LIST }} - name: 🚀 Create release to trigger production deploy 🚀 - run: gh release create ${{ env.PRODUCTION_VERSION }} --generate-notes + run: gh release create ${{ env.PRODUCTION_VERSION }} --notes '${{ steps.getReleaseBody.outputs.RELEASE_BODY }}' env: GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} + + - name: Warn deployers if production deploy failed + if: ${{ failure() }} + uses: 8398a7/action-slack@v3 + with: + status: custom + custom_payload: | + { + channel: '#deployer', + attachments: [{ + color: "#DB4545", + pretext: ``, + text: `💥 NewDot production deploy failed. 💥`, + }] + } + env: + GITHUB_TOKEN: ${{ github.token }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index ffce73644263..add4879d8de1 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -156,7 +156,7 @@ jobs: run: mkdir zip - name: Download baseline APK - uses: actions/download-artifact@348754975ef0295bfa2c111cba996120cfdf8a5d + uses: actions/download-artifact@v4 id: downloadBaselineAPK with: name: baseline-apk-${{ needs.buildBaseline.outputs.VERSION }} @@ -170,7 +170,7 @@ jobs: run: mv "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2e-release.apk" "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease.apk" - name: Download delta APK - uses: actions/download-artifact@348754975ef0295bfa2c111cba996120cfdf8a5d + uses: actions/download-artifact@v4 id: downloadDeltaAPK with: name: delta-apk-${{ needs.buildDelta.outputs.DELTA_REF }} @@ -184,7 +184,7 @@ 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 @@ -257,12 +257,12 @@ jobs: - name: Check if test failed, if so post the results and add the DeployBlocker label id: checkIfRegressionDetected run: | - if grep -q '🔴' ./output.md; then + if grep -q '🔴' "./Host_Machine_Files/\$WORKING_DIRECTORY/output.md"; then # Create an output to the GH action that the test failed: echo "performanceRegressionDetected=true" >> "$GITHUB_OUTPUT" gh pr edit ${{ inputs.PR_NUMBER }} --add-label DeployBlockerCash - gh pr comment ${{ inputs.PR_NUMBER }} -F ./output.md + gh pr comment ${{ inputs.PR_NUMBER }} -F "./Host_Machine_Files/\$WORKING_DIRECTORY/output.md" gh pr comment ${{ inputs.PR_NUMBER }} -b "@Expensify/mobile-deployers 📣 Please look into this performance regression as it's a deploy blocker." else echo "performanceRegressionDetected=false" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index 640d1eaa1172..ba776f257a3c 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -62,7 +62,7 @@ jobs: java-version: '17' - name: Setup Ruby - uses: ruby/setup-ruby@a05e47355e80e57b9a67566a813648fa67d92011 + uses: ruby/setup-ruby@v1.187.0 with: ruby-version: '2.7' bundler-cache: true @@ -80,38 +80,42 @@ jobs: - name: Set version in ENV run: echo "VERSION_CODE=$(grep -o 'versionCode\s\+[0-9]\+' android/app/build.gradle | awk '{ print $2 }')" >> "$GITHUB_ENV" - - name: Run Fastlane beta - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane android beta + - name: Run Fastlane + run: bundle exec fastlane android ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'production' || 'beta' }} env: + RUBYOPT: '-rostruct' 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 }} - name: Archive Android sourcemaps - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: android-sourcemap-${{ github.ref_name }} path: android/app/build/generated/sourcemaps/react/productionRelease/index.android.bundle.map - - name: Upload Android version to GitHub artifacts + - name: Upload Android build to GitHub artifacts if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: app-production-release.aab path: android/app/build/outputs/bundle/productionRelease/app-production-release.aab - - name: Upload Android version to Browser Stack + - name: Upload Android build to Browser Stack if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} run: curl -u "$BROWSERSTACK" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@./android/app/build/outputs/bundle/productionRelease/app-production-release.aab" env: BROWSERSTACK: ${{ secrets.BROWSERSTACK }} + - name: Upload Android build to GitHub Release + if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} + run: | + RUN_ID="$(gh run list --workflow platformDeploy.yml --event push --branch ${{ github.event.release.tag_name }} --json databaseId --jq '.[0].databaseId')" + gh run download "$RUN_ID" --name app-production-release.aab + gh release upload ${{ github.event.release.tag_name }} app-production-release.aab + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Warn deployers if Android production deploy failed if: ${{ failure() && fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} uses: 8398a7/action-slack@v3 @@ -147,9 +151,13 @@ jobs: env: DEVELOPER_ID_SECRET_PASSPHRASE: ${{ secrets.DEVELOPER_ID_SECRET_PASSPHRASE }} - - name: Build production desktop app - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: npm run desktop-build + - name: Build desktop app + run: | + if [[ ${{ env.SHOULD_DEPLOY_PRODUCTION }} == 'true' ]]; then + npm run desktop-build + else + npm run desktop-build-staging + fi env: CSC_LINK: ${{ secrets.CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} @@ -159,18 +167,17 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} GCP_GEOLOCATION_API_KEY: $${{ secrets.GCP_GEOLOCATION_API_KEY_PRODUCTION }} + - name: Upload desktop build to GitHub Workflow + uses: actions/upload-artifact@v4 + with: + name: NewExpensify.dmg + path: desktop-build/NewExpensify.dmg - - name: Build staging desktop app - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: npm run desktop-build-staging + - name: Upload desktop build to GitHub Release + if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} + run: gh release upload ${{ github.event.release.tag_name }} desktop-build/NewExpensify.dmg env: - CSC_LINK: ${{ secrets.CSC_LINK }} - CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - 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_STAGING }} + GITHUB_TOKEN: ${{ github.token }} iOS: name: Build and deploy iOS @@ -191,7 +198,7 @@ jobs: uses: ./.github/actions/composite/setupNode - name: Setup Ruby - uses: ruby/setup-ruby@a05e47355e80e57b9a67566a813648fa67d92011 + uses: ruby/setup-ruby@v1.187.0 with: ruby-version: '2.7' bundler-cache: true @@ -236,43 +243,45 @@ jobs: env: LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + - name: Set iOS version in ENV + run: echo "IOS_VERSION=$(echo '${{ github.event.release.tag_name }}' | tr '-' '.')" >> "$GITHUB_ENV" + - name: Run Fastlane - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane ios beta + run: bundle exec fastlane ios ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'production' || 'beta' }} env: APPLE_CONTACT_EMAIL: ${{ secrets.APPLE_CONTACT_EMAIL }} APPLE_CONTACT_PHONE: ${{ secrets.APPLE_CONTACT_PHONE }} APPLE_DEMO_EMAIL: ${{ secrets.APPLE_DEMO_EMAIL }} APPLE_DEMO_PASSWORD: ${{ secrets.APPLE_DEMO_PASSWORD }} + VERSION: ${{ env.IOS_VERSION }} - name: Archive iOS sourcemaps - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ios-sourcemap-${{ github.ref_name }} path: main.jsbundle.map - - name: Upload iOS version to GitHub artifacts + - name: Upload iOS build to GitHub artifacts if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: New Expensify.ipa path: /Users/runner/work/App/App/New Expensify.ipa - - name: Upload iOS version to Browser Stack + - name: Upload iOS build to Browser Stack if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} run: curl -u "$BROWSERSTACK" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@/Users/runner/work/App/App/New Expensify.ipa" env: BROWSERSTACK: ${{ secrets.BROWSERSTACK }} - - name: Set iOS version in ENV - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: echo "IOS_VERSION=$(echo '${{ github.event.release.tag_name }}' | tr '-' '.')" >> "$GITHUB_ENV" - - - name: Run Fastlane for App Store release + - name: Upload iOS build to GitHub Release if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane ios production + run: | + RUN_ID="$(gh run list --workflow platformDeploy.yml --event push --branch ${{ github.event.release.tag_name }} --json databaseId --jq '.[0].databaseId')" + gh run download "$RUN_ID" --name 'New Expensify.ipa' + gh release upload ${{ github.event.release.tag_name }} 'New Expensify.ipa' env: - VERSION: ${{ env.IOS_VERSION }} + GITHUB_TOKEN: ${{ github.token }} - name: Warn deployers if iOS production deploy failed if: ${{ failure() && fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} @@ -314,41 +323,33 @@ jobs: aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - - name: Build web for production - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: npm run build - - - name: Build web for staging - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: npm run build-staging - - - name: Build storybook docs for production - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: npm run storybook-build - continue-on-error: true + - name: Build web + run: | + if [[ ${{ env.SHOULD_DEPLOY_PRODUCTION }} == 'true' ]]; then + npm run build + else + npm run build-staging + fi - - name: Build storybook docs for staging - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: npm run storybook-build-staging + - name: Build storybook docs continue-on-error: true + run: | + if [[ ${{ env.SHOULD_DEPLOY_PRODUCTION }} == 'true' ]]; then + npm run storybook-build + else + npm run storybook-build-staging + fi - - name: Deploy production to S3 - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: aws s3 cp --recursive --acl public-read "$GITHUB_WORKSPACE"/dist s3://expensify-cash/ && aws s3 cp --acl public-read --content-type 'application/json' --metadata-directive REPLACE s3://expensify-cash/.well-known/apple-app-site-association s3://expensify-cash/.well-known/apple-app-site-association && aws s3 cp --acl public-read --content-type 'application/json' --metadata-directive REPLACE s3://expensify-cash/.well-known/apple-app-site-association s3://expensify-cash/apple-app-site-association - - - name: Deploy staging to S3 - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: aws s3 cp --recursive --acl public-read "$GITHUB_WORKSPACE"/dist s3://staging-expensify-cash/ && aws s3 cp --acl public-read --content-type 'application/json' --metadata-directive REPLACE s3://staging-expensify-cash/.well-known/apple-app-site-association s3://staging-expensify-cash/.well-known/apple-app-site-association && aws s3 cp --acl public-read --content-type 'application/json' --metadata-directive REPLACE s3://staging-expensify-cash/.well-known/apple-app-site-association s3://staging-expensify-cash/apple-app-site-association - - - name: Purge production Cloudflare cache - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: /home/runner/.local/bin/cli4 --verbose --delete hosts=["new.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache + - name: Deploy to S3 + run: | + aws s3 cp --recursive --acl public-read "$GITHUB_WORKSPACE"/dist ${{ env.S3_URL }}/ + aws s3 cp --acl public-read --content-type 'application/json' --metadata-directive REPLACE ${{ env.S3_URL }}/.well-known/apple-app-site-association ${{ env.S3_URL }}/.well-known/apple-app-site-association + aws s3 cp --acl public-read --content-type 'application/json' --metadata-directive REPLACE ${{ env.S3_URL }}/.well-known/apple-app-site-association ${{env.S3_URL }}/apple-app-site-association env: - CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} + S3_URL: s3://${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && '' || 'staging-' }}expensify-cash - - name: Purge staging Cloudflare cache - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: /home/runner/.local/bin/cli4 --verbose --delete hosts=["staging.new.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache + - name: Purge Cloudflare cache + run: /home/runner/.local/bin/cli4 --verbose --delete hosts=["${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && '' || 'staging.' }}new.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache env: CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index f09865de0194..bb1cd71c9518 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -32,7 +32,9 @@ jobs: - name: Exit failed workflow if: ${{ needs.typecheck.result == 'failure' || needs.lint.result == 'failure' || needs.test.result == 'failure' }} - run: exit 1 + run: | + echo "Checks failed, exiting ~ typecheck: ${{ needs.typecheck.result }}, lint: ${{ needs.lint.result }}, test: ${{ needs.test.result }}" + exit 1 chooseDeployActions: runs-on: ubuntu-latest diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml index a2aadc331f19..a0489a52711b 100644 --- a/.github/workflows/reassurePerformanceTests.yml +++ b/.github/workflows/reassurePerformanceTests.yml @@ -47,12 +47,12 @@ jobs: BASELINE_BRANCH=${BASELINE_BRANCH:="main"} git fetch origin "$BASELINE_BRANCH" --no-tags --depth=1 git switch "$BASELINE_BRANCH" - npm install --force + npm install --force || (rm -rf node_modules && npm install --force) NODE_OPTIONS=--experimental-vm-modules npx reassure --baseline git switch --force --detach - git merge --no-commit --allow-unrelated-histories "$BASELINE_BRANCH" -X ours git checkout --ours . - npm install --force + npm install --force || (rm -rf node_modules && npm install --force) NODE_OPTIONS=--experimental-vm-modules npx reassure --branch - name: Validate output.json diff --git a/.github/workflows/sendReassurePerfData.yml b/.github/workflows/sendReassurePerfData.yml index 53b3d3374a9e..30a30918f4f6 100644 --- a/.github/workflows/sendReassurePerfData.yml +++ b/.github/workflows/sendReassurePerfData.yml @@ -25,7 +25,7 @@ jobs: shell: bash run: | set -e - npx reassure --baseline + NODE_OPTIONS=--experimental-vm-modules npx reassure --baseline - name: Get merged pull request id: getMergedPullRequest diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index 10912aaeb436..e0ff9296abc0 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -88,7 +88,7 @@ jobs: java-version: '17' - name: Setup Ruby - uses: ruby/setup-ruby@a05e47355e80e57b9a67566a813648fa67d92011 + uses: ruby/setup-ruby@v1.187.0 with: ruby-version: '2.7' bundler-cache: true @@ -117,6 +117,7 @@ jobs: id: runFastlaneBetaTest run: bundle exec fastlane android build_internal env: + RUBYOPT: '-rostruct' S3_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }} S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} S3_BUCKET: ad-hoc-expensify-cash @@ -125,7 +126,7 @@ jobs: MYAPP_UPLOAD_KEY_PASSWORD: ${{ secrets.MYAPP_UPLOAD_KEY_PASSWORD }} - name: Upload Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: android path: ./android_paths.json @@ -161,7 +162,7 @@ jobs: run: sudo xcode-select -switch /Applications/Xcode_15.0.1.app - name: Setup Ruby - uses: ruby/setup-ruby@a05e47355e80e57b9a67566a813648fa67d92011 + uses: ruby/setup-ruby@v1.187.0 with: ruby-version: '2.7' bundler-cache: true @@ -217,7 +218,7 @@ jobs: S3_REGION: us-east-1 - name: Upload Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ios path: ./ios_paths.json @@ -321,7 +322,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} - name: Download Artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - name: Read JSONs with android paths diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 476b01f87b07..3bfc0ed28d1a 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -34,7 +34,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' '__mocks__/*.js' '.storybook/*.js' 'assets/*.js' 'config/*.js' 'desktop/*.js' 'jest/*.js' 'scripts/*.js' 'tests/*.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) + 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' 'workflow_tests/*.js' '.github/libs/*.js' '.github/scripts/*.js' ':!src/libs/SearchParser/*.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/.prettierignore b/.prettierignore index 09de20ba30b0..a9f7e1464529 100644 --- a/.prettierignore +++ b/.prettierignore @@ -19,3 +19,6 @@ package-lock.json src/libs/E2E/reactNativeLaunchingTest.ts # Temporary while we keep react-compiler in our repo lib/** + +# Automatically generated files +src/libs/SearchParser/searchParser.js diff --git a/.storybook/webpack.config.ts b/.storybook/webpack.config.ts index a7811a5a387d..0aa38f2e3b82 100644 --- a/.storybook/webpack.config.ts +++ b/.storybook/webpack.config.ts @@ -100,6 +100,11 @@ const webpackConfig = ({config}: {config: Configuration}) => { }), ); + config.module.rules?.push({ + test: /\.lottie$/, + type: 'asset/resource', + }); + return config; }; diff --git a/android/app/build.gradle b/android/app/build.gradle index 7d3b8aa46f35..1ea454570f88 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,6 +15,7 @@ fullstory { org 'o-1WN56P-na1' enabledVariants 'all' logcatLevel 'debug' + recordOnStart false } react { @@ -107,8 +108,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009000504 - versionName "9.0.5-4" + versionCode 1009001102 + versionName "9.0.11-2" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/assets/images/LaptopwithSecondScreenandHourglass.svg b/assets/images/LaptopwithSecondScreenandHourglass.svg new file mode 100644 index 000000000000..98010400f518 --- /dev/null +++ b/assets/images/LaptopwithSecondScreenandHourglass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/circular-arrow-backwards.svg b/assets/images/circular-arrow-backwards.svg index 209c0aea5fa7..7dab140cf78b 100644 --- a/assets/images/circular-arrow-backwards.svg +++ b/assets/images/circular-arrow-backwards.svg @@ -1,9 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/emptystate__expensifycard.svg b/assets/images/emptystate__expensifycard.svg new file mode 100644 index 000000000000..c5699c059e69 --- /dev/null +++ b/assets/images/emptystate__expensifycard.svg @@ -0,0 +1,909 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/folder-with-papers.svg b/assets/images/product-illustrations/folder-with-papers.svg new file mode 100644 index 000000000000..c0c35bc9b4ba --- /dev/null +++ b/assets/images/product-illustrations/folder-with-papers.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__empty-state.svg b/assets/images/simple-illustrations/simple-illustration__empty-state.svg new file mode 100644 index 000000000000..2aeb9e8f4fb5 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__empty-state.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg b/assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg new file mode 100644 index 000000000000..1c2bb43a04ab --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__tire.svg b/assets/images/simple-illustrations/simple-illustration__tire.svg new file mode 100644 index 000000000000..db84f054568a --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__tire.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__virtualcard.svg b/assets/images/simple-illustrations/simple-illustration__virtualcard.svg new file mode 100644 index 000000000000..6a96a874b97e --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__virtualcard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/babel.config.js b/babel.config.js index 7010404f2a31..dcf12ba4d91e 100644 --- a/babel.config.js +++ b/babel.config.js @@ -4,6 +4,9 @@ const IS_E2E_TESTING = process.env.E2E_TESTING === 'true'; const ReactCompilerConfig = { runtimeModule: 'react-compiler-runtime', + environment: { + enableTreatRefLikeIdentifiersAsRefs: true, + }, }; const defaultPresets = ['@babel/preset-react', '@babel/preset-env', '@babel/preset-flow', '@babel/preset-typescript']; const defaultPlugins = [ diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index d6ab46c809ef..0ddaafda2d82 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -31,15 +31,6 @@ This project and everyone participating in it is governed by the Expensify [Code ## Restrictions At this time, we are not hiring contractors in Crimea, North Korea, Russia, Iran, Cuba, or Syria. -## Slack channels -All contributors should be a member of a shared Slack channel called [#expensify-open-source](https://expensify.slack.com/archives/C01GTK53T8Q) -- this channel is used to ask **general questions**, facilitate **discussions**, and make **feature requests**. - -Before requesting an invite to Slack, please ensure your Upwork account is active, since we only pay via Upwork (see [below](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#payment-for-contributions)). To request an invite to Slack, email contributors@expensify.com with the subject `Slack Channel Invites`. We'll send you an invite! - -Note: Do not send direct messages to the Expensify team in Slack or Expensify Chat, they will not be able to respond. - -Note: if you are hired for an Upwork job and have any job-specific questions, please ask in the GitHub issue or pull request. This will ensure that the person addressing your question has as much context as possible. - ## Reporting Vulnerabilities If you've found a vulnerability, please email security@expensify.com with the subject `Vulnerability Report` instead of creating an issue. diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index 7f416951b58c..85ffbb30c360 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -69,8 +69,8 @@ platforms: icon: /assets/images/handshake.svg description: Discover the benefits of becoming an Expensify Partner. - - href: integrations - title: Integrations + - href: connections + title: Connections icon: /assets/images/simple-illustration__monitor-remotesync.svg description: Integrate with accounting or HR software to streamline expense approvals. diff --git a/docs/_includes/hub.html b/docs/_includes/hub.html index 7f1e25243c09..cac0eaeec382 100644 --- a/docs/_includes/hub.html +++ b/docs/_includes/hub.html @@ -12,13 +12,17 @@

{{ hub.description }}

+{% assign sortedSectionsAndArticles = hub.sections | concat: hub.articles | sort: 'title' %} +
- {% for section in hub.sections %} - {% include section-card.html platform=activePlatform hub=hub.href section=section.href title=section.title %} - {% endfor %} - {% for article in hub.articles %} - {% include article-card.html hub=hub.href href=article.href title=article.title platform=activePlatform %} + {% for item in sortedSectionsAndArticles %} + + {% if item.articles %} + {% include section-card.html platform=activePlatform hub=hub.href section=item.href title=item.title %} + {% else %} + {% include article-card.html hub=hub.href href=item.href title=item.title platform=activePlatform %} + {% endif %} {% endfor %}
diff --git a/docs/_includes/lhn-template.html b/docs/_includes/lhn-template.html index 80302f33f52e..d8298aa22aa7 100644 --- a/docs/_includes/lhn-template.html +++ b/docs/_includes/lhn-template.html @@ -21,43 +21,43 @@ {% for platform in site.data.routes.platforms %} {% if platform.href == activePlatform %}
  • - - + {% for hub in platform.hubs %}
      {% if hub.href == activeHub %} - - +
        - {% for section in hub.sections %} -
      • - {% if section.href == activeSection %} - - - {{ section.title }} - -
          - {% for article in section.articles %} - {% assign article_href = section.href | append: '/' | append: article.href %} - {% include lhn-article-link.html platform=activePlatform hub=hub.href href=article_href title=article.title %} - {% endfor %} -
        - {% else %} - - - {{ section.title }} - - {% endif %} - -
      • - {% endfor %} - - {% for article in hub.articles %} - {% include lhn-article-link.html platform=activePlatform hub=hub.href href=article.href title=article.title %} + {% assign sortedSectionsAndArticles = hub.sections | concat: hub.articles | sort: 'title' %} + {% for item in sortedSectionsAndArticles %} + {% if item.articles %} +
      • + {% if item.href == activeSection %} + +
          + {% for article in item.articles %} + {% assign article_href = item.href | append: '/' | append: article.href %} + {% include lhn-article-link.html platform=activePlatform hub=hub.href href=article_href title=article.title %} + {% endfor %} +
        + {% else %} + + + {{ item.title }} + + {% endif %} +
      • + {% else %} + {% include lhn-article-link.html platform=activePlatform hub=hub.href href=item.href title=item.title %} + {% endif %} {% endfor %}
      {% else %} diff --git a/docs/_includes/section.html b/docs/_includes/section.html index 786e7d997462..b6def157e954 100644 --- a/docs/_includes/section.html +++ b/docs/_includes/section.html @@ -15,7 +15,8 @@

      - {% for article in section.articles %} + {% assign sortedArticles = section.articles | sort: 'order', 'last' | default: 999 %} + {% for article in sortedArticles %} {% assign article_href = section.href | append: '/' | append: article.href %} {% include article-card.html hub=hub.href href=article_href title=article.title platform=activePlatform %} {% endfor %} diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 4232c565e715..cc45e83efa59 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -14,7 +14,7 @@ - + diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 29b02d8aeb00..f3bf1035ed56 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -749,6 +749,7 @@ button { width: 20px; height: 20px; cursor: pointer; + display: inline-block; } .homepage { diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Pay-Bills.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Pay-Bills.md deleted file mode 100644 index 81dcf3488462..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/Pay-Bills.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -title: Pay Bills -description: How to receive and pay company bills in Expensify ---- - - -# Overview -Simplify your back office by receiving bills from vendors and suppliers in Expensify. Anyone with or without an Expensify account can send you a bill, and Expensify will file it as a Bill and help you issue the payment. - -# How to Receive Vendor or Supplier Bills in Expensify - -There are three ways to get a vendor or supplier bill into Expensify: - -**Option 1:** Have vendors send bills to Expensify directly: Ask your vendors to email all bills to your Expensify billing intake email. - -**Option 2:** Forward bills to Expensify: If your bills are emailed to you, you can forward those bills to your Expensify billing intake email yourself. - -**Option 3:** Manually upload bills to Expensify: If you receive physical bills, you can manually create a Bill in Expensify on the web from the Reports page: -1. Click **New Report** and choose **Bill** -2. Add the expense details and vendor's email address to the pop-up window -3. Upload a pdf/image of the bill -4. Click **Submit** - -# How to Pay Bills - -There are multiple ways to pay Bills in Expensify. Let’s go over each method below: - -## ACH bank-to-bank transfer - -To use this payment method, you must have a business bank account connected to your Expensify account. - -To pay with an ACH bank-to-bank transfer: - -1. Sign in to your Expensify account on the web at www.expensify.com. -2. Go to the Inbox or Reports page and locate the Bill that needs to be paid. -3. Click the **Pay** button to be redirected to the Bill. -4. Choose the ACH option from the drop-down list. -5. Follow the prompts to connect your business bank account to Expensify. - -**Fees:** None - -## Pay using a credit or debit card - -This option is available to all US and International customers receiving an bill from a US vendor with a US business bank account. - -To pay with a credit or debit card: -1. Sign-in to your Expensify account on the web app at www.expensify.com. -2, Click on the Bill you’d like to pay to see the details. -3, Click the **Pay** button. -4. You’ll be prompted to enter your credit card or debit card details. - -**Fees:** Includes 2.9% credit card payment fee - -## Venmo - -If both you and the vendor have Venmo setup in their Expensify account, you can opt to pay the bill through Venmo. - -**Fees:** Venmo charges a 3% sender’s fee - -## Pay Outside of Expensify - -If you are not able to pay using one of the above methods, then you can mark the Bill as paid manually in Expensify to update its status and indicate that you have made payment outside Expensify. - -To mark a Bill as paid outside of Expensify: - -1. Sign-in to your Expensify account on the web app at www.expensify.com. -2. Click on the Bill you’d like to pay to see the details. -3. Click on the **Reimburse** button. -4. Choose **I’ll do it manually** - -**Fees:** None - -# Deep Dive: How company bills and vendor invoices are processed in Expensify - -Here is how a vendor or supplier bill goes from received to paid in Expensify: - -1. When a vendor or supplier bill is received in Expensify via, the document is SmartScanned automatically and a Bill is created. The Bill is owned by the primary domain contact, who will see the Bill on the Reports page on their default group policy. -2. When the Bill is ready for processing, it is submitted and follows the primary domain contact’s approval workflow. Each time the Bill is approved, it is visible in the next approver's Inbox. -3. The final approver pays the Bill from their Expensify account on the web via one of the methods. -4. The Bill is coded with the relevant imported GL codes from a connected accounting software. After it has finished going through the approval workflow the Bill can be exported back to the accounting package connected to the policy. - - -{% include faq-begin.md %} - -## What is my company's billing intake email? -Your billing intake email is [yourdomain.com]@expensify.cash. Example, if your domain is `company.io` your billing email is `company.io@expensify.cash`. - -## When a vendor or supplier bill is sent to Expensify, who receives it? - -Bills are received by the Primary Contact for the domain. This is the email address listed at **Settings > Domains > Domain Admins**. - -## Who can view a Bill in Expensify? - -Only the primary contact of the domain can view a Bill. - -## Who can pay a Bill? - -Only the primary domain contact (owner of the bill) will be able to pay the Mill. - -## How can you share access to Bills? - -To give others the ability to view a Bill, the primary contact can manually “share” the Bill under the Details section of the report via the Sharing Options button. -To give someone else the ability to pay Bills, the primary domain contact will need to grant those individuals Copilot access to the primary domain contact's account. - -## Is Bill Pay supported internationally? - -Payments are currently only supported for users paying in United States Dollars (USD). - -## What’s the difference between a Bill and an Invoice in Expensify? - -A Bill is a payable which represents an amount owed to a payee (usually a vendor or supplier), and is usually created from a vendor invoice. An Invoice is a receivable, and indicates an amount owed to you by someone else. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursements.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursements.md deleted file mode 100644 index a31c0a582fd7..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursements.md +++ /dev/null @@ -1,42 +0,0 @@ -# Overview - -If you want to know more about how and when you’ll be reimbursed through Expensify, we’ve answered your questions below. - -# How to Get Reimbursed - -To get paid back after submitting a report for reimbursement, you’ll want to be sure to connect your bank account. You can do that under **Settings** > **Account** > **Payments** > **Add a Deposit Account**. Once your employer has approved your report, the reimbursement will be paid into the account you added. - -# Deep Dive - -## Reimbursement Timing - -### US Bank Accounts - -If your company uses Expensify's ACH reimbursement we'll first check to see if the report is eligible for Rapid Reimbursement (next business day). For a report to be eligible for Rapid Reimbursement it must fall under two limits: - - - $100 per deposit bank account per day or less for the individuals being reimbursed or businesses receiving payments for bills. - - Less than $10,000 being disbursed in a 24-hour time period from the verified bank account being used to pay the reimbursement. - -If the request passes both checks, then you can expect to see funds deposited into your bank account on the next business day. - -If either limit has been reached, then you can expect to see funds deposited within your bank account within the typical ACH timeframe of 3-5 business days. - -### International Bank Accounts - -If receiving reimbursement to an international deposit account via Global Reimbursement, you should expect to see funds deposited in your bank account within 4 business days. - -## Bank Processing Timeframes - -Banks only process transactions and ACH activity on weekdays that are not bank holidays. These are considered business days. Additionally, the business day on which a transaction will be processed depends upon whether or not a request is created before or after the cutoff time, which is typically 3 pm PST. -For example, if your reimbursement is initiated at 4 pm on Wednesday, this is past the bank's cutoff time, and it will not begin processing until the next business day. -If that same reimbursement starts processing on Thursday, and it's estimated to take 3-5 business days, this will cover a weekend, and both days are not considered business days. So, assuming there are no bank holidays added into this mix, here is how that reimbursement timeline would play out: - -**Wednesday**: Reimbursement initiated after 3 pm PST; will be processed the next business day by your company’s bank. -**Thursday**: Your company's bank will begin processing the withdrawal request -**Friday**: Business day 1 -**Saturday**: Weekend -**Sunday**: Weekend -**Monday**: Business day 2 -**Tuesday**: Business day 3 -**Wednesday**: Business day 4 -**Thursday**: Business day 5 diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports.md deleted file mode 100644 index 69b39bae2874..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Reimbursing Reports -description: How to reimburse employee expense reports ---- -# Overview - -One essential aspect of the Expensify workflow is the ability to reimburse reports. This process allows for the reimbursement of expenses that have been submitted for review to the person who made the request. Detailed explanations of the various methods for reimbursing reports within Expensify are provided below. - -# How to reimburse reports - -Reports can be reimbursed directly within Expensify by clicking the **Reimburse** button at the top of the report to reveal the available reimbursement options. - -## Direct Deposit - -To reimburse directly in Expensify, the following needs to be already configured: -- The employee that's receiving reimbursement needs to add a deposit bank account to their Expensify account (under **Settings > Account > Payments > Add a Deposit-only Bank Account**) -- The reimburser needs to add a business bank account to Expensify (under **Settings > Account > Payments > Add a Verified Business Bank Account**) -- The reimburser needs to ensure Expensify is whitelisted to withdraw funds from the bank account - -If all of those settings are in place, to reimburse a report, you will click **Reimburse** on the report and then select **Via Direct Deposit (ACH)**. - -![Reimbursing Reports Dropdown]({{site.url}}/assets/images/Reimbursing Reports Dropdown.png){:width="100%"} - -## Indirect or Manual Reimbursement - -If you don't have the option to utilize direct reimbursement, you can choose to mark a report as reimbursed by clicking the **Reimburse** button at the top of the report and then selecting **I’ll do it manually – just mark as reimbursed**. - -This will effectively mark the report as reimbursed within Expensify, but you'll handle the payment elsewhere, outside of the platform. - -# Best Practices -- Plan ahead! Consider sharing a business bank account with multiple workspace admins so they can reimburse employee reports if you're unavailable. We recommend having at least two workspace admins with reimbursement permissions. - -- Understand there is a verification process when sharing a business bank account. The new reimburser will need access to the business bank account’s transaction history (or access to someone who has access to it) to verify the set of test transactions sent from Expensify. - -- Get into the routine of having every new employee connect a deposit-only bank account to their Expensify account. This will ensure reimbursements happen in a timely manner. - -- Employees can see the expected date of their reimbursement at the top of and in the comments section of their report. - -# How to cancel a reimbursement - -Reimbursed a report by mistake? No worries! Any workspace admin with access to the same Verified Bank Account can cancel the reimbursement from within the report until it is withdrawn from the payment account. - -**Steps to Cancel an ACH Reimbursement:** -1. On your web account, navigate to the Reports page -2. Open the report -3. Click **Cancel Reimbursement** -4. After the prompt, "Are you sure you want to cancel the reimbursement?" click **Cancel Reimbursement**. - -It's important to note that there is a small window of time (roughly less than 24 hours) when a reimbursement can be canceled. If you don't see the **Cancel Reimbursement** button on a report, this means your bank has already begun withdrawing the funds from the reimbursement account and the withdrawal cannot be canceled. - -In that case, you’ll want to contact your bank directly to see if they can cancel the reimbursement on their end - or manage the return of funds directly with your employee, outside of Expensify. - -If you cancel a reimbursement after the withdrawal has started, it will be automatically returned to your Verified Bank Account within 3-5 business days. - -# Deep Dive - -## Rapid Reimbursement -If your company uses Expensify's ACH reimbursement, we'll first check to see if the report is eligible for Rapid Reimbursement (next business day). For a report to be eligible for Rapid Reimbursement, it must fall under two limits: -- $100 per deposit only bank account per day for the individuals being reimbursed or businesses receiving payments for bills -- $10,000 per verified bank account for the company paying bills and reimbursing - -If neither limit is met, you can expect to see funds deposited into your bank account on the next business day. - -If either limit has been reached, you can expect funds deposited within your bank account within the typical ACH time frame of four to five business days. - -Rapid Reimbursement is not available for non-US-based reimbursement. If you are receiving a reimbursement to a non-US-based deposit account, you should expect to see the funds deposited in your bank account within four business days. - -{% include faq-begin.md %} - -## Who can reimburse reports? -Only a workspace admin who has added a verified business bank account to their Expensify account can reimburse employees. - -## Why can’t I trigger direct ACH reimbursements in bulk? - -Instead of a bulk reimbursement option, you can set up automatic reimbursement. With this configured, reports below a certain threshold (defined by you) will be automatically reimbursed via ACH as soon as they're "final approved." - -To set your manual reimbursement threshold, head to **Settings > Workspace > Group > _[Workspace Name]_ > Reimbursement > Manual Reimbursement**. - -![Manual Reimbursement]({{site.url}}/assets/images/Reimbursing Manual.png){:width="100%"} - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Cancel-an-ACH-Reimbursement.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Cancel-an-ACH-Reimbursement.md new file mode 100644 index 000000000000..ab75067b1a7f --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Cancel-an-ACH-Reimbursement.md @@ -0,0 +1,17 @@ +--- +title: Cancel an ACH reimbursement +description: Cancel an ACH payment after it has been sent +--- +
      + +If a report was reimbursed with an ACH payment by mistake or otherwise needs to be canceled, a Workspace Admin with access to the verified bank account can cancel the reimbursement up until it is withdrawn from the payment account. + +To cancel an ACH reimbursement, + +1. Click the **Reports** tab. +2. Open the report. +3. Click **Cancel Reimbursement**. + - If you don’t see the Cancel Reimbursement button, this means your bank has already begun transferring the funds and it cannot be canceled. In this case, you’ll need to contact your bank for cancellation. +4. Click **Cancel Reimbursement** to confirm cancellation. + +
      diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-Payments.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-Payments.md new file mode 100644 index 000000000000..00fb236e1763 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-Payments.md @@ -0,0 +1,36 @@ +--- +title: Receive payments +description: Receive reimbursements from an employer +--- +
      + +To get paid after submitting a report for reimbursement, you must first connect a personal U.S. bank account or a personal Australian bank account. Then once your employer approves your report or invoice, the reimbursement will be paid directly to your bank account. +Funds for U.S. and global payments are generally deposited within a maximum of four to five business days: 2 days for the funds to be debited from the business bank account, and 2-3 business days for the ACH or wire to deposit into the employee account. + +However, banks only process ACH transactions before the daily cutoff (generally 3 p.m. PST) and only on business weekdays that are not bank holidays. This may affect when the payment is disbursed. If the payment qualifies for Rapid Reimbursement, you may receive the payment sooner. + +{% include info.html %} +Companies also have the option to submit payments outside of Expensify via check, cash, or a third-party payment processor. Check with your Workspace Admin to know how you will be reimbursed. +{% include end-info.html %} + +# Rapid Reimbursement (U.S. only) + +With Expensify’s ACH reimbursement, payments may be eligible for reimbursement by the next business day with Rapid Reimbursement if they meet the following qualifications: +- **Deposit-only accounts**: Payment must not exceed $100 +- **Verified business bank accounts**: The account does not disburse more than $10,000 within a 24-hour time period. + +If the payment amount exceeds the limit, funds will be deposited within the typical ACH time frame of four to five business days. + +{% include faq-begin.md %} + +**Is there a way I can track my payment?** + +For U.S. ACH payments and global reimbursements, the expected date of reimbursement is provided at the top of the report and in the comments section of the report. Funds will be deposited within the typical ACH time frame of four to five business days unless the payment is eligible for Rapid Reimbursement. + +**For global payments, what currency is the payment provided in?** + +Global payments are reimbursed in the recipient's currency. + +{% include faq-end.md %} + +
      diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Australian-Reports.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Australian-Reports.md new file mode 100644 index 000000000000..90a89ff3c75e --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Australian-Reports.md @@ -0,0 +1,63 @@ +--- +title: Reimburse Australian reports +description: Send payment for Australian expense reports +--- +
      + +Workspace Admins can reimburse AUD expense reports by downloading an .aba file containing the accounts needing payment and uploading the file to the bank. This can be done for a single report or for a batch of payments at once. + +{% include info.html %} +Your financial institution may require .aba files to include a self-balancing transaction. If you are unsure, check with your bank. Otherwise, the .aba file may not work with your bank’s internet banking platform. +{% include end-info.html %} + +# Reimburse a single report + +1. Open the report, invoice, or bill from the email or Concierge notification, or from the **Reports** tab. +2. Click the **Reimburse** dropdown and select **Via ABA File**. +3. Click **Generate ABA and Mark as Reimbursed**. +4. Click **Download**. +5. Upload the .aba file to your bank. For additional guidance, use any of the following bank guides: + - [ANZ Bank](https://www.anz.com.au/support/internet-banking/pay-transfer-business/payroll/import-file/) + - [CommBank](https://www.commbank.com.au/business/pds/003-279-importing-a-de-file.pdf) + - [Westpac](https://www.westpac.com.au/business-banking/online-banking/support-faqs/import-files/) + - [NAB](https://www.nab.com.au/business/online-banking/nab-connect/help) + - [Bendigo Bank](https://www.bendigobank.com.au/globalassets/documents/business/bulk-payments-user-guide.pdf) + - [Bank of Queensland](https://www.boq.com.au/help-and-support/online-banking/ob-faqs-and-support/faq-pfuf) + +# Send batch payments + +Once employees submit their expense reports, a Workspace Admin exports the reports (which contains the employees’ bank account information) and uploads the .aba file to the bank. + +## Step 1: Verify currency & reimbursement settings + +1. Hover over **Settings**, then click **Workspaces**. +2. Select the desired workspace. +3. Click the **Reports** tab on the left. +4. Click the Report Currency dropdown and select **AUD A$**. +5. Click the **Reimbursement** tab on the left. +6. Verify that **Indirect** is selected as the Reimbursement type or select it if not. + +## Step 2: Download and upload the ABA file + +1. Click the **Reports** tab. +2. Use the checkbox on the left to select all the reports needing payment. +3. Click **Bulk Actions** and select **Reimburse via ABA**. +5. Click **Generate ABA and Mark as Reimbursed**. +6. Click **Download Report**. +7. Upload the .aba file to your bank. For additional guidance, use any of the following bank guides: + - [ANZ Bank](https://www.anz.com.au/support/internet-banking/pay-transfer-business/payroll/import-file/) + - [CommBank](https://www.commbank.com.au/business/pds/003-279-importing-a-de-file.pdf) + - [Westpac](https://www.westpac.com.au/business-banking/online-banking/support-faqs/import-files/) + - [NAB](https://www.nab.com.au/business/online-banking/nab-connect/help) + - [Bendigo Bank](https://www.bendigobank.com.au/globalassets/documents/business/bulk-payments-user-guide.pdf) + - [Bank of Queensland](https://www.boq.com.au/help-and-support/online-banking/ob-faqs-and-support/faq-pfuf) + +{% include faq-begin.md %} + +**Can I use direct deposit for an AUD bank account?** + +No, AUD bank accounts do not rely on direct deposit or ACH. + +{% include faq-end.md %} + +
      diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills.md new file mode 100644 index 000000000000..b2cfbf833e13 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills.md @@ -0,0 +1,108 @@ +--- +title: Reimburse reports, invoices, and bills +description: Use direct deposit or indirect reimbursement to pay reports, invoices, and bills +--- +
      + +Once a report, invoice, or bill has been submitted and approved for reimbursement, you can reimburse the expenses using direct deposit or an indirect reimbursement option outside of Expensify (like cash, a check, or a third-party payment processor). + +# Pay with direct deposit + +{% include info.html %} +Before a report can be reimbursed with direct deposit, the employee or vendor receiving the reimbursement must connect their personal U.S. bank account, and the reimburser must connect a verified business bank account. + +Direct deposit is available for U.S. and global reimbursements. It is not available for Australian bank accounts. For Australian accounts, review the process for reimbursing Australian expenses. +{% include end-info.html %} + +1. Open the report, invoice, or bill from the email or Concierge notification, or from the **Reports** tab. +2. Click the **Reimburse** (for reports) or **Pay** (for bills and invoices) dropdown and select **Via Direct Deposit (ACH)**. +3. Confirm that the correct VBA is selected or use the dropdown menu to select a different one. +4. Click **Accept Terms & Pay**. + +The reimbursement is now queued in the daily batch. + +# Pay with indirect reimbursement + +When payments are submitted through Expensify, the report is automatically labeled as Reimbursed after it has been paid. However, if you are reimbursing reports via paper check, payroll, or any other method that takes place outside of Expensify, you’ll want to manually mark the bill as paid in Expensify to track the payment history. + +To label a report as Reimbursed after sending a payment outside of Expensify, + +1. Pay the report, invoice, or bill outside of Expensify. +2. Open the report, invoice, or bill from the email or Concierge notification, or from the **Reports** tab. +3. Click **Reimburse**. +4. Select **I’ll do it manually - just mark it as reimbursed**. This changes the report status to Reimbursed. + +Once the recipient has received the payment, the submitter can return to the report and click **Confirm** at the top of the report. This will change the report status to Reimbursed: CONFIRMED. + +{% include faq-begin.md %} + +**Is there a maximum total report total?** + +Expensify cannot process a reimbursement for any single report over $20,000. If you have a report with expenses exceeding $20,000 we recommend splitting the expenses into multiple reports. + +**Why is my account locked?** + +When you reimburse a report, you authorize Expensify to withdraw the funds from your account and send them to the person requesting reimbursement. If your bank rejects Expensify’s withdrawal request, your verified bank account is locked until the issue is resolved. + +Withdrawal requests can be rejected if the bank account has not been enabled for direct debit or due to insufficient funds. If you need to enable direct debits from your verified bank account, your bank will require the following details: +- The ACH CompanyIDs: 1270239450 and 4270239450 +- The ACH Originator Name: Expensify + +Once resolved, you can request to unlock the bank account by completing the following steps: + +1. Hover over **Settings**, then click **Account**. +2. Click the **Payments** tab. +3. Click **Bank Accounts**. +4. Next to the bank account, click **Fix**. + +Our support team will review and process the request within 4-5 business days. + +**How are bills and invoices processed in Expensify?** + +Here is the process a vendor or supplier bill goes through from receipt to payment: + +1. A vendor or supplier bill is received in Expensify. +2. Automatically, the document is SmartScanned and a bill is created for the primary domain contact. The bill will appear under the Reports tab on their default group policy. +3. When the bill is ready for processing, it is submitted and follows the primary domain contact’s approval workflow until the bill has been fully approved. +4. The final approver pays the bill from their Expensify account using one of the methods outlined in the article above. +5. If the workspace is connected to an accounting integration, the bill is automatically coded with the relevant imported GL codes and can be exported back to the accounting software. + +**When a vendor or supplier bill is sent to Expensify, who receives it?** + +Bills are sent to the primary contact for the domain. They’ll see a notification from Concierge on their Home page, and they’ll also receive an email. + +**How can I share access to bills?** + +By default, only the primary contact for the domain can view and pay the bill. However, you can allow someone else to view or pay bills. + +- **To allow someone to view a bill**: The primary contact can manually share the bill with others to allow them to view it. + 1. Click the **Reports** tab. + 2. Click the report. + 3. Click **Details** in the top right. + 4. Click the **Add Person** icon. + 5. Enter the email address or phone number of the person you will share the report with. + 6. Enter a message, if desired. + 7. Click **Share Report**. + +- **To allow someone to pay bills**: The primary domain contact can allow others to pay bills on their behalf by [assigning those individuals as Copilots](https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Assign-or-remove-a-Copilot). + +**Is Bill Pay supported internationally?** + +Payments are currently only supported for users paying in United States Dollars (USD). + +**What’s the difference between a bill and an invoice?** + +- A **bill** is a payable that represents an amount owed to a payee (usually a vendor or supplier), and it is usually created from a vendor invoice. +- An **invoice** is a receivable that indicates an amount owed to you by someone else. + +**Who can reimburse reports?** + +Only a Workspace Admin who has added a verified business bank account to their Expensify account can reimburse employee reports. + +**Why can’t I trigger direct ACH reimbursements in bulk?** + +Expensify does not offer bulk reimbursement, but you can set up automatic reimbursement to automatically reimburse approved reports via ACH that do not exceed the threshold that you define. + +{% include faq-end.md %} + +
      diff --git a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md index 05366a91d9fa..f94e692f5e56 100644 --- a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md +++ b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md @@ -8,6 +8,15 @@ Whether you're encountering issues related to company cards, require assistance ## How to add company cards to Expensify You can add company credit cards under the Domain settings in your Expensify account by navigating to *Settings* > *Domain* > _Domain Name_ > *Company Cards* and clicking *Import Card/Bank* and following the prompts. +## To Locate Missing Card Transactions in Expensify +1. **Wait for Posting**: Bank transactions may take up to 24 hours to import into Expensify after they have "posted" at your bank. Ensure sufficient time has passed for transactions to appear. +2. **Update Company Cards**: Go to Settings > Domains > Company Cards. Click on the card in question and click "Update" to refresh the card feed. +3. **Reconcile Cards**: Navigate to the Reconciliation section under Settings > Domains > Company Cards. Refer to the detailed guide on how to use the [Reconciliation Dashboard](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Reconciliation#identifying-outstanding-unapproved-expenses-using-the-reconciliation-dashboard). +4. **Review Transactions**: Use the Reconciliation Dashboard to view all transactions within a specific timeframe. Transactions will display on the Expenses page based on their "Posted Date". If needed, uncheck the "use posted date" checkbox near the filters to view transactions based on their "Transaction Date" instead. +5. **Address Gaps**: If there is a significant gap in transactions or if transactions are still missing, contact Expensify's Concierge or your Account Manager. They can initiate a historical data update on your card feed to ensure all transactions are properly imported. + +Following these steps should help you identify and resolve any issues with missing card transactions in Expensify. + ## Known issues importing transactions The first step should always be to "Update" your card, either from Settings > Your Account > Credit Card Import or Settings > Domain > [Domain Name] > Company Cards for centrally managed cards. If a "Fix" or "Fix card" option appears, follow the steps to fix the connection. If this fails to import your missing transactions, there is a known issue whereby some transactions will not import for certain API-based company card connections. So far this has been reported on American Express, Chase and Wells Fargo. This can be temporarily resolved by creating the expenses manually instead: @@ -63,6 +72,24 @@ If you've answered "yes" to any of these questions, a Domain Admins need to upda Make sure you're importing your card in the correct spot in Expensify and selecting the right bank connection. For company cards, use the master administrative credentials to import your set of cards at *Settings* > *Domains* > _Domain Name_ > *Company Cards* > *Import Card*. Please note there are some things that cannot be bypassed within Expensify, including two-factor authentication being enabled within your bank account. This will prevent the connection from remaining stable and will need to be turned off on the bank side. +## Why Can’t I See the Transactions Before a Certain Date? +When importing a card into Expensify, the platform typically retrieves 30-90 days of historical transactions, depending on the card or account type. For commercial feeds, transactions cannot be imported before the bank starts sending data. If needed, banks can send backdated files, and Expensify can run a historical update upon request. + +Additionally, Expensify does not import transactions dated before the "start date" you specify when assigning the card. Unless transitioning from an old card to a new one to avoid duplicates, it's advisable to set the start date to "earliest possible" or leave it blank. + +For historical expenses that cannot be imported automatically, consider using Expensify's [company card](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import) or [personal card](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards#importing-expenses-via-a-spreadsheet) spreadsheet import method. This allows you to manually input missing transactions into the system. + +## Why Am I / Why Is My Employee Seeing Duplicates? +If an employee is seeing duplicate expenses, they may have accidentally imported the card as a personal credit card as well as having the Domain Admin assign them a company card. + +To troubleshoot: +- Have the employee navigate to their Settings > Your Account > Credit Card Import and confirm that their card is only listed once. +- If the card is listed twice, delete the entry without the "padlock" icon. + +**Important:** Deleting a duplicate card will delete all unapproved expenses from that transaction feed. Transactions associated with the remaining card will not be affected. If receipts were attached to those transactions, they will still be on the Expenses page, and the employee can click to SmartScan them again. + +Duplicate expenses might also occur if you recently unassigned and reassigned a company card with an overlapping start date. If this is the case and expenses on the “new” copy have not been submitted, you can unassign the card again and reassign it with a more appropriate start date. This action will delete all unsubmitted expenses from the new card feed. + ## What are the most reliable bank connections in Expensify?* All bank connections listed below are extremely reliable, but we recommend transacting with the Expensify Visa® Commercial Card. It also offers daily and monthly settlement, unapproved expense limits, realtime compliance for secure and efficient spending, and cash back on all US purchases. [Click here to learn more about the Expensify Card](https://use.expensify.com/company-credit-card). diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/ADP.md b/docs/articles/expensify-classic/connections/ADP.md similarity index 100% rename from docs/articles/expensify-classic/integrations/HR-integrations/ADP.md rename to docs/articles/expensify-classic/connections/ADP.md diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Additional-Travel-Integrations.md b/docs/articles/expensify-classic/connections/Additional-Travel-Integrations.md similarity index 100% rename from docs/articles/expensify-classic/integrations/travel-integrations/Additional-Travel-Integrations.md rename to docs/articles/expensify-classic/connections/Additional-Travel-Integrations.md diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Egencia.md b/docs/articles/expensify-classic/connections/Egencia.md similarity index 75% rename from docs/articles/expensify-classic/integrations/travel-integrations/Egencia.md rename to docs/articles/expensify-classic/connections/Egencia.md index 178621a62d90..35d232d2df67 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Egencia.md +++ b/docs/articles/expensify-classic/connections/Egencia.md @@ -24,7 +24,7 @@ Egencia controls the feed, so to connect Expensify you will need to: # How to Connect to a Central Purchasing Account Once your Egencia account manager has established the feed, you can automatically forward all Egencia booking receipts to a single Expensify account. To do this: 1. Open a chat with Concierge. -2. Tell Concierge “Please enable Central Purchasing Account for our Egencia feed. The account email is: xxx@yourdomain.com”. +2. Tell Concierge the address of your central purchasing account, “Please enable Central Purchasing Account for our Egencia feed. The account email is: xxx@yourdomain.com”. -The receipt the traveler receives is a "reservation expense." Reservation expenses are non-reimbursable and won’t be included in any integrated accounting system exports. The reservation sent to the traveler's account is added to their mobile app Trips feature so that the traveler can easily keep tabs on upcoming travel and receive trip notifications. +A receipt will be sent to both the traveler and the central account. The receipt sent to the traveler is a "reservation expense." Reservation expenses are non-reimbursable and won’t be included in any integrated accounting system exports. diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md b/docs/articles/expensify-classic/connections/Global-VaTax.md similarity index 100% rename from docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md rename to docs/articles/expensify-classic/connections/Global-VaTax.md diff --git a/docs/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO.md b/docs/articles/expensify-classic/connections/Google-Apps-SSO.md similarity index 100% rename from docs/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO.md rename to docs/articles/expensify-classic/connections/Google-Apps-SSO.md diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Greenhouse.md b/docs/articles/expensify-classic/connections/Greenhouse.md similarity index 100% rename from docs/articles/expensify-classic/integrations/HR-integrations/Greenhouse.md rename to docs/articles/expensify-classic/connections/Greenhouse.md diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Gusto.md b/docs/articles/expensify-classic/connections/Gusto.md similarity index 100% rename from docs/articles/expensify-classic/integrations/HR-integrations/Gusto.md rename to docs/articles/expensify-classic/connections/Gusto.md diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Indirect-Accounting-Integrations.md b/docs/articles/expensify-classic/connections/Indirect-Accounting-Integrations.md similarity index 100% rename from docs/articles/expensify-classic/integrations/accounting-integrations/Indirect-Accounting-Integrations.md rename to docs/articles/expensify-classic/connections/Indirect-Accounting-Integrations.md diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md b/docs/articles/expensify-classic/connections/Lyft.md similarity index 100% rename from docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md rename to docs/articles/expensify-classic/connections/Lyft.md diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Navan.md b/docs/articles/expensify-classic/connections/Navan.md similarity index 100% rename from docs/articles/expensify-classic/integrations/travel-integrations/Navan.md rename to docs/articles/expensify-classic/connections/Navan.md diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/QuickBooks-Time.md b/docs/articles/expensify-classic/connections/QuickBooks-Time.md similarity index 100% rename from docs/articles/expensify-classic/integrations/HR-integrations/QuickBooks-Time.md rename to docs/articles/expensify-classic/connections/QuickBooks-Time.md diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Rippling.md b/docs/articles/expensify-classic/connections/Rippling.md similarity index 100% rename from docs/articles/expensify-classic/integrations/HR-integrations/Rippling.md rename to docs/articles/expensify-classic/connections/Rippling.md diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md b/docs/articles/expensify-classic/connections/TravelPerk.md similarity index 100% rename from docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md rename to docs/articles/expensify-classic/connections/TravelPerk.md diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md b/docs/articles/expensify-classic/connections/Uber.md similarity index 90% rename from docs/articles/expensify-classic/integrations/travel-integrations/Uber.md rename to docs/articles/expensify-classic/connections/Uber.md index 16da0c0caa5b..213a90b6d288 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md +++ b/docs/articles/expensify-classic/connections/Uber.md @@ -22,3 +22,5 @@ Now, every time you use Uber for Business – be it for rides or meals – the r ![Uber integration set up steps: Connecting your account](https://help.expensify.com/assets/images/Uber1.png){:width="100%"} ![Uber integration set up steps: Selecting Expensify](https://help.expensify.com/assets/images/Uber2.png){:width="100%"} + +To disconnect Uber and Expensify, simply follow the above path and select Disconnect on the Expensify option. diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Workday.md b/docs/articles/expensify-classic/connections/Workday.md similarity index 100% rename from docs/articles/expensify-classic/integrations/HR-integrations/Workday.md rename to docs/articles/expensify-classic/connections/Workday.md diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md b/docs/articles/expensify-classic/connections/Zenefits.md similarity index 100% rename from docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md rename to docs/articles/expensify-classic/connections/Zenefits.md diff --git a/docs/articles/expensify-classic/connections/accelo/Accelo-Troubleshooting.md b/docs/articles/expensify-classic/connections/accelo/Accelo-Troubleshooting.md new file mode 100644 index 000000000000..fd0a6ca59069 --- /dev/null +++ b/docs/articles/expensify-classic/connections/accelo/Accelo-Troubleshooting.md @@ -0,0 +1,7 @@ +--- +title: Accelo Troubleshooting +description: Accelo Troubleshooting +order: 3 +--- + +# Coming soon diff --git a/docs/articles/expensify-classic/connections/accelo/Configure-Accelo.md b/docs/articles/expensify-classic/connections/accelo/Configure-Accelo.md new file mode 100644 index 000000000000..a71671ad8da0 --- /dev/null +++ b/docs/articles/expensify-classic/connections/accelo/Configure-Accelo.md @@ -0,0 +1,55 @@ +--- +title: Configure Accelo +description: Configure Accelo's export and coding settings. +order: 2 +--- + +# Configure Accelo +1. Log into Accelo +2. Navigate to the Integrations page +3. Select the **Expensify** tab +4. Enter Expensify Integration Server Credentials +5. Provide your Expensify Integration Server’s Partner User ID and Partner User Secret +6. Click “Save” to complete the setup + +## Upload Accelo Project Codes as Tags in Expensify +Once you have connected Accelo to Expensify, the next step is to upload your Accelo Project Codes as Tags in Expensify: +1. Head to to **Settings** > **Workspaces** > **Group** > _[Workspace Name]_ > **Tags** +2. Choose to upload a CSV +3. If you also integrate with Xero or QuickBooks Online, you must upload your Project Codes by appending your tags + - Go to **Settings** > **Workspaces** > **Group** > _[Workspace Name]_ > **Tags** + - Click on **Append a custom tag list from a CSV** to upload your Project Codes via a CSV + +## Information sync between Expensify and Accelo +The Accelo integration does a one-way sync, which means it brings expenses from Expensify into Accelo. When this happens, it transfers specific information from Expensify expenses to Accelo: + +| Expensify | Accelo | +|---------------------|-----------------------| +| Comment | Title | +| Date | Date Incurred | +| Category | Type | +| Tags | Against (relevant Project, Ticket or Retainer) | +| Distance (mileage) | Quantity | +| Hours (time expenses) | Quantity | +| Amount | Purchase Price and Sale Price | +| Reimbursable? | Reimbursable? | +| Billable? | Billable? | +| Receipt | Attachment | +| Tax Rate | Tax Code | +| Attendees | Submitted By | + +## Expense Status +The status of your expense report in Expensify is also synced in Accelo. + +| Expensify Report Status | Accelo Expense Status | +|-------------------------|-----------------------| +| Open | Submitted | +| Submitted | Submitted | +| Approved | Approved | +| Reimbursed | Approved | +| Rejected | Declined | +| Archived | Approved | +| Closed | Approved | + +## Importing expenses from Expensify to Accelo +Accelo checks Expensify for new expenses once every hour. It automatically adds expenses that have been created or changed since the connection's last sync. diff --git a/docs/articles/expensify-classic/connections/accelo/Connect-To-Accelo.md b/docs/articles/expensify-classic/connections/accelo/Connect-To-Accelo.md new file mode 100644 index 000000000000..bdb21ceab9c0 --- /dev/null +++ b/docs/articles/expensify-classic/connections/accelo/Connect-To-Accelo.md @@ -0,0 +1,26 @@ +--- +title: Accelo +description: Help doc for Accelo integration +order: 1 +--- + + +# Overview +Accelo is a cloud-based business management platform tailored to professional service companies. It enables seamless integration with Expensify, allowing users to effortlessly import expense details from Expensify into Accelo, associating them with the corresponding project, ticket, or retainer within the system. + +**A couple of notes before connecting Accelo to Expensify:** +- You must have administrator access to Accelo +- You need to be a workspace admin in Expensify + +# Connect to Accelo +To connect Expensify to Accelo, follow these steps: +1. Open the [Expensify Integration Server](https://www.expensify.com/tools/integrations/) +2. Copy your **Partner User ID** and **Partner User Secret** + - If you haven’t previously set up the integration server, follow the prompt on the screen and select **click here**. + - If the integration server is already set up, select **Click here to regenerate your Partner User Secret to generate a new code**. +3. Copy the Partner User ID and Partner User Secret and store them in a secure location. +4. In Accelo, go to the Integrations page and select the **Expensify** tab. +5. Enter the Expensify Partner User ID and Partner User Secret. + - (Optional) If you currently use the Integration Server for another integration, open that platform and update the secret to the newly generated Partner User Secret +6. Click **Save**. +7. Connection Established: Expensify is now successfully connected to Accelo! diff --git a/docs/articles/expensify-classic/connections/certinia/Certinia-Troubleshooting.md b/docs/articles/expensify-classic/connections/certinia/Certinia-Troubleshooting.md new file mode 100644 index 000000000000..82a2762ee99a --- /dev/null +++ b/docs/articles/expensify-classic/connections/certinia/Certinia-Troubleshooting.md @@ -0,0 +1,6 @@ +--- +title: Certinia Troubleshooting +description: Certinia Troubleshooting +--- + +# Coming soon diff --git a/docs/articles/expensify-classic/connections/certinia/Configure-Certinia.md b/docs/articles/expensify-classic/connections/certinia/Configure-Certinia.md new file mode 100644 index 000000000000..3f9172baaf9e --- /dev/null +++ b/docs/articles/expensify-classic/connections/certinia/Configure-Certinia.md @@ -0,0 +1,91 @@ +--- +title: Configure Certinia +description: Configure Certinia's export, coding, and advanced settings. +order: 2 +--- +After Certinia and Expensify are connected, head to **Settings > Workspaces > [Workspace Name] > Connections > Certinia > Configure** to configure the export, coding, and advanced settings for the connection. + +# Step 1: Configure Export Settings +## Preferred Exporter +The preferred exporter is the user who will be the main exporter of reports. This person will receive the notifications for errors. + +## Payable Invoice Status and Date +Reports can be exported as Complete or In Progress, using the date of last expense, submitted date, or exported date. + +## Reimbursable and non-reimbursable exports +Both reimbursable and non-reimbursable reports are exported as payable invoices (FFA) or expense reports (PSA/SRP). If you have both Reimbursable and Non-Reimbursable expenses on a single report, Expensify will create a separate payable invoice/expense report for each type. + +## Default Vendor (FFA) +Choose from the full list of vendors from your Certinia FFA account. The amount will be applied to the non-reimbursable payable invoices. + +# Step 2: Configure Coding Settings +## Company +Select which FinancialForce company to import from/export to. + +## Chart of Accounts (FFA) +Prepaid Expense Type and Profit & Loss accounts are imported to be used as categories on each expense. + +## Expense Type GLA Mappings (PSA/SRP) +Your Expense Type GLA Mappings are enabled in Expensify to use as categories on each expense when using both PSA and SRP; however, PSA will not import or export categories, while SRP will. + +## Dimensions (FFA) +We import four dimension levels and each has three options to select from: + +* Do not map: FinancialForce defaults will apply to the payable invoice, without importing it into Expensify +* Tags: These are shown in the Tag section of your workspace, and employees can select them on each expense created +* Report fields: These will show in the Reports section of your workspace. Employees can select one to be applied at the header level, i.e., the entire report. + +## Projects, Assignments, or Projects & Assignments (PSA/SRP) +These can be imported as tags with **Milestones** being optional. When selecting to import only projects, we will derive the account from the project. If an assignment is selected, we will derive both the account and project from the assignment. + +**Note:** If you are using a project without an assignment, the box **Allow Expenses Without Assignment** must be checked on the project in FinancialForce. + +## Tax +Import tax rates from Certinia to apply to expenses. + +# Step 3: Configure Advanced Settings +## Auto Sync +Auto Sync in Certinia performs daily updates to your coding. Additionally, it automatically exports reports after they receive final approval. For Non-Reimbursable expenses, syncing happens immediately upon final approval of the report. In the case of Reimbursable expenses, syncing occurs as soon as the report is reimbursed or marked as reimbursed. + +## Export tax as non-billable +When exporting Billable expenses, this dictates whether you will also bill the tax component to your clients/customers. + +## Multi-Currency in Certinia PSA/SRP +When exporting to Certinia PSA/SRP, if employees are submitting expenses in more than one original currency, you may see up to three different currencies on the expense report in Certinia. +* Summary Total Reimbursement Amount: this currency is derived from the currency of the project selected on the expense. +* Amount field on the Expense line: this currency is derived from the Expensify workspace default report currency. +* Reimbursable Amount on the Expense line: this currency is derived from the currency of the resource with an email matching the report submitter. + +{% include faq-begin.md %} + +## What happens if the report can’t be exported to Certinia? + +The following happens if a report isn't exported: +- The preferred exporter will receive an email outlining the issue and any specific error messages +- Any error messages preventing the export from taking place will be recorded in the report’s history +- The report will be listed in the exporter’s Expensify Inbox as awaiting export. + +## If I enable Auto Sync, what happens to existing approved and reimbursed reports? + +Enabling Auto-Sync afterward won't affect existing approved or reimbursed reports. For any approved reports that haven't been exported to Certinia, you'll need to either manually export them or mark them as manually entered. + +## How do I export tax? + +Tax rates are created in Expensify through the tax tracking feature under **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Tax**. We export the tax amount calculated on the expenses. + +## How do reports map to Payable Invoices in Certinia FFA? + +Reports map to FFA as follows: +- Account Name - Account associated with Expensify submitter’s email address +- Reference 1 - Report URL +- Invoice Description - Report title + +## How do reports map to Expense Reports in Certinia PSA/SRP? + +Reports map to PSA/SRP as follows: +- Expense report name - Report title +- Resource - User associated with Expensify submitter’s email address +- Description - Report URL +- Approver - Expensify report approver + +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connections/certinia/Connect-To-Certinia.md b/docs/articles/expensify-classic/connections/certinia/Connect-To-Certinia.md new file mode 100644 index 000000000000..3d9c13449934 --- /dev/null +++ b/docs/articles/expensify-classic/connections/certinia/Connect-To-Certinia.md @@ -0,0 +1,40 @@ +--- +title: Certinia +description: Connect Expensify to Certinia +order: 1 +--- +[Certinia](https://use.expensify.com/financialforce) (formerly known as FinancialForce) is a cloud-based software solution that provides a range of financial management and accounting applications built on the Salesforce platform. There are two versions: PSA/SRP and FFA -- Expensify supports connections to both. + +**Before connecting to Certinia:** +Install the Expensify bundle in Certinia using the relevant installer: +- [PSA/SRP](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2M000002J0BHD%252Fpackaging%252FinstallPackage.apexp%253Fp0%253D04t2M000002J0BH) +- [FFA](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t4p000001UQVj) +- **Check the contact details in Certinia:** + - Confirm there's a user and contact in Certinia that matches your main email in Expensify + - Then, create contacts for all employees who will be sending expense reports + - Be sure that each contact's email matches the email address associated with their Expensify account + +# Connect to Certinia +1. Go to **Settings > Workspaces > Groups > [Workspace Name] > Connections** in Expensify +2. Click **Create a New Certinia (FinancialForce) Connection** +3. Log into your Certinia account +4. Follow the prompts until the connection between Certinia and Expensify is established + +### If you use PSA/SRP +- Each report approver needs both a User and a Contact +- The user does not need a SalesForce license + +Then, run through the following steps before connecting to Expensify: +1. Set permission controls in Certinia for your user for each contact/resource + - Go to Permission Controls + - Create a new permission control + - Set yourself (exporter) as the user + - Select the resource (report submitter) + - Grant all available permissions +2. Set permissions on any project you are exporting to + - Go to **Projects** > _select a project_ > **Project Attributes** > **Allow Expenses Without Assignment** + - Select the project > **Edit** + - Under the Project Attributes section, check **Allow Expenses Without Assignment** +3. Set up Expense Types (categories in Expensify - _SRP only_) + - Go to **Main Menu** > _+ symbol_ > **Expense Type GLA Mappings** + - Click **New** to add new mappings diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/NetSuite.md b/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md similarity index 73% rename from docs/articles/expensify-classic/integrations/accounting-integrations/NetSuite.md rename to docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md index ee116f65a398..f926792ffd1f 100644 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/NetSuite.md +++ b/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md @@ -1,136 +1,13 @@ --- -title: NetSuite -description: Connect and configure NetSuite directly to Expensify. +title: Configure Netsuite +description: Configure NetSuite's export, coding, and advanced settings. --- -# Overview -Expensify's seamless integration with NetSuite enables you to streamline your expense reporting process. This integration allows you to automate the export of reports, tailor your coding preferences, and tap into NetSuite's array of advanced features. By correctly configuring your NetSuite settings in Expensify, you can leverage the connection's settings to automate most of the tasks, making your workflow more efficient. +By correctly configuring your NetSuite settings in Expensify, you can leverage the connection's settings to automate most of the tasks, making your workflow more efficient. -Before getting started with connecting NetSuite to Expensify, there are a few things to note: -- Token-based authentication works by ensuring that each request to NetSuite is accompanied by a signed token which is verified for authenticity -- You must be able to login to NetSuite as an administrator to initiate the connection -- You must have a Control Plan in Expensify to integrate with NetSuite -- Employees don’t need NetSuite access or a NetSuite license to submit expense reports since the connection is managed by the Workspace Admin -- Each NetSuite subsidiary will need its own Expensify Group Workspace -- Ensure that your workspace's report output currency setting matches the NetSuite Subsidiary default currency -- Make sure your page size is set to 1000 for importing your customers and vendors. Go to Setup > Integration > Web Services Preferences > 'Search Page Size' - -# How to Connect to NetSuite - -## Step 1: Install the Expensify Bundle in NetSuite - -1. While logged into NetSuite as an administrator, go to Customization > SuiteBundler > Search & Install Bundles, then search for "Expensify" -2. Click on the Expensify Connect bundle (Bundle ID 283395) -3. Click Install -4. If you already have the Expensify Connect bundle installed, head to _Customization > SuiteBundler > Search & Install Bundles > List_ and update it to the latest version -5. Select **Show on Existing Custom Forms** for all available fields - -## Step 2: Enable Token-Based Authentication - -1. Head to _Setup > Company > Enable Features > SuiteCloud > Manage Authentication_ -2. Make sure “Token Based Authentication” is enabled -3. Click **Save** - -## Step 3: Add Expensify Integration Role to a User - -The user you select must have access to at least the permissions included in the Expensify Integration Role, but they're not required to be an Admin. -1. In NetSuite, head to Lists > Employees, and find the user you want to add the Expensify Integration role to -2. Click _Edit > Access_, then find the Expensify Integration role in the dropdown and add it to the user -3. Click **Save** - -Remember that Tokens are linked to a User and a Role, not solely to a User. It's important to note that you cannot establish a connection with tokens using one role and then switch to another role afterward. Once you've initiated a connection with tokens, you must continue using the same token/user/role combination for all subsequent sync or export actions. - -## Step 4: Create Access Tokens - -1. Using Global Search in NetSuite, enter “page: tokens” -2. Click **New Access Token** -3. Select Expensify as the application (this must be the original Expensify integration from the bundle) -4. Select the role Expensify Integration -5. Press **Save** -6. Copy and Paste the token and token ID to a saved location on your computer (this is the only time you will see these details) - -## Step 5: Confirm Expense Reports are Enabled in NetSuite. - -Enabling Expense Reports is required as part of Expensify's integration with NetSuite: -1. Logged into NetSuite as an administrator, go to Setup > Company > Enable Features > Employees -2. Confirm the checkbox next to Expense Reports is checked -3. If not, click the checkbox and then Save to enable Expense Reports - -## Step 6: Confirm Expense Categories are set up in NetSuite. - -Once Expense Reports are enabled, Expense Categories can be set up in NetSuite. Expense Categories are an alias for General Ledger accounts for coding expenses. - -1. Logged into NetSuite as an administrator, go to Setup > Accounting > Expense Categories (a list of Expense Categories should show) -2. If no Expense Categories are visible, click **New** to create new ones - -## Step 7: Confirm Journal Entry Transaction Forms are Configured Properly - -1. Logged into NetSuite as an administrator, go to _Customization > Forms > Transaction Forms_ -2. Click **Customize** or **Edit** next to the Standard Journal Entry form -3. Then, click Screen Fields > Main. Please verify the "Created From" label has "Show" checked and the Display Type is set to Normal -4. Click the sub-header Lines and verify that the "Show" column for "Receipt URL" is checked -5. Go to _Customization > Forms > Transaction Forms_ and ensure all other transaction forms with the journal type have this same configuration - -## Step 8: Confirm Expense Report Transaction Forms are Configured Properly - -1. Logged into NetSuite as an administrator, go to _Customization > Forms > Transaction Forms_ -2. Click **Customize** or **Edit** next to the Standard Expense Report form, then click **Screen Fields > Main** -3. Verify the "Created From" label has "Show" checked and the Display Type is set to Normal -4. Click the second sub-header, Expenses, and verify that the 'Show' column for 'Receipt URL' is checked -5. Go to _Customization > Forms > Transaction Forms_ and ensure all other transaction forms with the expense report type have this same configuration - -## Step 9: Confirm Vendor Bill Transactions Forms are Configured Properly - -1. Logged into NetSuite as an administrator, go to _Customization > Forms > Transaction Forms_ -2. Click **Customize** or **Edit** next to your preferred Vendor Bill form -3. Then, click _Screen Fields > Main_ and verify that the "Created From" label has "Show" checked and that Departments, Classes, and Locations have the "Show" label unchecked -4. Under the Expenses sub-header (make sure to click the "Expenses" sub-header at the very bottom and not "Expenses & Items"), ensure "Show" is checked for Receipt URL, Department, Location, and Class -5. Go to _Customization > Forms > Transaction Forms_ and provide all other transaction forms with the vendor bill type have this same configuration - -## Step 10: Confirm Vendor Credit Transactions Forms are Configured Properly - -1. While logged in as an administrator, go to _Customization > Forms > Transaction Forms_ -2. Click **Customize** or **Edit** next to your preferred Vendor Credit form, then click _Screen Fields > Main_ and verify that the "Created From" label has "Show" checked and that Departments, Classes, and Locations have the "Show" label unchecked -3. Under the Expenses sub-header (make sure to click the "Expenses" sub-header at the very bottom and not "Expenses & Items"), ensure "Show" is checked for Receipt URL, Department, Location, and Class -4. Go to _Customization > Forms > Transaction Forms_ and ensure all other transaction forms with the vendor credit type have this same configuration - -## Step 11: Set up Tax Groups (only applicable if tracking taxes) - -Expensify imports NetSuite Tax Groups (not Tax Codes), which you can find in NetSuite under _Setup > Accounting > Tax Groups_. - -Tax Groups are an alias for Tax Codes in NetSuite and can contain one or more Tax Codes (Please note: for UK and Ireland subsidiaries, please ensure your Tax Groups do not have more than one Tax Code). We recommend naming Tax Groups so your employees can easily understand them, as the name and rate will be displayed in Expensify. - -Before importing NetSuite Tax Groups into Expensify: -1. Create your Tax Groups in NetSuite by going to _Setup > Accounting > Tax Groups_ -2. Click **New** -3. Select the country for your Tax Group -4. Enter the Tax Name (this is what employees will see in Expensify) -5. Select the subsidiary for this Tax Group -6. Select the Tax Code from the table you wish to include in this Tax Group -7. Click **Add** -8. Click **Save** -9. Create one NetSuite Tax Group for each tax rate you want to show in Expensify - -Ensure Tax Groups can be applied to expenses by going to _Setup > Accounting > Set Up Taxes_ and setting the Tax Code Lists Include preference to "Tax Groups And Tax Codes" or "Tax Groups Only." - -If this field does not display, it’s not needed for that specific country. - -## Step 12: Connect Expensify and NetSuite - -1. Log into Expensify as a Policy Admin and go to **Settings > Workspaces > _[Workspace Name]_ > Connections > NetSuite** -2. Click **Connect to NetSuite** -3. Enter your Account ID (Account ID can be found in NetSuite by going to _Setup > Integration > Web Services Preferences_) -4. Then, enter the token and token secret -5. Click **Connect to NetSuite** - -From there, the NetSuite connection will sync, and the configuration dialogue box will appear. - -Please note that you must create the connection using a NetSuite account with the Expensify Integration role - -Once connected, all reports exported from Expensify will be generated in NetSuite using SOAP Web Services (the term NetSuite employs when records are created through the integration). - -# How to Configure Export Settings +# Step 1: Configure Export Settings There are numerous options for exporting Expensify reports to NetSuite. Let's explore how to configure these settings to align with your business needs. + To access these settings, head to **Settings > Workspace > Group > Connections** and select the **Configure** button. ## Export Options @@ -206,9 +83,9 @@ Select the Accounts Receivable account you want your Invoice Reports to export. ### Default Vendor Bills -The list of vendors will be available in the dropdown when selecting the option to export non-reimbursable expenses as vendor bills. +When selecting the option to export non-reimbursable expenses as vendor bills, the list of vendors will be available in the dropdown menu. -# How to Configure Coding Settings +# Step 2: Configure Coding Settings The Coding tab is where NetSuite information is configured in Expensify, which allows employees to code expenses and reports accurately. There are several coding options in NetSuite. Let’s go over each of those below. @@ -313,7 +190,7 @@ If configuring Custom Segments as Report Fields, use the Field ID on the Transac If configuring Custom Segments as Tags, use the Field ID on the Transaction Columns tab (under _Custom Segments > Transaction Columns_). Lastly, head over to Expensify and do the following: -1. Navigate to **Settings > Workspace > Group > [Workspace Name]_ > Connections > Configure > Coding tab** +1. Navigate to **Settings > Workspace > Group > [Workspace Name] > Connections > Configure > Coding tab** 2. Choose how to import Custom Records (Report Fields or Tags) 3. Fill out the three fields (the name or label of the record, Internal ID, Transaction Column ID) 4. Click **Submit** @@ -328,7 +205,7 @@ To add Custom Lists to your workspace, you’ll need to locate two fields in Net **To find the record:** 1. Log into Expensify -2. Head to **Settings > Workspace > Group > _[Workspace Name]_ > Connections > Configure > Coding tab** +2. Head to **Settings > Workspace > Group > [Workspace Name] > Connections > Configure > Coding tab** 3. The name of the record will be populated in a dropdown list The name of the record will populate in a dropdown list. If you don't see the one you are looking for, click **Refresh Custom List Options**. @@ -339,14 +216,14 @@ The name of the record will populate in a dropdown list. If you don't see the on 3. Open the option that is holding the record to get the ID Lastly, head over to Expensify, and do the following: -1. Navigate to **Settings > Workspaces > Group > _[Workspace Name]_ > Connections > Configure > Coding tab** +1. Navigate to **Settings > Workspaces > Group > [Workspace Name] > Connections > Configure > Coding tab** 2. Choose how to import Custom Lists (Report Fields or Tags) 3. Enter the ID in Expensify in the configuration screen 4. Click **Submit** From there, you should see the values for the Custom Lists under the Tag or Report Field settings in Expensify. -# How to Configure Advanced Settings +# Step 3: Configure Advanced Settings The NetSuite integration’s advanced configuration settings are accessed under **Settings > Workspaces > Group > _[Workspace Name]_ > Connections > NetSuite > Configure > Advanced tab**. @@ -384,13 +261,13 @@ Besides inviting employees, you can also establish an approval process in NetSui By doing this, the Approval Workflow in Expensify will automatically follow the same rules as NetSuite, typically starting with Manager Approval. -- **Basic Approval:** A single level of approval, where all users submit directly to a Final Approver. The Final Approver defaults to the workspace owner but can be edited on the people page. +- **Basic Approval:** This is a single level of approval, where all users submit directly to a Final Approver. The Final Approver defaults to the workspace owner but can be edited on the people page. - **Manager Approval (default):** Two levels of approval route reports first to an employee's NetSuite expense approver or supervisor, and second to a workspace-wide Final Approver. By NetSuite convention, Expensify will map to the supervisor if no expense approver exists. The Final Approver defaults to the workspace owner but can be edited on the people page. - **Configure Manually:** Employees will be imported, but all levels of approval must be manually configured on the workspace's People settings page. If you enable this setting, it’s recommended you review the newly imported employees and managers on the **Settings > Workspaces > Group > _[Workspace Name]_ > People page**. You can set a user role for each new employee and enforce an approval workflow. ## Automatically Create Employees/Vendors -With this feature enabled, Expensify will automatically create a new employee or vendor (if one doesn’t already exist) from the email of the report submitter in NetSuite. +With this feature enabled, Expensify will automatically create a new employee or vendor (if one doesn’t already exist) from the report submitter's email in NetSuite. ## Export Foreign Currency Amount @@ -430,13 +307,12 @@ If you do not wish to use Approval Routing in NetSuite, go to _Setup > Accountin When exporting invoices, once marked as Paid, the payment is marked against the account selected after enabling the Collection Account setting. -# Deep Dive - +# Additional Settings ## Categories You can use the Auto-Categorization feature so that expenses are automatically categorized. -To set Category Rules (e.g., receipt requirements or comments), go to the categories page in the workspace under **Settings > Workspaces > _[Workspace Name]_ > Categories**. +To set Category Rules (e.g., receipt requirements or comments), go to the categories page in the workspace under **Settings > Workspaces > [Workspace Name] > Categories**. With this setting enabled, when an Expense Category updates in NetSuite, it will update in Expensify automatically. @@ -448,7 +324,7 @@ NetSuite's company card feature simplifies exporting reimbursable and non-reimbu 2. **Default Accounts Payable (A/P) Account:** Expense reports enable you to set a default A/P account for export on your subsidiary record. Unlike vendor bills, where the A/P account defaults to the last selected account, the expense report export option allows you to establish a default A/P account. 3. **Mix Reimbursable and Non-Reimbursable Expenses:** You can freely mix reimbursable and non-reimbursable expenses without categorizing them in NetSuite after export. NetSuite's corporate card feature automatically categorizes expenses into the correct GL accounts, ensuring a neat and organized GL impact. -#### Let’s go over an example! +**Let’s go over an example!** Consider an expense report with one reimbursable and one non-reimbursable expense. Each needs to be exported to different accounts and expense categories. @@ -456,7 +332,7 @@ In NetSuite, you can quickly identify the non-reimbursable expense marked as a c Furthermore, each expense is categorized according to your selected expense category. -You'll need to set up default corporate cards in NetSuite to use the expense report option for your corporate card expenses. +To use the expense report option for your corporate card expenses, you'll need to set up default corporate cards in NetSuite. For non-reimbursable expenses, choose the appropriate card on the subsidiary record. You can find the default in your accounting preferences if you're not using a OneWorld account. @@ -545,7 +421,7 @@ When exporting to NetSuite, we match the recipient's email address on the invoic Once exported, the invoice will appear in the Accounts Receivable account you selected during your NetSuite Export configuration. -### Updating an Invoice to paid +### Updating the status of an invoice to "paid" When you mark an invoice as "Paid" in Expensify, this status will automatically update in NetSuite. Similarly, if the invoice is marked as "Paid" in NetSuite, it will sync with Expensify. The payment will be reflected in the Collection account specified in your Advanced Settings Configuration. @@ -562,10 +438,6 @@ Send these two files to your Account Manager or Concierge so we can continue tro {% include faq-begin.md %} -## What type of Expensify plan is required for connecting to NetSuite? - -You need a group workspace on a Control Plan to integrate with NetSuite. - ## How does Auto Sync work with reimbursed reports? If a report is reimbursed via ACH or marked as reimbursed in Expensify and then exported to NetSuite, the report is automatically marked as paid in NetSuite during the next sync. @@ -574,6 +446,24 @@ If a report is exported to NetSuite and then marked as paid in NetSuite, the rep ## If I enable Auto Sync, what happens to existing approved and reimbursed reports? -If you previously had Auto Sync disabled but want to allow that feature to be used going forward, you can safely turn on Auto Sync without affecting existing reports. Auto Sync will only take effect for reports created after enabling that feature. +If you previously had Auto Sync disabled but want to allow that feature to be used going forward, you can safely turn it on without affecting existing reports. Auto Sync will only take effect for reports created after enabling that feature. + +## Why are some of my customers not importing from NetSuite? + +If only part of your customer list is importing from NetSuite to Expensify, ensure your page size is set to 1000 for importing customers and vendors: +1. Navigate to **Setup > Integration > Web Services Preferences > Search Page Size** +2. Adjust this setting to 1000 +3. Sync your connection again under **Settings > Workspaces > Group > Workspace Name > Connections** + +Additionally, ensure the "Company Name" field is completed for each customer profile; otherwise, they won't import into the Group Workspace. + +## Why aren't all my Categories pulling into Expensify from NetSuite? + +If you're having trouble importing your Categories, you'll want to start by checking that they are set up in NetSuite as actual Expense Categories, not General Ledger accounts: +- Log into NetSuite as an administrator and go to **Setup > Accounting > Expense Categories** +- A list of Expense Categories should be available +- If no Expense Categories are visible click on "New" to create new Expense Categories + +If you have confirmed that your categories are set as Expense Categories in NetSuite and they still aren't importing to Expensify, make sure that the subsidiary of the Expense Category matches the subsidiary selected in your connection settings. {% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connections/netsuite/Connect-To-NetSuite.md b/docs/articles/expensify-classic/connections/netsuite/Connect-To-NetSuite.md new file mode 100644 index 000000000000..1f96d9b8a633 --- /dev/null +++ b/docs/articles/expensify-classic/connections/netsuite/Connect-To-NetSuite.md @@ -0,0 +1,164 @@ +--- +title: NetSuite +description: Set up the direct connection from Expensify to NetSuite. +order: 1 +--- +# Overview +Expensify's integration with NetSuite allows you to automate report exports, tailor your coding preferences, and tap into NetSuite's array of advanced features. By correctly configuring your NetSuite settings in Expensify, you can leverage the connection's settings to automate most of the tasks, making your workflow more efficient. + +**Before connecting NetSuite to Expensify, a few things to note:** +- Token-based authentication works by ensuring that each request to NetSuite is accompanied by a signed token which is verified for authenticity +- You must be able to login to NetSuite as an administrator to initiate the connection +- You must have a Control Plan in Expensify to integrate with NetSuite +- Employees don’t need NetSuite access or a NetSuite license to submit expense reports since the connection is managed by the Workspace Admin +- Each NetSuite subsidiary will need its own Expensify Group Workspace +- Ensure that your workspace's report output currency setting matches the NetSuite Subsidiary default currency +- Make sure your page size is set to 1000 for importing your customers and vendors. You can check this in NetSuite under **Setup > Integration > Web Services Preferences > 'Search Page Size'** + +# Connect to NetSuite + +## Step 1: Install the Expensify Bundle in NetSuite + +1. While logged into NetSuite as an administrator, go to Customization > SuiteBundler > Search & Install Bundles, then search for "Expensify" +2. Click on the Expensify Connect bundle (Bundle ID 283395) +3. Click Install +4. If you already have the Expensify Connect bundle installed, head to _Customization > SuiteBundler > Search & Install Bundles > List_ and update it to the latest version +5. Select **Show on Existing Custom Forms** for all available fields + +## Step 2: Enable Token-Based Authentication + +1. Head to _Setup > Company > Enable Features > SuiteCloud > Manage Authentication_ +2. Make sure “Token Based Authentication” is enabled +3. Click **Save** + +## Step 3: Add Expensify Integration Role to a User + +The user you select must have access to at least the permissions included in the Expensify Integration Role, but they're not required to be an Admin. +1. In NetSuite, head to Lists > Employees, and find the user you want to add the Expensify Integration role to +2. Click _Edit > Access_, then find the Expensify Integration role in the dropdown and add it to the user +3. Click **Save** + +Remember that Tokens are linked to a User and a Role, not solely to a User. It's important to note that you cannot establish a connection with tokens using one role and then switch to another role afterward. Once you've initiated a connection with tokens, you must continue using the same token/user/role combination for all subsequent sync or export actions. + +## Step 4: Create Access Tokens + +1. Using Global Search in NetSuite, enter “page: tokens” +2. Click **New Access Token** +3. Select Expensify as the application (this must be the original Expensify integration from the bundle) +4. Select the role Expensify Integration +5. Press **Save** +6. Copy and Paste the token and token ID to a saved location on your computer (this is the only time you will see these details) + +## Step 5: Confirm Expense Reports are Enabled in NetSuite. + +Enabling Expense Reports is required as part of Expensify's integration with NetSuite: +1. Logged into NetSuite as an administrator, go to Setup > Company > Enable Features > Employees +2. Confirm the checkbox next to Expense Reports is checked +3. If not, click the checkbox and then Save to enable Expense Reports + +## Step 6: Confirm Expense Categories are set up in NetSuite. + +Once Expense Reports are enabled, Expense Categories can be set up in NetSuite. Expense Categories are an alias for General Ledger accounts used to code expenses. + +1. Logged into NetSuite as an administrator, go to Setup > Accounting > Expense Categories (a list of Expense Categories should show) +2. If no Expense Categories are visible, click **New** to create new ones + +## Step 7: Confirm Journal Entry Transaction Forms are Configured Properly + +1. Logged into NetSuite as an administrator, go to _Customization > Forms > Transaction Forms_ +2. Click **Customize** or **Edit** next to the Standard Journal Entry form +3. Then, click Screen Fields > Main. Please verify the "Created From" label has "Show" checked and the Display Type is set to Normal +4. Click the sub-header Lines and verify that the "Show" column for "Receipt URL" is checked +5. Go to _Customization > Forms > Transaction Forms_ and ensure all other transaction forms with the journal type have this same configuration + +## Step 8: Confirm Expense Report Transaction Forms are Configured Properly + +1. Logged into NetSuite as an administrator, go to _Customization > Forms > Transaction Forms_ +2. Click **Customize** or **Edit** next to the Standard Expense Report form, then click **Screen Fields > Main** +3. Verify the "Created From" label has "Show" checked and the Display Type is set to Normal +4. Click the second sub-header, Expenses, and verify that the 'Show' column for 'Receipt URL' is checked +5. Go to _Customization > Forms > Transaction Forms_ and ensure all other transaction forms with the expense report type have this same configuration + +## Step 9: Confirm Vendor Bill Transactions Forms are Configured Properly + +1. Logged into NetSuite as an administrator, go to _Customization > Forms > Transaction Forms_ +2. Click **Customize** or **Edit** next to your preferred Vendor Bill form +3. Then, click _Screen Fields > Main_ and verify that the "Created From" label has "Show" checked and that Departments, Classes, and Locations have the "Show" label unchecked +4. Under the Expenses sub-header (make sure to click the "Expenses" sub-header at the very bottom and not "Expenses & Items"), ensure "Show" is checked for Receipt URL, Department, Location, and Class +5. Go to _Customization > Forms > Transaction Forms_ and provide all other transaction forms with the vendor bill type have this same configuration + +## Step 10: Confirm Vendor Credit Transactions Forms are Configured Properly + +1. While logged in as an administrator, go to _Customization > Forms > Transaction Forms_ +2. Click **Customize** or **Edit** next to your preferred Vendor Credit form, then click _Screen Fields > Main_ and verify that the "Created From" label has "Show" checked and that Departments, Classes, and Locations have the "Show" label unchecked +3. Under the Expenses sub-header (make sure to click the "Expenses" sub-header at the very bottom and not "Expenses & Items"), ensure "Show" is checked for Receipt URL, Department, Location, and Class +4. Go to _Customization > Forms > Transaction Forms_ and ensure all other transaction forms with the vendor credit type have this same configuration + +## Step 11: Set up Tax Groups (only applicable if tracking taxes) + +Expensify imports NetSuite Tax Groups (not Tax Codes), which you can find in NetSuite under _Setup > Accounting > Tax Groups_. + +Tax Groups are an alias for Tax Codes in NetSuite and can contain one or more Tax Codes (Please note: for UK and Ireland subsidiaries, please ensure your Tax Groups do not have more than one Tax Code). We recommend naming Tax Groups so your employees can easily understand them, as the name and rate will be displayed in Expensify. + +Before importing NetSuite Tax Groups into Expensify: +1. Create your Tax Groups in NetSuite by going to _Setup > Accounting > Tax Groups_ +2. Click **New** +3. Select the country for your Tax Group +4. Enter the Tax Name (this is what employees will see in Expensify) +5. Select the subsidiary for this Tax Group +6. Select the Tax Code from the table you wish to include in this Tax Group +7. Click **Add** +8. Click **Save** +9. Create one NetSuite Tax Group for each tax rate you want to show in Expensify + +Ensure Tax Groups can be applied to expenses by going to _Setup > Accounting > Set Up Taxes_ and setting the Tax Code Lists Include preference to "Tax Groups And Tax Codes" or "Tax Groups Only." + +If this field does not display, it’s not needed for that specific country. + +## Step 12: Connect Expensify and NetSuite + +1. Log into Expensify as a Policy Admin and go to **Settings > Workspaces > _[Workspace Name]_ > Connections > NetSuite** +2. Click **Connect to NetSuite** +3. Enter your Account ID (Account ID can be found in NetSuite by going to _Setup > Integration > Web Services Preferences_) +4. Then, enter the token and token secret +5. Click **Connect to NetSuite** + +From there, the NetSuite connection will sync, and the configuration dialogue box will appear. + +Please note that you must create the connection using a NetSuite account with the Expensify Integration role + +Once connected, all reports exported from Expensify will be generated in NetSuite using SOAP Web Services (the term NetSuite employs when records are created through the integration). + +{% include faq-begin.md %} + +## Can negative expenses be exported to NetSuite? +You can export reports with a negative total to NetSuite by selecting “Vendor Bill” as your export option. When a report total is negative, we’ll create a Vendor Credit in NetSuite instead of a bill. + +**Important**: Only enable this if you pay your employees/vendors outside of Expensify. A Vendor Credit reduces the total amount payable in NetSuite, but not in Expensify. + +To use this feature, make sure you have configured your Vendor Credit transaction form in NetSuite and are using the latest version of the Expensify bundle (version 1.4). If you need to update, go to **Customization > SuiteBundler > Search & Install Bundles > List** and click **Update** next to **Expensify Connect**. + +## How do you switch the owner of the connection between NetSuite and Expensify? + +Follow the steps below to transfer ownership of the NetSuite connection to someone else: +1. Head to **Settings > Workspaces > Workspace Name > Connections > NetSuite** +2. Click **Configure** to review and save the settings for future reference +3. Select **Do not connect to NetSuite** +4. Select **Connect to NetSuite** +5. Enter the email address of the new admin who will take over as the NetSuite User ID +6. Enter the NetSuite Account ID (found in NetSuite under **Setup > Integration > Web Services Preferences**) +7. Click **Create a new NetSuite Connection** +8. Confirm completion of prerequisites and proceed by clicking Continue +9. You will be redirected to the NetSuite SSO page, where you will enter the email address of the new connection owner and the NetSuite password for that account +10. Once redirected to the NetSuite page, click **View all roles** and ensure you are logged in under the Administrator role +11. After confirmation, sign out +12. Return to Expensify to reconfigure the sync and export settings on the updated connection +13. Click **Save** + +**If you run into any issues updating the connection, follow these additional troubleshooting steps:** +- In NetSuite, access the role of the current connection owner +- Click Edit > Access > Choose any role other than Administrator > Save +- Click Edit > Access > Select Administrator role > Save +- Repeat the steps outlined above + +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connections/netsuite/Netsuite-Troubleshooting.md b/docs/articles/expensify-classic/connections/netsuite/Netsuite-Troubleshooting.md new file mode 100644 index 000000000000..01aa21a28b80 --- /dev/null +++ b/docs/articles/expensify-classic/connections/netsuite/Netsuite-Troubleshooting.md @@ -0,0 +1,464 @@ +--- +title: Netsuite Troubleshooting +description: Troubleshoot common NetSuite sync and export errors. +--- + +# Overview of NetSuite Troubleshooting +Synchronizing and exporting data between Expensify and NetSuite can streamline your financial processes, but occasionally, users may encounter errors that prevent a smooth integration. These errors often arise from discrepancies in settings, missing data, or configuration issues within NetSuite or Expensify. + +This troubleshooting guide aims to help you identify and resolve common sync and export errors, ensuring a seamless connection between your financial management systems. By following the step-by-step solutions provided for each specific error, you can quickly address issues and maintain accurate and efficient expense reporting and data management. + +# ExpensiError NS0005: Please enter value(s) for Department, Location or Class + +This error occurs when the classification (like Location) is required at the header level of your transaction form in NetSuite. + +For expense reports and journal entries, NetSuite uses classifications from the employee record default. Expensify only exports this information at the line item level. + +For vendor bills, these classifications can't be mandatory because we use the vendor record instead of the employee record, and vendor records don’t have default classifications. + +**Vendor Bills:** +When exporting as a Vendor Bill, we pull from the vendor record, not the employee. Therefore, employee defaults don’t apply at the header ("main") level. This error appears if your NetSuite transaction form requires those fields. + +## How to Fix ExpensiError NS0005 +1. Go to **Customization > Forms > Transaction Forms**. +2. Click **"Edit"** on your preferred vendor bill form. +3. Go to **Screen Fields > Main**. +4. Uncheck both **"Show"** and **"Mandatory"** for the listed fields in your error message. +5. Sync the workspace connection in Expensify (**Settings > Workspaces > Workspace Name > Connections > Sync**). +6. Attempt the export again. + +**Journal Entries and Expense Reports:** +If you see this error when exporting a Journal Entry or Expense Report, it might be because the report submitter doesn’t have default settings for Departments, Classes, or Locations. + +**To fix this:** +1. Go to **Lists > Employees** in NetSuite. +2. Click **"Edit"** next to the employee's name who submitted the report. +3. Scroll down to the **Classification** section. +4. Select a default **Department**, **Class**, and **Location** for the employee. +5. Click **Save**. +6. Sync your NetSuite connection in Expensify. + + +# ExpensiError NS0012: Currency Does Not Exist In NetSuite + +**Scenario One:** When dealing with foreign transactions, Expensify sends the conversion rate and currency of the original expense to NetSuite. If the currency isn't listed in your NetSuite subsidiary, you'll see an error message saying the currency does not exist in NetSuite. + +**To fix this:** +1. Ensure the currency in Expensify matches what's in your NetSuite subsidiary. +2. If you see an error saying 'The currency X does not exist in NetSuite', re-sync your connection to NetSuite through the workspace admin section in Expensify. +3. Try exporting again. + +**Scenario Two:** This error can happen if you’re using a non-OneWorld NetSuite instance and exporting a currency other than EUR, GBP, USD, or CAD. + +**To fix this:** +1. Head to NetSuite. +2. Go to **Setup > Enable Features**. +3. Check the **Multiple Currencies** box. + +Once you've done this, you can add the offending currency by searching **New Currencies** in the NetSuite global search. + + +# ExpensiError NS0021: Invalid tax code reference key + +This error usually indicates an issue with the Tax Group settings in NetSuite, which can arise from several sources. + +#### Tax Group to Tax Code Mapping +If a Tax Code on Sales Transactions is mapped to a Tax Group, an error will occur. To fix this, the Tax Code must be mapped to a Tax Code on Purchase Transactions instead. + +To verify if a Tax Code is for Sales or Purchase transactions, view the relevant Tax Code(s). + +**For Australian Taxes:** +Ensure your Tax Groups are mapped correctly: +- **GST 10%** to **NCT-AU** (not the Sales Transaction Tax Code TS-AU) +- **No GST 0%** to **NCF-AU** (not the Sales Transaction Tax Code TFS-AU) + +#### Tax Group Type +Tax Groups can represent different types of taxes. For compatibility with Expensify, ensure the tax type is set to GST/VAT. + +#### Enable Tax Groups +Some subsidiaries require you to enable Tax Groups. Go to **Set Up Taxes** for the subsidiary's country and ensure the Tax Code lists include both Tax Codes and Tax Groups. + + +# ExpensiError NS0023: Employee Does Not Exist in NetSuite (Invalid Employee) + +This can happen if the employee’s subsidiary in NetSuite doesn’t match what’s listed in Expensify. + +## How to Fix ExpensiError NS0023 +1. **Check the Employee's Subsidiary** + - Go to the employee record in NetSuite. + - Confirm the employee's subsidiary matches what’s listed as the subsidiary at the workspace level. + - To find this in Expensify navigate to **Settings > Workspaces > Workspace Name > Connections > Configure**. + - If the subsidiaries don’t match, update the subsidiary in Expensify to match what’s listed in NetSuite. + - Sync the NetSuite connection in Expensify. +2. **Verify Access Restrictions:** + - Go to **Lists > Employees > Employees > [Select Employee] > Edit > Access**. + - Uncheck **Restrict Access to Expensify**. +3. **Additional Checks:** + - Ensure the email on the employee record in NetSuite matches the email address of the report submitter in Expensify. + - In NetSuite, make sure the employee's hire date is in the past and/or the termination date is in the future. +4. **Currency Match for Journal Entries:** + - If exporting as Journal Entries, ensure the currency for the NetSuite employee record, NetSuite subsidiary, and Expensify policy all match. + - In NetSuite, go to the **Human Resources** tab > **Expense Report Currencies**, and add the subsidiary/policy currency if necessary. + + +# ExpensiError NS0024: Invalid Customer or Project Tag + +Employees must be listed as a resource on the customer/project in NetSuite to be able to apply it to an expense. If that isn’t set up in NetSuite, you can run into this error. + +## How to Fix ExpensiError NS0024 + +1. **Ensure Employee Access:** + - In NetSuite, go to **Lists > Relationships > Customer/Projects**. + - Click **Edit** next to the desired Customer/Project. + - Click **Resources**, select the Employee from the drop-down menu, click **Add**, then **Save** your changes. +2. **Sync with Expensify:** + - In Expensify, go to **Settings > Workspaces > [Workspace Name] > NetSuite > Sync Now**. + - Attempt to export again. +3. **Remove Restriction for a Specific Customer/Project (Optional):** + - In NetSuite, edit the customer/project. + - Go to **Preferences**, then uncheck **Limit time and expenses to resources**. +4. **Remove Restrictions for All Projects:** + - In NetSuite, go to **Setup > Accounting > Accounting Preferences > Time & Expenses**. + - Uncheck **Show Projects Only For Time And Expense Entry**. +5. **Enable Cross-Subsidiary Customers/Projects in Expensify (Optional):** + - Go to **Settings > Workspaces > Group > [Workspace Name] > Connections > NetSuite > Configure > Advanced**. + - Enable **Cross-Subsidiary Customers/Projects** to remove the requirement for the employee's subsidiary and the customer's subsidiary to match. + + +# ExpensiError NS0034: This record already exists + +This error occurs when the report in question was already exported to NetSuite. + +## How to fix ExpensiError NS0034 +1. **Check for the existing Report in NetSuite:** + - If the report already exists in NetSuite but has been edited in Expensify, you need to delete the report in NetSuite. +2. **Find the Report ID in Expensify:** + - Locate the Report ID in Expensify under the Details section in the top right corner of the report. +3. **Search for the Report ID in NetSuite:** + - Use the Global Search Box in NetSuite to find the report using the Report ID. +4. **Delete the Report in NetSuite:** + - In NetSuite, click _**Edit > Actions > Delete**_ to remove the report. +5. **Re-export the Report from Expensify to NetSuite:** + - After deleting the report in NetSuite, re-export it from Expensify to NetSuite. + + +# ExpensiError NS0046: Billable Expenses Not Coded with a NetSuite Customer or Billable Project + +NetSuite requires billable expenses to be assigned to a Customer or a Project that is configured as billable to a Customer. If this is not set up correctly in NetSuite, this error can occur. + +## How to Fix ExpensiError NS0046 +1. **Access the Report in Expensify:** + - Go to the Expensify website and open the report. +2. Click the **Details** icon in the upper-left corner of the report. +3. Select the following options (this allows you to easily see which expenses are flagged as billable but don't have a valid customer tagged): + - **View:** Details + - **Group by:** Tag + - **Split report by:** Billable +4. **Check Billable Expenses:** + - Click on each expense in the Billable section of the report. + - Ensure that the Customer or Project tag field is present. + - Verify that there are no violations and that a value has been applied to the field. +5. Make any necessary adjustments to the billable expenses and try the export again. + + +# ExpensiError NS0059: A credit card account has not been selected for corporate card expenses. + +**To resolve this error:** +1. Log into NetSuite as an admin. +2. Type "Page: Subsidiaries" in the global search box and select the subsidiary you will export to. +3. Under the Preferences tab of the subsidiary, locate the field: Default Account for Corporate Card Expenses. + +**If you want to assign different cards to different employees:** +1. Go to each employee record in NetSuite. +2. Under the Human Resources > Expense and Purchasing section, find the field: Default Account for Corporate Card Expenses. + +**For reports containing cash expenses that are not marked as Reimbursable:** +1. Have the approver reject the report. +2. Mark the expenses as Reimbursable. +3. Re-submit the report, approve it, and try to export again. + +For accounts without subsidiaries (non-OneWorld accounts), the default field is in your accounting preferences. + + +# ExpensiError NS0085: Expense Does Not Have Appropriate Permissions for Settings an Exchange Rate in NetSuite + +This error occurs when the exchange rate settings in NetSuite aren't updated correctly. + +## How to Fix ExpensiError NS0085 +1. In NetSuite, go to Customization > Forms > Transaction Forms. +2. Search for the form type that the report is being exported as (Expense Report, Journal Entry, or Vendor Bill) and click Edit next to the form that has the Preferred checkbox checked. + - **For Expense Reports:** + - Go to Screen Fields > Expenses (the Expenses tab farthest to the right). + - Ensure the Exchange Rate field under the Description column has the Show checkbox checked. + - **For Vendor Bills:** + - Go to Screen Fields > Main. + - Ensure the Exchange Rate field under the Description column has the Show checkbox checked. + - **For Journal Entries:** + - Go to Screen Fields > Lines. + - Ensure the Exchange Rate field under the Description column has the Show checkbox checked. + - Go to Screen Fields > Main and ensure the Show checkbox is checked in the Exchange Rate field under the Description column. +3. Go to **Settings > Workspaces > Group > [Workspace Name] > Connections**. +4. Click Sync Now to sync the NetSuite connection. +5. Export the report(s) again. + + +# ExpensiError NS0079: The Transaction Date is Not Within the Date Range of Your Accounting Period + +The transaction date you specified is not within the date range of your accounting period. When the posting period settings in NetSuite are not configured to allow a transaction date outside the posting period, you can't export a report to the next open period, which is why you’ll run into this error. + +## How to Fix ExpensiError NS0079 +1. In NetSuite, navigate to Setup > Accounting > Accounting Preferences. +2. Under the General Ledger section, ensure the field Allow Transaction Date Outside of the Posting Period is set to Warn. +3. Then, choose whether to export your reports to the First Open Period or the Current Period. + +Additionally, ensure the Export to Next Open Period feature is enabled within Expensify: +1. Navigate to **Settings > Workspaces > Group > [Workspace Name] > Connections > Configure**. +2. Open the **Advanced tab**. +3. Confirm that the setting for **Export to Next Open Period** is enabled. + +If any configuration settings are updated on the NetSuite connection, be sure to sync the connection before trying the export again. + + +# ExpensiError NS0055: The Vendor You are Trying to Export to Does Not Have Access to the Currency X + +This error occurs when a vendor tied to a report in Expensify does not have access to a currency on the report in NetSuite. The vendor used in NetSuite depends on the type of expenses on the report you're exporting. +- For **reimbursable** (out-of-pocket) expenses, this is the report's submitter (the employee who submitted the report). +- For **non-reimbursable** (e.g., company card) expenses, this is the default vendor set via Settings > Workspaces > Group > [Workspace Name] > Connections > NetSuite > Configure. + +## How to Fix ExpensiError NS0055 +To fix this, the vendor needs to be given access to the applicable currency: +1. In NetSuite, navigate to Lists > Relationships > Vendors to access the list of Vendors. +2. Click Edit next to the Vendor tied to the report: + - For reimbursable (out-of-pocket) expenses, this is the report's submitter. + - For non-reimbursable (e.g., company card) expenses, this is the default vendor set via **Settings > Workspaces > Group > [Workspace Name] > Connections > NetSuite > Configure**. +3. Navigate to the Financial tab. +4. Scroll down to the Currencies section and add all the currencies that are on the report you are trying to export. +5. Click Save. + + +# ExpensiError NS0068: You do not have permission to set a value for element - “Created From” + +**To resolve this error:** +1. In NetSuite, go to Customization > Forms > Transaction Forms. +2. Search for the form type that the report is being exported as in NetSuite (Expense Report, Journal Entry, Vendor Bill, or if the report total is negative, Vendor Credit). +3. Click Edit next to the form that has the Preferred checkbox checked. +4. Go to Screen Fields > Main and ensure the field Created From has the Show checkbox checked. +5. Sync the NetSuite connection under **Settings > Workspaces > Group > [Workspace Name] > Connections > Sync Now**. +6. Export the report(s) again. + +#### For reports with Expensify Card expenses +Expensify Card expenses export as Journal Entries. If you encounter this error when exporting a report with Expensify Card non-reimbursable expenses, ensure the field Created From has the Show checkbox checked for Journal Entries in NetSuite. + + +# ExpensiError NS0037: You do not have permission to set a value for element - “Receipt URL” + +**To resolve this error:** +1. In NetSuite, go to Customization > Forms > Transaction Forms. +2. Search for the form type that the report is being exported as in NetSuite (Expense Report, Journal Entry, or Vendor Bill). +3. Click Edit next to the form that has the Preferred checkbox checked. + - If the report is being exported as an Expense Report: + - Go to Screen Fields > Expenses (the Expenses tab farthest to the right). + - Ensure the field ReceiptURL has the Show checkbox checked. + - If the report is being exported as a Journal Entry: + - Go to Screen Fields > Lines. + - Ensure the field ReceiptURL has the Show checkbox checked. + - If the report is being exported as a Vendor Bill: + - Go to Screen Fields > Main. + - Ensure the field ReceiptURL has the Show checkbox checked. +4. Click Sync Now to sync the NetSuite connection at **Settings > Workspaces > Group > Workspace Name > Connections**. +5. Export the report(s) again. + + +# ExpensiError NS0042: Error creating vendor - this entity already exists + +This error occurs when a vendor record already exists in NetSuite, but Expensify is still attempting to create a new one. This typically means that Expensify cannot find the existing vendor during export. +- The vendor record already exists in NetSuite, but there may be discrepancies preventing Expensify from recognizing it. +- The email on the NetSuite vendor record does not match the email of the report submitter in Expensify. +- The vendor record might not be associated with the correct subsidiary in NetSuite. + +## How to Fix ExpensiError NS0042 + +Follow these steps to resolve the issue: +1. **Check Email Matching:** + - Ensure the email on the NetSuite vendor record matches the email of the report submitter in Expensify. +2. **Check Subsidiary Association:** + - Ensure the vendor record is associated with the same subsidiary selected in the connection configurations + - You can review this under **Settings > Workspaces > Group > Workspace Name > Connections > Configure**. +3. **Automatic Vendor Creation:** + - If you want Expensify to automatically create vendors, ensure the "Automatically Create Vendor" option is enabled under **Settings > Workspaces > Group > Workspace Name > Connections > Advanced**. + +**Options to Resolve the Error:** +- **Edit the Existing Vendor:** Update the existing vendor record in NetSuite to match the report submitter's email and name. +- **Delete the Existing Vendor:** If appropriate, delete the existing vendor record in NetSuite to allow Expensify to create a new one. +- **Add Email to Existing Vendor:** Add the email address of the report’s submitter to the existing vendor record in NetSuite. + +**Final Steps:** +1. After making the necessary changes, head to **Settings > Workspaces > Group > Workspace Name > Connections** in Expensify. +2. Sync the NetSuite workspace connection. +3. Retry exporting the report. + + +# ExpensiError NS0109: Failed to login to NetSuite, please verify your credentials + +This error indicates a problem with the tokens created for the connection between Expensify and NetSuite. The error message will say, "Login Error. Please check your credentials." + +## How to Fix ExpensiError NS0109 +1. Review the [Connect to NetSuite](https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/NetSuite) guide and follow steps 1 and 2 exactly as outlined. +2. If you're using an existing token and encounter a problem, you may need to create a new token. + + +# ExpensiError NS0123 Login Error: Please make sure that the Expensify integration is enabled + +This error indicates that the Expensify integration is not enabled in NetSuite. + +## How to Fix ExpensiError NS0123 +1. **Enable the Expensify Integration:** + - In NetSuite, navigate to Setup > Integrations > Manage Integrations. + - Ensure that the Expensify Integration is listed and that the State is Enabled. +2. **If you can't find the Expensify integration:** + - Click "Show Inactives" to see if Expensify is listed as inactive. + - If Expensify is listed, update its state to Enabled. + +Once the Expensify integration is enabled, try syncing the NetSuite connection again. + + +# ExpensiError NS0045: Expenses Not Categorized with a NetSuite Account + +**To resolve this error:** +1. Log into NetSuite +2. Do a global search for the missing record. + - Ensure the expense category is active and correctly named. + - Ensure the category is associated with the correct subsidiary that the Expensify workspace is linked to. +3. In Expensify, head to **Settings > Workspaces > Groups > Workspace Name > Connections** and click **Sync Now** on the NetSuite connection to resync the workspace. +4. Go back to the report, click on the offending expense(s), and re-apply the category in question. +5. Export the report again. + + +# ExpensiError NS0061: Please Enter Value(s) for: Tax Code + +This error typically occurs when attempting to export expense reports to a Canadian subsidiary in NetSuite for the first time and/or if your subsidiary in NetSuite has Tax enabled. + +## How to Fix ExpensiError NS0061 +To fix this, you need to enable Tax in the NetSuite configuration settings. + +1. Go to **Settings > Workspaces > Group > Workspace Name > Connections > NetSuite**. + - Be sure to select posting accounts for GST/HST and PST if you plan on exporting any expenses with taxes on them to Journal Entries. +2. Click **Save** +3. Click **Sync Now** to sync the connection + +**Note:** Expenses created before Tax was enabled might need to have the newly imported taxes applied to them retroactively to be exported. + + +# Error creating employee: Your current role does not have permission to access this record. + +This error indicates that the credentials or role used to connect NetSuite to Expensify do not have the necessary permissions within NetSuite. You can find setup instructions for configuring permissions in NetSuite [here](https://help.expensify.com/articles/expensify-classic/connections/netsuite/Connect-To-NetSuite#step-3-add-expensify-integration-role-to-a-user). + +**To resolve this error:** +1. If permissions are configured correctly, confirm the report submitter exists in the subsidiary set on the workspace and that their Expensify email address matches the email on the NetSuite Employee Record. +2. If the above is true, try toggling off "Automatically create vendors/employees" under the Advanced tab of the NetSuite configuration window. + - Head to **Settings > Workspaces > Group > Workspace Name > Connections > NetSuite > Configure** + - Click on the **Advanced** tab + - Disable **Automatically create vendors/employees** +3. Sync the NetSuite connection in Expensify +4. Export the report again. + +# Elimination Settings for X Do Not Match + +This error occurs when an Intercompany Payable account is set as the default in the Default Payable Account field in the NetSuite subsidiary preferences, and the Accounting Approval option is enabled for Expense Reports. + +**To resolve this error:** +Set the Default Payable Account for Expense Reports on each subsidiary in NetSuite to ensure the correct payable account is active. +1. Navigate to Subsidiaries: + - Go to Setup > Company > Subsidiaries. +2. Edit Subsidiary Preferences: + - Click Edit for the desired subsidiary. + - Go to the Preferences tab. +3. Set Default Payable Account: + - Choose the preferred account for Default Payable Account for Expense Reports. + +Repeat these steps for each subsidiary to ensure the settings are correct, and then sync Expensify to NetSuite to update the connection. + +# Why are reports exporting as `Accounting Approved` instead of `Paid in Full`? + +**This can occur for two reasons:** +- Missing Locations, Classes, or Departments in the Bill Payment Form +- Incorrect Settings in Expensify Workspace Configuration + +## Missing Locations, Classes, or Departments in Bill Payment Form + +If locations, classes, or departments are required in your accounting classifications but are not marked as 'Show' on the preferred bill payment form, this error can occur, and you will need to update the bill payment form in NetSuite: +1. Go to Customization > Forms > Transaction Forms. +2. Find your preferred (checkmarked) Bill Payment form. +3. Click Edit or Customize. +4. Under the Screen Fields > Main tab, check 'Show' near the department, class, and location options. + +## Incorrect Settings in Expensify Workspace Configuration + +To fix this, you'll want to confirm the NetSuite connection settings are set up correctly in Expensify: +1. Head to **Settings > Workspaces > Group > Workspace Name > Connections > NetSuite > Configure > Advanced** +2. **Ensure the following settings are correct:** + - Sync Reimbursed Reports: Enabled and payment account chosen. + - Journal Entry Approval Level: Approved for Posting. + - A/P Approval Account: This must match the current account being used for bill payment. +3. **Verify A/P Approval Account:** + - To ensure the A/P Approval Account matches the account in NetSuite: + - Go to your bill/expense report causing the error. + - Click Make Payment. + - This account needs to match the account selected in your Expensify configuration. +4. **Check Expense Report List:** + - Make sure this is also the account selected on the expense report by looking at the expense report list. + +Following these steps will help ensure that reports are exported as "Paid in Full" instead of "Accounting Approved." + + +# Why are reports exporting as `Pending Approval`? + +If reports are exporting as "Pending Approval" instead of "Approved," you'll need to adjust the approval preferences in NetSuite. + +**Exporting as Journal Entries/Vendor Bills:** +1. In NetSuite, go to Setup > Accounting > Accounting Preferences. +2. On the **General** tab, uncheck **Require Approvals on Journal Entries**. +3. On the **Approval Routing** tab, uncheck Journal Entries/Vendor Bills to remove the approval requirement for Journal Entries created in NetSuite. + +**Note:** This change affects all Journal Entries, not just those created by Expensify. + +**Exporting as Expense Reports:** +1. In NetSuite, navigate to Setup > Company > Enable Features. +2. On the "Employee" tab, uncheck "Approval Routing" to remove the approval requirement for Expense Reports created in NetSuite. Please note that this setting also applies to purchase orders. + + +# How do I Change the Default Payable Account for Reimbursable Expenses in NetSuite? + +NetSuite is set up with a default payable account that is credited each time reimbursable expenses are exported as Expense Reports to NetSuite (once approved by the supervisor and accounting). If you need to change this to credit a different account, follow the below steps: + +**For OneWorld Accounts:** +1. Navigate to Setup > Company > Subsidiaries in NetSuite. +2. Next to the subsidiary you want to update, click Edit. +3. Click the Preferences tab. +4. In the Default Payable Account for Expense Reports field, select the desired payable account. +5. Click Save. + +**For Non-OneWorld Accounts:** +1. Navigate to Setup > Accounting > Accounting Preferences in NetSuite. +2. Click the Time & Expenses tab. +3. Under the Expenses section, locate the Default Payable Account for Expense Reports field and choose the preferred account. +4. Click Save. + + +# Why are my Company Card Expenses Exporting to the Wrong Account in NetSuite? + +If your company card transactions are exporting to the wrong account in your accounting system, there are a couple of factors to check: +1. **Verify Card Mapping:** + - Ensure that the cards are mapped to the correct accounts at the domain level + - This can be viewed under **Settings > Domains > Domain Name > Company Cards**. +2. **Check Default Account Settings:** + - Review the account where the expenses were exported + - It should be the default account under **Settings > Workspaces > Group > Workspace Name > Connections**. + - Click **Configure** to check the default export settings for your non-reimbursable expenses. + +The most common reason expenses export to the default account is that they are not actually imported from the mapped company card. Only company card expenses (notated with the “Card+Lock” icon) can use the export mapping settings configured at the domain level. + +Even if an expense was paid with the company card, it is considered a 'cash' expense unless it merges with a card expense marked with the Card+Lock icon. + +Less commonly, the issue may occur if the company card has been added to the user's personal settings. Expenses imported from a card linked at the individual account level will have a plain card icon. + diff --git a/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md b/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md new file mode 100644 index 000000000000..eda92d41e820 --- /dev/null +++ b/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md @@ -0,0 +1,6 @@ +--- +title: Configure Quickbooks Desktop +description: Configure Quickbooks Desktop +--- + +# Coming soon diff --git a/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md b/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md new file mode 100644 index 000000000000..92e1e4dd841f --- /dev/null +++ b/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md @@ -0,0 +1,62 @@ +--- +title: QuickBooks Desktop +description: Connect Expensify to QuickBooks Desktop +order: 1 +--- +# Overview +To connect Expensify to QuickBooks Desktop, use Right Networks as the hosting platform if possible. Right Networks is a cloud-based service that was built specifically for this integration. If you need a Right Networks account, complete [this form](https://info.rightnetworks.com/partner-expensify) to start the process. + +**A couple of notes before connecting QuickBooks Desktop to Expensify:** +- Make sure you're logged into QuickBooks Desktop as an admin +- Check that the company file you want to connect Expensify to is the only one open + + +# Connect to QuickBooks Desktop + +## Step 1: Set up submitters in QuickBooks Desktop +- Make sure all report submitters are set up as Vendors in QuickBooks Desktop and their Expensify email is in the "Main Email" field of their Vendor record. You can do this in the vendor section of QuickBooks. +- If you want to export reports to your users' employee records instead of vendor records, select Check or Journal Entry as your reimbursable export option. +- To set up Expensify users as employees, activate QuickBooks Desktop Payroll. This module is necessary to access the Employee Profile tab, where you can enter the submitter's email addresses. + +## Step 2: Enable/install the Expensify Sync Manager +1. Navigate to **Settings > Workspaces > Group > [Workspace Name] > Connections** +2. Click **Connect to QuickBooks Desktop** to initiate the connection + +**Option 1: Enable the Expensify Sync Manager in Right Networks (recommended)** +- For this option, **single-user mode** in QuickBooks Desktop is required. +- If you don't have an account with Right Networks, you can contact Right Networks [here](https://info.rightnetworks.com/partner-expensify) +- Once set up, you can enable the Expensify Sync Manager from the **My Account** section in Right Networks' portal + +**Option 2: Install the Expensify Sync Manager on Your Third-Party Remote Desktop.** +To download the Sync Manager to your desktop, you must contact your third-party remote desktop provider and request permission. They might have security restrictions, so it's best to communicate with them directly to avoid potential problems with the Sync Manager. Remember that the Sync Manager program file should be stored in the same location (i.e., the same drive) as your QuickBooks Desktop program. + +## Step 3: Complete the connection +1. Open QuickBooks and access the desired Company File using the QuickBooks Admin credentials (admin credentials are necessary for creating the connection) +2. Navigate to **Settings > Workspaces > Group > [Workspace Name] > Connections** +3. Copy the Token by selecting the copy icon +4. While QuickBooks is still running, launch the Expensify Sync Manager by pasting the Token into the Sync Manager +5. Click **Save** +6. Once the Sync Manager status displays **Connected**, return to Expensify and click **Continue** + +## Step 4: Allow access +1. Return to QuickBooks where you'll see an **Application Certificate** screen + - On the first page of the Certificate screen, click **Yes, always; allow access even if QuickBooks is not running** +3. Click **Continue** +4. On the second page of the Certificate screen, choose the Admin user from the dropdown menu +5. Click **Done** +7. Return to Expensify and wait for the sync to complete + +{% include faq-begin.md %} + +## After connecting, how do I sync QuickBooks and Expensify? +1. Confirm that both the Expensify Sync Manager and QuickBooks Desktop are running +2. On the Expensify website, navigate to **Settings > Workspaces > Group > [Workspace Name] > Connections**, and click **Sync now** +3. Wait for the sync to complete + +Typically, this takes about 2-5 minutes, but it might take longer, depending on when you last synced and the size of your QuickBooks company file. The page will refresh automatically once syncing is complete. + +We recommend syncing at least once a week or whenever you make changes in QuickBooks Desktop that could impact how your reports export from Expensify. Changes could include adjustments to your Chart of Accounts, Vendors, Employees, Customers/Jobs, or Items. + +Remember, both the Sync Manager and QuickBooks Desktop need to be running for syncing or exporting to work. + +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md b/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md new file mode 100644 index 000000000000..061b01b7a924 --- /dev/null +++ b/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md @@ -0,0 +1,45 @@ +--- +title: Quickbooks Desktop Troubleshooting +description: Quickbooks Desktop Troubleshooting +--- + +# Sync and export errors +## Error: No Vendor Found For Email in QuickBooks +To address this issue, ensure that each submitter's email is saved as the **Main Email** in their Vendor record within QuickBooks Desktop. Here's how to resolve it: +1. Go to your Vendor section in QuickBooks. +2. Verify that the email mentioned in the error matches the **Main Email** field in the respective vendor's record. It's important to note that this comparison is case-sensitive, so ensure that capitalization matches as well. +3. If you prefer to export reports to your users' employee records instead of their vendor records, select either **Check** or **Journal Entry** as your reimbursable export option. If you are setting up Expensify users as employees, activate QuickBooks Desktop Payroll to access the Employee Profile tab where submitter email addresses need to be entered. +4. Once you've added the correct email to the vendor record, save this change, and then sync your policy before attempting to export the report again. + +## Error: Do Not Have Permission to Access Company Data File +To resolve this error, follow these steps: +1. Log into QuickBooks Desktop as an Admin in single-user mode. +2. Go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences**. +3. Select the Expensify Sync Manager and click on **Properties**. +4. Ensure that **Allow this application to login automatically** is checked, and then click **OK**. Close all windows within QuickBooks. +5. If you still encounter the error after following the above steps, go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences**, and remove the Expensify Sync Manager from the list. +6. Next, attempt to sync your policy again in Expensify. You'll be prompted to re-authorize the connection in QuickBooks. +7. Click **Yes, always; allow access even if QuickBooks is not running.** +8. From the dropdown, select the Admin user, and then click **Continue**. Note that selecting **Admin** here doesn't mean you always have to be logged in as an admin to use the connection; it's just required for setting up the connection. +9. Click **Done** on the pop-up window and return to Expensify, where your policy should complete the syncing process. + +## Error: The Wrong QuickBooks Company is Open. +This error suggests that the wrong company file is open in QuickBooks Desktop. To resolve this issue, follow these steps: +1. First, go through the general troubleshooting steps as outlined. +2. If you can confirm that the incorrect company file is open in QuickBooks, go to QuickBooks and select **File** > **Open or Restore Company** > _[Company Name]_ to open the correct company file. After doing this, try syncing your policy again. +3. If the correct company file is open, but you're still encountering the error, completely close QuickBooks Desktop, reopen the desired company file and then attempt to sync again. +4. If the error persists, log into QuickBooks as an admin in single-user mode. Then, go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences** and remove the Expensify Sync Manager from the list. +5. Next, try syncing your policy again in Expensify. You'll be prompted to re-authorize the connection in QuickBooks, allowing you to sync successfully. +6. If the error continues even after trying the steps above, double-check that the token you see in the Sync Manager matches the token in your connection settings. + +## Error: The Expensify Sync Manager Could Not Be Reached. +To resolve this error, follow these steps: +*Note: You must be in single-user mode to sync.* + +1. Ensure that both the Sync Manager and QuickBooks Desktop are running. +2. Confirm that the Sync Manager is installed in the correct location. It should be in the same location as your QuickBooks application. If QuickBooks is on your local desktop, the Sync Manager should be there, too. If QuickBooks is on a remote server, install the Sync Manager there. +Verify that the Sync Manager's status is **Connected**. +3. If the Sync Manager status is already **Connected**, click **Edit** and then *Save* to refresh the connection. Afterwards, try syncing your policy again. +4. If the error persists, double-check that the token you see in the Sync Manager matches the token in your connection settings. + +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connections/quickbooks-online/Configure-Quickbooks-Online.md b/docs/articles/expensify-classic/connections/quickbooks-online/Configure-Quickbooks-Online.md new file mode 100644 index 000000000000..3fd1df0c0a1c --- /dev/null +++ b/docs/articles/expensify-classic/connections/quickbooks-online/Configure-Quickbooks-Online.md @@ -0,0 +1,140 @@ +--- +title: Configure Quickbooks Online +description: Configure Quickbooks Online +--- + +# Best Practices Using QuickBooks Online + +A connection to QuickBooks Online lets you combine the power of Expensify's expense management features with QuickBooks’s accounting capabilities. By following the recommended best practices below, your finances will be automatically categorized correctly and accounted for in the right place. + +- Configure your setup immediately after making the connection, and review each settings tab thoroughly. +- Keep Auto Sync enabled. + - The daily sync will update Expensify with any changes to your chart of accounts, customers/projects, or bank accounts in QuickBooks Online. + - Finalized reports will be exported to QuickBooks Online automatically, saving your admin team time with every report. +- Set your preferred exporter to a user who is both a workspace and domain admin. +- Configure your coding settings and enforce them by requiring categories and tags on expenses. + +# Accessing the QuickBooks Configuration Settings + +QuickBooks Online is connected at the workspace level, and each workspace can have a unique configuration that dictates how the connection functions. To access the configuration: + +1. Click **Settings** near the bottom of the left-hand menu. +2. Navigate to Workspaces > Groups > [workspace Name] > Connections. +3. Scroll down to the QuickBooks Online connection and click the **Configure** button to open the settings menu. + +# Step 1: Configure Export Settings + +The following steps help you determine how data will be exported from Expensify to QuickBooks Online. + +1. Click the **Configure** button under the QuickBooks Online connection to open the settings menu. +2. Under the Export tab, review each of the following export settings: + - _Preferred Exporter_: Choose a Workspace Admin to set as the Preferred Exporter. + - Concierge exports reports automatically on behalf of the preferred exporter. + - Other Workspace Admins will still be able to export to QuickBooks Online manually. + - If you set different export bank accounts for individual company cards under Settings > Domain > Company Cards, your Preferred Exporter must be a Domain Admin in addition to a Workspace Admin. + - _Date_: When exporting reports to QuickBooks Online, you can choose the report’s submitted date, the report’s exported date, or the date of the last expense on the report. + - If you choose a Credit Card or Debit Card for non-reimbursable expenses, we’ll use the transaction date on each expense during export. + - _Reimbursable expenses_: Reimbursable expenses export to QuickBooks Online as: + - Vendor Bills (recommended): This is a single itemized vendor bill for each Expensify report. + - Checks - This is a single itemized check for each Expensify report. You can mark a check to be printed later in QuickBooks Online. + - Journal Entries - This is a single itemized journal entry for each Expensify report. + - _Non-reimbursable expenses_: Non-reimbursable expenses export to QuickBooks Online as: + - Credit Card expenses - Each expense will be exported as a bank transaction with its transaction date. + - Debit Card Expenses - Each expense will be exported as a bank transaction with its transaction date. + - Vendor Bills - A single detailed vendor bill is generated for each Expensify report. + - If the accounting period is closed, the vendor bill will be posted on the first day of the next open period. If you choose to export non-reimbursable expenses as Vendor Bills, you can assign a default vendor to the bill. + - The export will use your default vendor if you have Default Vendor enabled. If the Default Vendor is disabled, the report’s submitter will be set as the Vendor in QuickBooks. + - _Billable Expenses_: In Expensify, you can designate expenses as billable. These will be exported to QuickBooks Online with the billable flag. + - This feature applies only to expenses exported as Vendor Bills or Checks. To maximize this functionality, ensure that any billable expense is associated with a Customer/Job. + - _Export Invoices_: If you are creating Invoices in Expensify and exporting these to QuickBooks Online, this is the account the invoice will appear against. + +## Step 1B: Optional Configuration When Company Cards Are Connected +1. Click **Settings** near the bottom of the left-hand menu. +2. Navigate to Domains > [domain name] > Company Cards. +3. If you have more than one company card connection, select the connection first. +4. Locate the cardholder you want to configure in the list, +5. Click the **Edit Exports** button and assign the account the card expenses should export to in QuickBooks Online. + +# Step 2: Configure Coding Settings + +The following steps help you determine how data will be imported from QuickBooks Online to Expensify. + +1. Click the **Configure** button under the QuickBooks Online connection to open the settings menu. +2. Under the Coding tab, review each of the following settings and configure the options to determine what information will be imported: + - _Categories_: QuickBooks Online Chart of Accounts are imported into Expensify as categories. This is enabled by default and cannot be disabled. + - Equity-type accounts will also be imported as categories. + - Other Current Liabilities can only be exported as Journal Entries if the submitter is set up as an Employee in QuickBooks. + - _Classes and Customers/Projects_: If you use Classes or Customers/Projects in QuickBooks Online, you can import those into Expensify as Tags or Report Fields: + - Tags let you apply a Class and/or Customer/Project to each expense. + - Report Fields enables you to apply a Class and/or Customer/Project to all expenses on a report. + - Note: Although Projects can be imported into Expensify and coded to expenses, due to the limitations of the QuickBooks API, expenses cannot be created within the Projects module in QuickBooks. + - _Locations_: When enabled will import into Expensify as a Report Field or, if you export reimbursable expenses as Journal Entries and non-reimbursable expenses as Credit/Debit Card, you can import Locations as Tags. + - _Items_: If you use Items in QuickBooks Online, you can import Items defined with Purchasing Information (with or without Sales Information) into Expensify as Categories. + - _Tax_: Once enabled, QuickBooks Online tax rates can be further configured on the Settings > Workspaces > Groups > [Workspace Name] > [Tax](https://expensify.com/policy?param=%7B%22policyID%22:%22B936DE4542E9E78B%22%7D#tax) page. + - Note: Tax cannot be exported to Journal Entries in QuickBooks Online. + +# Step 3: Configure Advanced Settings + +The following steps help you determine the advanced settings for your connection, like auto-sync. + +1. Click the **Configure** button under the QuickBooks Online connection to open the settings menu. +2. Under the Advanced tab, review each of the following settings and configure the options you wish to use: + - _Auto Sync_: When enabled, the connection will sync daily to ensure that the data shared between the two systems is up-to-date. + - New report approvals/reimbursements will be synced during the next auto-sync period. + - Reimbursable expenses will export after reimbursement occurs or the report is marked as reimbursed outside Expensify when using Direct or Indirect reimbursement. + - Non-reimbursable expenses will export automatically after the report is final approved. + - _Newly Imported Categories Should Be_: When a new account is created in the QuickBooks Online chart of accounts, this setting controls whether the new category in Expensify is enabled or disabled by default. Disabled categories are not visible to employees when coding expenses. + - _Invite Employees_: When enabled, Auto Sync imports QuickBooks Online employee records and invites them to the workspace. + - _Automatically Create Entities_: If you export reimbursable expenses as Vendor Bills or Journal Entries, Expensify will automatically create a vendor in QuickBooks (If one does not already exist). Expensify will also automatically create a customer when exporting Invoices. + - _Sync Reimbursed Reports_: Enabling will mark the Vendor Bill as paid in QuickBooks Online if you reimburse a report via ACH direct deposit in Expensify. If you reimburse outside of Expensify, then marking the Vendor Bill as paid in QuickBooks Online will automatically mark the report as reimbursed in Expensify. + - _QuickBooks Account_: Select the bank account your reimbursements are coming out of, and we'll create the payment in QuickBooks. + - _Collection Account_: When exporting invoices from Expensify to Quickbooks Online, the invoice will appear against the Collection Account once marked as Paid. + +{% include faq-begin.md %} + +## Why am I seeing duplicate credit card expenses in QuickBooks Online? + +When importing a banking feed directly into QuickBooks Online while also importing transactions from Expensify, it’s possible to encounter duplicate entries in QuickBooks. To prevent this, follow these steps: + +- Step 1: Complete the Approval Process in Expensify +Before exporting any expenses to QuickBooks Online, ensure they are added to a report and the report receives approval. Depending on your Workspace setup, reports may require approval from one or more individuals. The approval process concludes when the last user who views the report selects “Final Approve.” +- Step 2: Exporting Reports to QuickBooks Online +To ensure expenses exported from Expensify match seamlessly in the QuickBooks Banking platform, make sure these expenses are marked as non-reimbursable within Expensify and that “Credit Card” is selected as the non-reimbursable export option for your expenses. +- Step 3: Importing Your Credit Card Transactions into QuickBooks Online +After completing Steps 1 and 2, you can import your credit card transactions into QuickBooks Online. These imported banking transactions will align with the ones brought in from Expensify. QuickBooks Online will guide you through the process of matching these transactions, similar to the example below: + +## What happens if the report can’t be exported to QuickBooks Online automatically? + +If a report encounters an issue during automatic export to QuickBooks Online, you’ll receive an email with details about the problem, including any specific error messages. These messages will also be recorded in the report’s history section. + +The report will be placed in your Home for your attention. You can address the issues there. If you need further assistance, refer to our QuickBooks Online Export Errors page or export the report manually. + +## What happens to existing approved and reimbursed reports if I enable Auto Sync? + +- If Auto Sync was disabled when your Workspace was linked to QuickBooks Online, enabling it won’t impact existing reports that haven’t been exported. +- If a report has been exported and reimbursed via ACH, it will be automatically marked as paid in QuickBooks Online during the next sync. +- If a report has been exported and marked as paid in QuickBooks Online, it will be automatically marked as reimbursed in Expensify during the next sync. +- Reports that have yet to be exported to QuickBooks Online won’t be automatically exported. + +## Does splitting a non-reimbursable expense affect how it exports to QuickBooks Online? + +When exporting non-reimbursable expenses as Credit Card or Debit Card expenses, split expenses will be consolidated it into a single credit card transaction in QuickBooks with multiple line items posted to the corresponding General Ledger accounts. + +Pro-Tip: To ensure the payee field in QuickBooks Online reflects the merchant name for Credit Card expenses, ensure there’s a matching Vendor in QuickBooks Online. Expensify checks for an exact match during export. If none are found, the payee will be mapped to a vendor we create and labeled as Credit Card Misc. or Debit Card Misc. + +## I’m using multi-currency in QuickBooks Online, how do I control the currency conversion rate? + +When working with QuickBooks Online Multi-Currency, there are some things to remember when exporting Vendor Bills and Check! Make sure the vendor’s currency and the Accounts Payable (A/P) bank account match. + +In QuickBooks Online, the currency conversion rates are not applied when exporting. All transactions will be exported with a 1:1 conversion rate, so for example, if a vendor’s currency is CAD (Canadian Dollar) and the home currency is USD (US Dollar), the export will show these currencies without applying conversion rates. + +To correct this, you must manually update the conversion rate after the report has been exported to QuickBooks Online. + +**Specifically for Vendor Bills**: + +- If multi-currency is enabled and the Vendor’s currency is different from the Workspace currency, OR if QuickBooks Online home currency is foreign from the Workspace currency, then: +- We create the Vendor Bill in the Vendor’s currency (this is a QuickBooks Online requirement - we don’t have a choice) +- We set the exchange rate between the home currency and the Vendor’s currency +- We convert line item amounts to the vendor’s currency + +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connections/quickbooks-online/Connect-To-QuickBooks-Online.md b/docs/articles/expensify-classic/connections/quickbooks-online/Connect-To-QuickBooks-Online.md new file mode 100644 index 000000000000..970f17c59018 --- /dev/null +++ b/docs/articles/expensify-classic/connections/quickbooks-online/Connect-To-QuickBooks-Online.md @@ -0,0 +1,43 @@ +--- +title: Connect to QuickBooks Online +description: Everything you need to know about using Expensify's direct integration with QuickBooks Online. +order: 1 +--- + +The Expensify integration with QuickBooks Online brings in your expense accounts and other data and even exports reports directly to QuickBooks for easy reconciliation. Plus, with advanced features in QuickBooks Online, you can fine-tune coding settings in Expensify for automated data export to optimize your accounting workflow. + +## Before connecting + +It’s crucial to understand the requirements based on your specific QuickBooks subscription: +- While all the features are available in Expensify, their accessibility may vary depending on your QuickBooks Online subscription. +- An error will occur if you try to export to QuickBooks Online with a feature enabled that isn’t part of your subscription. +- Please be aware that Expensify does not support the Self-Employed subscription in QuickBooks Online. + +![QuickBooks Online - Subscription types]({{site.url}}/assets/images/QBO1.png){:width="100%"} + +# Step 1: Setup Employees in QuickBooks Online +Employees must be set up as either Vendors or Employees in QuickBooks Online. Make sure to include the submitter’s email in their record. + +If you use vendor records, you can export as Vendor Bills, Checks, or Journal Entries. If you use employee records, you can export as Checks or Journal Entries (if exporting against a liability account). + +# Step 2: Connect Expensify and QuickBooks Online +1. Click **Settings** near the bottom of the left-hand menu. +2. Navigate to Workspaces > Groups > [workspace Name] > Connections. +3. Click on **Connect to QuickBooks Online**. +4. Click the **Create a New QuickBooks Online Connection** button. +5. Enter your QuickBooks Online Administrator’s login information and choose the QuickBooks Online Company File you want to connect to Expensify (you can connect one Company File per Workspace). +6. Then click **Authorize**. +7. You will be redirected back to Expensify and the connection will import some initial settings from QuickBooks Online to Expensify. +8. Once the sync is complete, the configuration window for QuickBooks Online will open automatically so you can configure your export, import, and advanced settings. +9. Click the **Save** button when you’re done configuring to finalize the connection. + +## Step 2B: Exporting Historical Reports to QuickBooks Online + +After connecting QuickBooks Online to Expensify, you may receive a prompt to export all historical reports from Expensify. To export multiple reports at once, follow these steps: + +1. Open the Reports page in a web browser. +2. Reset the filters and then adjust the filters to display the reports in question. +3. Check the box to the left of the reports you want to export. +4. Click **Export To** and select **QuickBooks Online**. + - If you don’t want to export specific reports, select “Mark as manually entered” instead. + diff --git a/docs/articles/expensify-classic/connections/quickbooks-online/Quickbooks-Online-Troubleshooting.md b/docs/articles/expensify-classic/connections/quickbooks-online/Quickbooks-Online-Troubleshooting.md new file mode 100644 index 000000000000..158a55b93e0f --- /dev/null +++ b/docs/articles/expensify-classic/connections/quickbooks-online/Quickbooks-Online-Troubleshooting.md @@ -0,0 +1,189 @@ +--- +title: Quickbooks Online Troubleshooting +description: Quickbooks Online Troubleshooting +--- + +# ExpensiError QBO022: When exporting billable expenses, please make sure the account in QuickBooks Online has been marked as billable. + +**Why does this happen?** + +This error occurs when the account applied as a category to the expense in Expensify is not marked as as a billable type account. + +## How to fix it + +1. Log in to QuickBooks Online. +2. Click the Gear in the upper right-hand corner. +3. Under Company Settings click Expenses. +4. Enable the option “Make expenses and items billable” +5. Click on the pencil icon on the right to check if you have "In multiple accounts" selected: +6. If "In multiple accounts" is selected, go to Chart of Accounts and click Edit for the account in question. +7. Check the billable option and select an income account within your chart of accounts +8. Sync your QuickBooks Online connection in Settings > Workspaces > [click workspace] > Connections. +9. Open the report and click the Export to button and then the QuickBooks Online option. + +# ExpensiError QBO046: Feature Not Included in Subscription + +**Why does this happen?** + +This error occurs when your version of QuickBooks Online doesn’t support the feature you are using in Expensify. + +## How to fix it + +Though you will see all of these features available in Expensify, you will receive an error trying to export to QuickBooks if you have a feature enabled that isn't available with your QuickBooks Online subscription. + +**Here is a list of the features supported by each version:** +_Please note: Self Employed is not supported:_ + +![QuickBooks Online - Subscription types]({{site.url}}/assets/images/QBO1.png){:width="100%"} + +# ExpensiError QBO056: Expenses are not categorized with a QuickBooks Online account + +**Why does this happen?** + +QuickBooks Online requires all expenses exported from Expensify to use a category matching an account in your chart of accounts. If a category from another source is used, QuickBooks Online will reject the expense. This errors occurs when an expense on the report has a category applied that is not valid in QuickBooks Online. + +## How to fix it + +1. Sync your QuickBooks Online connection in Expensify from Settings > Workspaces > [click workspace] > Connections, and click the **Sync Now** button. +2. Review the expenses on the report. If any appear with a red _Category no longer valid_ violation, recategorize the expense until all expenses are violation-free. +3. Click the **Export t**o button and then the **QuickBooks Online** option. + - If you receive the same error, continue. +4. Note the categories used on the expenses and check the Settings > Workspaces > [workspace name] > Categories page to confirm the exact categories used on the report are enabled and connected to QuickBooks Online (you'll see a green QB icon next to all connected categories). +5. Confirm the categories used on the expenses in the report match exactly the accounts in your QuickBooks Online chart of accounts. +6. If you make any changes in QuickBooks Online or in Expensify, always sync the connection and then try to export again. + +# ExpensiError QBO088: Error Creating Vendor + +**Why does this happen?** + +This error occurs when you have an Employee Record set up with the employee's name, which prevents the Expensify integration from automatically creating the Vendor Record with the same name, since QuickBooks Online won't allow you to have an employee and vendor with the same name. + +## How to fix it + +There are two different ways you can resolve this error. + +**Option 1**: + +1. Log into QuickBooks Online. +2. Access the Employee Records for your submitters. +3. Edit the name to differentiate them from the name they have on their account in Expensify. +4. Sync your QuickBooks Online connection in Settings > Workspaces > [click workspace] > Connections. +5. Open the report and click the Export to button and then the QuickBooks Online option. + +**Option 2**: +1. Log into QuickBooks Online. +2. Manually create all of your Vendor Records, making sure that the email matches the email address associated with the user in Expensify. + - In this case, we recommend disabling _Automatically Create Entities_ under Settings > Workspaces > [workspace name] > Connections > Configure > Advanced, so that you will receive the correct error messages when a vendor record doesn't exist. + +# ExpensiError QBO097: When You Use Accounts Payable, You Must Choose a Vendor in the Name Field + +**Why does this happen?** + +This error occurs when you are exporting reimbursable expenses as Journal Entries against an A/P account and also use Employee Records in QuickBooks Online. + +## How to fix it + +There are three different ways you can resolve this error. +- Select a different type of export for reimbursable expenses under Settings > Workspaces > [worksapce name] > Connections > Configure > Export tab. +- Enable _Automatically Create Entities_ under Settings > Workspaces > [worksapce name] > Connections > Configure > Advanced to create vendor records automatically. +- Manually create vendor records in QuickBooks Online for each employee. + +# ExpensiError QBO099: Items marked as billable must have sales information checked + +**Why does this happen?** + +This error occurs when an Item category on an expense does not have sales information in QuickBooks Online. + +## How to fix it + +1. Log into QuickBooks Online. +2. Navigate to to your items list. +3. Click **Edit** to the right of the item used on the report with the error. Here you will see an option to check either "Sales" or "Purchasing". +4. Check the option for **Sales**. +5. Select an income account. +6. Save your changes. +7. Sync your QuickBooks Online connection in Settings > Workspaces > [workspace name] > Connections. +8. Open the report and click the **Export to** button and then the **QuickBooks Online** option. + + +# ExpensiError QBO193: Couldn't Connect to QuickBooks Online + +**Why does this happen?** + +This error occurs when the QuickBooks Online credentials used to make the connection have changed. + +_Note: This error message can also show up as, "QuickBooks Reconnect error: OAuth Token rejected.”_ + +## How to fix it + +1. Navigate to Settings > Workspaces > Groups > [workspace name] > Connections. +2. Click the **Sync Now** button. +3. In the pop-up window, click **Reconnect** and enter your current QuickBooks Online credentials. + +Note: If you are connecting with new credentials, you will need to reconfigure your settings and re-select the categories and tags you want enabled. We recommend taking a screenshot of your configuration settings beforehand so that you can reset the connection with those settings. + +# ExpensiError QBO077: Duplicate Document Number, This bill number has already been used. + +**Why does this happen?** + +This error occurs when settings in QuickBooks Online are enabled to warn of duplicate document numbers. + +## How to fix it + +1. Log into QuickBooks Online. +2. Navigate to Settings > Advanced. +3. Under the Other Preferences section, make sure "Warn if duplicate bill number is used" is set to "Off" +4. Sync your QuickBooks Online connection in Settings > Workspaces > [workspace name] > Connections. +5. Open the report and click the **Export to** button and then the **QuickBooks Online** option. + +# Export error: QuickBooks Online: The transaction needs to be in the same currency as the A/R and A/P accounts + +**Why does this happen?** + +This error occurs because the currency on the Vendor record in QuickBooks Online doesn't match the currency on the A/P account. + +## How to fix it + +1. Log into QuickBooks Online. +2. Open the vendor record. +3. Update the record to use with the correct A/P account, currency and an email matching their Expensify email. +You can find the correct Vendor record by exporting your QuickBooks Online [vendor list](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fqbo.intuit.com%2Fapp%2Fvendors) to a spreadsheet (click the export icon on the right-hand side of the page), and search for the email address of the person who submitted the report. + +If you have multiple Vendors with different currencies with the same email, Expensify is likely trying to export to the wrong one. + +1. Try removing the email address from the vendor in QuickBooks Online you aren't trying to export to. +2. Sync your QuickBooks Online connection in Settings > Workspaces > [workspace name] > Connections. +3. Open the report and click the **Export to** button and then the **QuickBooks Online** option. + +If this still fails, you'll need to confirm that the A/P account selected in Expensify is set to the correct currency for the export. + +1. Navigate to Settings > Workspaces > [workspace name] > Connections. +2. Under the Exports tab check that both A/P accounts are the correct currency. + +# Why are company card expenses exporting to the wrong account in QuickBooks Online? + +Multiple factors could be causing your company card transactions to export to the wrong place in your accounting system, but the best place to start is always the same. + +1. First, confirm that the company cards have been mapped to the correct accounts in Settings > Domains > Company Cards > click the **Edit Export button** for the card to view the account. +2. Next, confirm the expenses in question have been imported from the company card? + - Only expenses that have the Card+Lock icon next to them will export according to the mapping settings that you configure in the domain settings. + +It’s important to note that expenses imported from a card linked at the individual account level, expenses created from a SmartScanned receipt, and manually created cash expenses will export to the default bank account selected in your connection's configuration settings. + +**Is the report exporter a domain admin?** + +The user exporting the report must be a domain admin. You can check the history and comment section at the bottom of the report to see who exported the report. + +If your reports are being exported automatically by Concierge, the user listed as the Preferred Exporter under Settings > Workspaces > [workspaces name] > Connections > click **Configure** must be a domain admin as well. + +If the report exporter is not a domain admin, all company card expenses will export to the bank account set in Settings > Workspaces > [workspace name] > Connections > click **Configure** for non-reimbursable expenses. + +**Has the company card been mapped under the correct workspace?** + +If you have multiple workspaces connected to QuickBooks Online, each connected workspace will have a separate list of accounts to assign the card to. Unless you choose an account listed under the same workspace as the report you are exporting, expenses will export to the default bank account. + +# Can I export negative expenses to QuickBooks Online? + +In general, you can export negative expenses successfully to QBO regardless of which export method you choose. + +The one thing to keep in mind is that if you have Check selected as your export option, the total of the report can not be negative. diff --git a/docs/articles/expensify-classic/connections/sage-intacct/Configure-Sage-Intacct.md b/docs/articles/expensify-classic/connections/sage-intacct/Configure-Sage-Intacct.md new file mode 100644 index 000000000000..1f0be2f4571a --- /dev/null +++ b/docs/articles/expensify-classic/connections/sage-intacct/Configure-Sage-Intacct.md @@ -0,0 +1,153 @@ +--- +title: Configure Sage Intacct +description: Configure Sage Intacct's export, coding, and advanced settings. +--- + +By configuring your Sage Intacct settings in Expensify correctly, you can leverage the connection's settings to automate most tasks, making your workflow more efficient. + +# How to Configure Export Settings + +There are several options for exporting Expensify reports to Sage Intacct. Let's explore how to configure these settings to align with your business needs. + +To access these settings, go to **Settings > Workspace > Group > Connections** and select the **Configure** button. + +## Export Options + +### Preferred Exporter + +Any workspace admin can export to Sage Intacct, but only the preferred exporter will receive notifications in Expensify regarding the status of exports. + +### Date + +The three options for the date your report will export with are: +- **Date of last expense:** Uses the date on the most recent expense added to the report. +- **Exported date:** Is the date you export the report to Sage Intacct. +- **Submitted date:** Is the date the report creator originally submitted the report. + +Note: All export options except credit cards use the date in the drop-down. Credit card transactions use the transaction date. + +### Reimbursable Expenses + +Depending on your initial setup, your **reimbursable expenses** will be exported as either **Expense Reports** or **Vendor Bills** to Sage Intacct. + +### Non-Reimbursable Expenses + +**1a. Non-reimbursable expenses** will export separately from reimbursable expenses either as **Vendor Bills** or as **credit card charges** to the account you select. It is not an option to export non-reimbursable expenses as **Journal** entries. + +If you are centrally managing your company cards through your Domain Settings, you can export expenses from each individual card to a specific account in Intacct. See section 1b below on how to configure those export settings. + +Please note that credit Card Transactions cannot be exported to Sage Intacct at the top level if you have **Multi-Currency** enabled. Therefore, you will need to select an entity in the configuration of your Expensify Workspace by going to **Settings > Workspaces > Groups > [Workspace Name] > Connections > Configure**. + +**1b. Optional configuration when company cards are connected** +1. Click **Settings** near the bottom of the left-hand menu. +2. Navigate to Domains > [_domain name_] > Company Cards. +3. If you have more than one company card connection, select the connection first. +4. Locate the cardholder you want to configure in the list, +5. Click the **Edit Exports** button and assign the account the card expenses should export to in Sage Intacct. + +### Exporting Negative Expenses + +You can export negative expenses successfully to Intacct regardless of which Export Option you choose. The one thing to keep in mind is that if you have Expense Reports selected as your export option, the **total** of the report can not be negative. + +## How to Configure Coding Settings + +The appearance of your expense data in Sage Intacct depends on how you've configured it in Expensify. It's important to understand each available option to achieve the desired results. + +The appearance of your expense data in Sage Intacct depends on how you've configured it in Expensify. It's important to understand each available option to achieve the desired results. + +### Expense Types + +Categories are always enabled and are the primary means of matching expenses to the correct accounts in Sage Intact. The Categories in Expensify depend on your **Reimbursable** export options: +- If your Reimbursable export option is set to **Expense Reports** (the default), your Categories will be your **Expense Types**. +- If your Reimbursable export option is set to **Vendor Bills**, your Categories will be your **Chart of Accounts** (also known as GL Codes or Account Codes). + +You can disable unnecessary categories from your **Settings > Workspaces > Group > [Workspace Name] > Categories** page if your list is too extensive. Note that every expense must be coded with a Category, or it will not export. Also, when you first set up the integration, your existing categories will be overwritten. + +### Billable Expenses + +Enabling Billable expenses allows you to map your expense types or accounts to items in Sage Intacct. To do this, you'll need to enable the correct permissions on your Sage Intacct user or role. This may vary based on the modules you use in Sage Intacct, so you should enable read-only permissions for relevant modules such as Projects, Purchasing, Inventory Control, and Order Entry. + +Once permissions are set, you can map your categories (expense types or accounts, depending on your export settings) to specific items, which will then export to Sage Intacct. When an expense is marked as Billable in Expensify, users must select the correct billable Category (Item), or there will be an error during export. + +### Dimensions - Departments, Classes, and Locations + +If you enable these dimensions, you can choose from three data options: +- Not pulled into Expensify: Employee default (available when the reimbursable export option is set to Expense Reports) +- Pulled into Expensify and selectable on reports/expenses: Tags (useful for cross-charging between Departments or Locations) +- Report Fields (applies at the header level, useful when an employee's Location varies from one report to another) + +Please note that the term "tag" may appear instead of "Department" on your reports, so ensure that "Projects" is not disabled in your Tags configuration within your workspace settings. Make sure it's enabled within your coding settings of the Intacct configuration settings. When multiple options are available, the term will default to Tags. + +### Customers and Projects + +These settings are particularly relevant to billable expenses and can be configured as Tags or Report Fields. + +### Tax + +As of September 2023, our Sage Intacct integration supports native VAT and GST tax. To enable this feature, open the Sage Intacct configuration settings in your workspace, go to the Coding tab, and enable Tax. For existing Sage Intacct connectings, simply resync your workspace and the tax toggle will appear. For new Sage Intacct connections, the tax toggle will be available when you complete the integration steps. +Enabling this option will import your native tax rates from Sage Intacct into Expensify. From there, you can select default rates for each category. + +### User-Defined Dimensions + +You can add User-Defined Dimensions (UDD) to your workspace by locating the "Integration Name" in Sage Intacct. Please note that you must be logged in as an administrator in Sage Intacct to find the required fields. + +To find the Integration Name in Sage Intacct: +1. Go to **Platform Services > Objects > List** +2. Set "filter by application" to "user-defined dimensions." + +Now, in Expensify, navigate to **Settings > Workspaces > Group > [Workspace Name] > Connections**, and click **Configure** under Sage Intacct. On the Coding tab, enable the toggle next to User Defined Dimensions. Enter the "Integration name" and choose whether to import it into Expensify as an expense-level Tag or as a Report Field, then click **Save**. + +You'll now see the values for your custom segment available under Tags settings or Report Fields settings in Expensify. + + +## How to Configure Advanced Settings + +In multi-entity environments, you'll find a dropdown at the top of the sync options menu, where you can choose to sync with the top-level or a specific entity in your Sage Intacct instance. If you sync at the top level, we pull in employees and dimensions shared at the top level and export transactions to the top level. Otherwise, we sync information with the selected entity. + +### Auto Sync + +When a non-reimbursable report is finally approved, it will be automatically exported to Sage Intacct. Typically, non-reimbursable expenses will sync to the next open period in Sage Intacct by default. If your company uses Expensify's ACH reimbursement, reimbursable expenses will be held back and exported to Sage when the report is reimbursed. + +### Inviting Employees + +Enabling **Invite Employees** allows the integration to automatically add your employees to your workspace and create an Expensify account for them if they don't have one. +If you have your domain verified on your account, ensure that the Expensify account connected to Sage Intacct is an admin on your domain. +When you toggle on "Invite Employees" on the Advanced tab, all employees in Sage Intacct who haven't been invited to the Expensify group workspace you're connecting will receive an email invitation to join the group workspace. Approval workflow will default to Manager Approval and can be further configured on the People settings page. + +### Import Sage Intacct Approvals + +When the "Import Sage Intacct Approvals" setting is enabled, Expensify will automatically set each user's manager listed in Sage Intacct as their first approver in Expensify. If no manager exists in Sage Intacct, the approver can be set in the Expensify People table. You can also add a second level of approval to your Sage Intacct integration by setting a final approver in Expensify. +Please note that if you need to add or edit an optional final approver, you will need to select the **Manager Approval** option in the workflow. Here is how each option works: +- **Basic Approval:** All users submit to one user. +- **Manager Approval:** Each user submits to the manager (imported from Sage Intacct). Each manager forwards to one final approver (optional). +- **Configure Manually:** Import employees only, configure workflow in Expensify. + +### Sync Reimbursed Reports +When using Expensify ACH, reimbursable reports exported to Intacct are exported: +- As Vendor Bills to the default Accounts Payable account set in your Intacct Accounts Payable module configuration, OR +- As Expense Reports to the Employee Liabilities account in your Time & Expenses module configuration. +When ACH reimbursement is enabled, the "Sync Reimbursed Reports" feature will additionally export a Bill Payment to the selected Cash and Cash Equivalents account listed. If **Auto Sync** is enabled, the payment will be created when the report is reimbursed; otherwise, it will be created the next time you manually sync the workspace. +Intacct requires that the target account for the Bill Payment be a Cash and Cash Equivalents account type. If you aren't seeing the account you want in that list, please first confirm that the category on the account is Cash and Cash Equivalents. + +{% include faq-begin.md %} +## What if my report isn't automatically exported to Sage Intacct? + +There are a number of factors that can cause automatic export to fail. If this happens, the preferred exporter will receive an email and an Inbox task outlining the issue and any associated error messages. +The same information will be populated in the comments section of the report. +The fastest way to find a resolution for a specific error is to search the Community, and if you get stuck, give us a shout! +Once you've resolved any errors, you can manually export the report to Sage Intacct. + +## How can I make sure that I final approve reports before they're exported to Sage Intacct? + +Make sure your approval workflow is configured correctly so that all reports are reviewed by the appropriate people within Expensify before exporting to Sage Intacct. +Also, if you have verified your domain, consider strictly enforcing expense workspace workflows. You can set this up via Settings > Domains > [Domain Name] > Groups. + + +## If I enable Auto Sync, what happens to existing approved and reimbursed reports? + +If your workspace has been connected to Intacct with Auto Sync disabled, you can safely turn on Auto Sync without affecting existing reports which have not been exported. +If a report has been exported to Intacct and reimbursed via ACH in Expensify, we'll automatically mark it as paid in Intacct during the next sync. +If a report has been exported to Intacct and marked as paid in Intacct, we'll automatically mark it as reimbursed in Expensify during the next sync. +If a report has not been exported to Intacct, it will not be exported to Intacct automatically. + +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md b/docs/articles/expensify-classic/connections/sage-intacct/Connect-To-Sage-Intacct.md similarity index 99% rename from docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md rename to docs/articles/expensify-classic/connections/sage-intacct/Connect-To-Sage-Intacct.md index 560a65d0d722..369c3aae9fa9 100644 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md +++ b/docs/articles/expensify-classic/connections/sage-intacct/Connect-To-Sage-Intacct.md @@ -1,6 +1,7 @@ --- title: Sage Intacct description: Connect your Expensify workspace with Sage Intacct +order: 1 --- # Overview Expensify’s seamless integration with Sage Intacct allows you to connect using either Role-based permissions or User-based permissions. diff --git a/docs/articles/expensify-classic/connections/sage-intacct/Sage-Intacct-Troubleshooting.md b/docs/articles/expensify-classic/connections/sage-intacct/Sage-Intacct-Troubleshooting.md new file mode 100644 index 000000000000..db341f87e930 --- /dev/null +++ b/docs/articles/expensify-classic/connections/sage-intacct/Sage-Intacct-Troubleshooting.md @@ -0,0 +1,6 @@ +--- +title: Sage Intacct Troubleshooting +description: Sage Intacct Troubleshooting +--- + +# Coming soon diff --git a/docs/articles/expensify-classic/connections/xero/Configure-Xero.md b/docs/articles/expensify-classic/connections/xero/Configure-Xero.md new file mode 100644 index 000000000000..b23216c28401 --- /dev/null +++ b/docs/articles/expensify-classic/connections/xero/Configure-Xero.md @@ -0,0 +1,6 @@ +--- +title: Configure Xero +description: Configure Xero +--- + +# Coming soon diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md b/docs/articles/expensify-classic/connections/xero/Connect-To-Xero.md similarity index 99% rename from docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md rename to docs/articles/expensify-classic/connections/xero/Connect-To-Xero.md index 9dd479e90cf1..3010d11c2ff1 100644 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md +++ b/docs/articles/expensify-classic/connections/xero/Connect-To-Xero.md @@ -1,6 +1,7 @@ --- title: The Xero Integration description: Everything you need to know about Expensify's direct integration with Xero +order: 1 --- # About diff --git a/docs/articles/expensify-classic/connections/xero/Xero-Troubleshooting.md b/docs/articles/expensify-classic/connections/xero/Xero-Troubleshooting.md new file mode 100644 index 000000000000..98ae5033db50 --- /dev/null +++ b/docs/articles/expensify-classic/connections/xero/Xero-Troubleshooting.md @@ -0,0 +1,6 @@ +--- +title: Xero Troubleshooting +description: Xero Troubleshooting +--- + +# Coming soon diff --git a/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md b/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md index f24ed57dc655..38686462a1c2 100644 --- a/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md +++ b/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md @@ -3,78 +3,55 @@ title: Cardholder Settings and Features description: Expensify Card Settings for Employees --- -# How to use your Expensify Visa® Commercial Card -Once you receive your card, you can start using it right away. +# Using Your Expensify Visa® Commercial Card -First, you'll want to take note of the Smart Limit tied to your card – this is listed in your card settings via **Settings > Account > Credit Card Import**. This limit represents the total amount of unapproved expenses you can have on the card. +### Activate Your Card +You can start using your card immediately upon receipt by logging into your Expensify account, heading to your Home tab, and following the prompts on the _**Activate your Expensify Card**_ task. -It's crucial to continuously submit your expenses promptly, as that'll ensure they can be approved and restore your full limit. You can always chat with your admin if you need your limit adjusted. +### Review your Card's Smart Limit +Check your card’s Smart Limit via _**Settings > Account > Credit Card Import**_: +- This limit is the total amount of unapproved expenses you can have on the card. +- If a purchase is more than your card's Smart Limit, it will be declined. -You can swipe your Expensify Card like you would with any other card. As you make purchases, you'll get instant alerts on your phone letting you know if you need to SmartScan receipts. Any SmartScanned receipts should merge with the card expense automatically. +## Managing Expenses +- **Submit Expenses Promptly**: Submit your expenses regularly to restore your full limit. Contact your admin if you need a limit adjustment. +- **Using Your Card**: Swipe your Expensify Card like any other card. You’ll receive instant alerts on your phone for SmartScan receipts. SmartScanned receipts will merge automatically with card expenses. +- **eReceipts**: If your organization doesn’t require itemized receipts, Expensify will generate IRS-compliant eReceipts for all non-lodging transactions. +- **Reporting Expenses**: Report and submit Expensify Card expenses as usual. Approved expenses refresh your Smart Limit. -If your organization doesn't require itemized receipts, you can rely on eReceipts instead. As long as the expense isn't lodging-related, Expensify will automatically generate an IRS-compliant eReceipt for every transaction. +## Enabling Notifications +Download the Expensify mobile app and enable push notifications to stay updated on spending activity and potential fraud. -You can report and submit Expensify Card expenses just like any other expenses. As they're approved, your Smart Limit will be refreshed accordingly, allowing you to keep making purchases. +#### For iPhone: +1. Open the Expensify app and tap the three-bar icon in the upper-left corner. +2. Tap _**Settings > enable Receive real-time alerts**_. +3. Accept the confirmation to access your iPhone’s notification settings for Expensify. +4. Turn on **Allow Notifications** and select your notification types. -## Enable Notifications -Download the Expensify mobile app and enable push notifications to stay current on your spending activity. Your card is connected to your Expensify account, so each transaction on your card will trigger a push notification. We'll also send you a push notification if we detect potentially fraudulent activity and allow you to confirm your purchase. - -Follow the steps below to enable real-time alerts on your mobile device. - -**If you have an iPhone**: -1. Open the Expensify app and tap the three-bar icon in the upper-left corner -2. Tap **Settings** and enable **Receive realtime alerts** -3. Accept the confirmation dialogue to go to your iPhone's notification settings for Expensify. Turn on Allow Notifications, and choose the notification types you’d like! - -**If you have an Android**: -1. Go to Settings and open 'Apps and Notifications'. +#### For Android: +1. Go to _**Settings > Apps and Notifications**_. 2. Find and open Expensify and enable notifications. -3. Customize your alerts. Depending on your phone model, you may have extra options to customize the types of notifications you receive. - -## Your virtual card -Once you're assigned a limit, you'll be able to use your virtual card immediately. You can view your virtual card details via **Settings > Account > Credit Card Import > Show Details**. Keep in mind that your virtual card and physical card share a limit. - -The virtual Expensify Card includes a card number, expiration date, and security code (CVC). You can use the virtual card for online purchases, in-app transactions, and in-person payments once it's linked to a mobile wallet (Apple Pay or Google Pay). - -## How to access your virtual card details -Here's how to access your virtual card details via the Expensify mobile app: -1. Tap the three-bar icon in the upper-left corner -2. Tap **Settings > Connected Cards** -3. Under **Virtual Card**, tap **Show Details** - -From there, you can view your virtual card's number, CVV, expiration date, and billing address. - -Here's how to access your virtual card details via the Expensify web app: -1. Head to **Settings > Account > Credit Card Import** -2. Under **Virtual Card**, click **Show Details** - -From there, you can view your virtual card's card number, CVV, expiration date, and billing address. - -## How to add your virtual card to a digital wallet (Apple Pay or Google Pay) - -To use the Expensify Card for contactless payment, add it to your digital wallet from the mobile app: -1. Tap the three-bar icon in the upper-left corner -2. Tap **Settings > Connected Cards** -3. Depending on your device, tap **Add to Apple Wallet** or **Add to Gpay** -4. Complete the remaining steps - -## Expensify Card declines -As long as you've enabled 'Receive real-time alerts', you'll get a notification explaining the reason for each decline. You can enable alerts in the mobile app by clicking on the three-bar icon in the upper-left corner > **Settings** > toggle **Receive real-time alerts**. - -Here are some reasons an Expensify Card transaction might be declined: - -- You have an insufficient card limit - - If a transaction exceeds your Expensify Card's available limit, the transaction will be declined. You can see the remaining limit in the mobile app under **Settings > Connected Cards** or in the web app under **Settings > Account > Credit Card Import**. - - Submitting expenses and getting them approved will free up your limit for more spending. - -- Your card isn't active yet or it was disabled by your Domain Admin -- Your card information was entered incorrectly with the merchant. Entering incorrect card information, such as the CVC, ZIP, or expiration date, will also lead to declines. -There was suspicious activity -- If Expensify detects unusual or suspicious activity, we may block transactions as a security measure - - This could happen due to irregular spending patterns, attempted purchases from risky vendors, or multiple rapid transactions. - - Check your Expensify Home page to approve unusual merchants and try again. - - If the spending looks suspicious, we may complete a manual due diligence check, and our team will do this as quickly as possible - your cards will all be locked while this happens. -- The merchant is located in a restricted country +3. Customize your alerts based on your phone model. + +## Using Your Virtual Card +- **Access Details**: You can view your virtual card details (card number, expiration date, CVC) via _**Settings > Account > Credit Card Import > Show Details**_. The virtual and physical cards share the same limit. +- **Purchases**: Use the virtual card for online, in-app, and in-person payments when linked to a mobile wallet (Apple Pay or Google Pay). + +#### Adding to a Digital Wallet +To add your Expensify Card to a digital wallet, follow the steps below: + 1. Tap the three-bar icon in the upper-left corner. + 2. Tap _**Settings > Connected Cards**_. + 3. Tap **Add to Apple Wallet** or **Add to Gpay**, depending on your device. + 4. Complete the steps as prompted. + +## Handling Declines +- **Real-Time Alerts**: Enable real-time alerts in the mobile app (_**Settings > toggle Receive real-time alerts**_) to get notifications for declines. +- **Common Decline Reasons**: + - **Insufficient Limit**: Transactions exceeding the available limit will be declined. You can check your limit in _**Settings > Connected Cards**_ or _**Settings > Account > Credit Card Import**_. + - **Inactive or Disabled Card**: Ensure your card is active and not disabled by your Domain Admin. + - **Incorrect Information**: Entering incorrect card details (CVC, ZIP, expiration date) will result in declines. + - **Suspicious Activity**: Transactions may be blocked for unusual or suspicious activity. Check the Expensify Home page to approve unusual merchants. Suspicious spending may prompt a manual due diligence check, during which your cards will be locked. + - **Restricted Country**: Transactions from restricted countries will be declined. {% include faq-begin.md %} ## Can I use Smart Limits with a free Expensify account? diff --git a/docs/articles/expensify-classic/expensify-card/Dispute-A-Transaction.md b/docs/articles/expensify-classic/expensify-card/Dispute-A-Transaction.md index 19972b79d5e0..cb86c340dc81 100644 --- a/docs/articles/expensify-classic/expensify-card/Dispute-A-Transaction.md +++ b/docs/articles/expensify-classic/expensify-card/Dispute-A-Transaction.md @@ -1,50 +1,50 @@ --- title: Expensify Card - Transaction Disputes & Fraud -description: Learn how to dispute an Expensify Card transaction. +description: Understand how to dispute an Expensify Card transaction. --- -# Overview -When using your Expensify Visa® Commercial Card, you may come across transaction errors, which can include things like: -- Unrecognized, unauthorized, or fraudulent charges. -- Transactions of an incorrect amount. +# Disputing Expensify Card Transactions +While using your Expensify Visa® Commercial Card, you might encounter transaction errors, such as: +- Unauthorized transaction activity +- Incorrect transaction amounts. - Duplicate charges for a single transaction. -- Missing a promised merchant refund. +- Missing merchant refunds. -You’ll find all the relevant information on handling these below. +When that happens, you may need to file a dispute for one or more transactions. -# How to Navigate the Dispute Process ## Disputing a Transaction - -If you spot an Expensify Card transaction error, please contact us immediately at [concierge@expensify.com](mailto:concierge@expensify.com). After that, we'll ask a few questions to better understand the situation. If the transaction has already settled in your account (no longer pending), we can file a dispute with our card processor on your behalf. - -If you suspect fraud on your Expensify Card, don't hesitate to cancel it by heading to Settings > Account > Credit Card Import > Request A New Card. Better safe than sorry! - -Lastly, if you haven’t enabled Two-Factor Authentication (2FA) yet, please do so ASAP to add an additional layer of security to your account. +If you notice a transaction error on your Expensify Card, contact us immediately at concierge@expensify.com. We will ask a few questions to understand the situation better, and file a dispute with our card processor on your behalf. ## Types of Disputes +The most common types of disputes are: +- Unauthorized or fraudulent disputes +- Service disputes -There are two main dispute types: +### Unauthorized or fraudulent disputes +- Charges made after your card was lost or stolen. +- Unauthorized charges while your card is in your possession (indicating compromised information). +- Continued charges for a canceled recurring subscription. -1. Unauthorized charges/fraud disputes, which include: - - Charges made with your card after it was lost or stolen. - - Unauthorized charges while your card is still in your possession (indicating compromised card information). - - Continued charges for a canceled recurring subscription. +**If there are transactions made with your Expensify Card you don't recognize, you'll want to do the following right away:** +1. Cancel your card by going to _**Settings > Account > Credit Card Import > Request A New Card**_. +2. Enable Two-Factor Authentication (2FA) for added security under _**Settings > Account > Account Details > Two Factor Authentication**_. -2. Service disputes, which include: - - Received damaged or defective merchandise. - - Charged for merchandise but never received it. - - Double-charged for a purchase made with another method (e.g., cash). - - Made a return but didn't receive a timely refund. - - Multiple charges for a single transaction. - - Charges settled for an incorrect amount. +### Service Disputes +- Received damaged or defective merchandise. +- Charged for merchandise that was never received. +- Double-charged for a purchase made with another method (e.g., cash). +- Made a return but didn't receive a refund. +- Multiple charges for a single transaction. +- Charges settled for an incorrect amount. -You don't need to categorize your dispute; we'll handle that. However, this may help you assess if a situation warrants a dispute. In most cases, the initial step for resolving a dispute should be contacting the merchant, as they can often address the issue promptly. +For service disputes, contacting the merchant is often the quickest way to resolve the dispute. ## Simplifying the Dispute Process - -To ensure the dispute process goes smoothly, please: -- Provide detailed information about the disputed charge, including why you're disputing it, what occurred, and any steps you've taken to address the issue. -- If you recognize the merchant but not the charge, and you've transacted with them before, contact the merchant directly, as it may be a non-fraudulent error. -- Include supporting documentation like receipts or cancellation confirmations when submitting your dispute to enhance the likelihood of a favorable resolution (not required but highly recommended). +To ensure a smooth dispute process, please: +- Provide detailed information about the disputed charge, including why you're disputing it and any steps you've taken to address the issue. +- If you recognize the merchant but not the charge, contact the merchant directly. +- Include supporting documentation (e.g., receipts, cancellation confirmations) when submitting your dispute to increase the chances of a favorable resolution (recommended but not required). +- Make sure the transaction isn't pending (pending transactions cannot be disputed). + {% include faq-begin.md %} diff --git a/docs/articles/expensify-classic/expensify-card/Request-the-Card.md b/docs/articles/expensify-classic/expensify-card/Request-the-Card.md index 724745f458ef..1f412665fc2f 100644 --- a/docs/articles/expensify-classic/expensify-card/Request-the-Card.md +++ b/docs/articles/expensify-classic/expensify-card/Request-the-Card.md @@ -2,45 +2,36 @@ title: Request the Card description: Details on requesting the Expensify Card as an employee --- -# Overview - -Once your organization is approved for the Expensify Visa® Commercial Card, you can request a card! - -This article covers how to request, activate, and replace your physical and virtual Expensify Cards. - -# How to get your first Expensify Card - -An admin in your organization must first enable the Expensify Cards before you can receive a card. After that, an admin may assign you a card by setting a limit. You can think of setting a card limit as “unlocking” access to the card. - -If you haven’t been assigned a limit yet, look for the task on your account's homepage that says, “Ask your admin for the card!” This task allows you to message your admin team to make that request. - -Once you’re assigned a card limit, we’ll notify you via email to let you know you can request a card. A link within the notification email will take you to your account’s homepage, where you can provide your shipping address for the physical card. Enter your address, and we’ll ship the card to arrive within 3-5 business days. - -Once your physical card arrives in the mail, activate it in Expensify by entering the last four digits of the card in the activation task on your account’s homepage. - -# Virtual Card - -Once assigned a limit, a virtual card is available immediately. You can view the virtual card details via **Settings > Account > Credit Card Import > Show Details**. Feel free to begin transacting with the virtual card while your physical card is in transit – your virtual card and physical card share a limit. - -Please note that you must enable two-factor authentication on your account if you want to have the option to dispute transactions made on your virtual card. - -# Notifications - -To stay up-to-date on your card’s limit and spending activity, download the Expensify mobile app and enable push notifications. Your card is connected to your Expensify account, so each transaction on your card will trigger a push notification. We’ll also send you a push notification if we detect potentially fraudulent activity and allow you to confirm your purchase. - -# How to request a replacement Expensify Card - -You can request a new card anytime if your Expensify Card is lost, stolen, or damaged. From your Expensify account on the web, head to **Settings > Account > Credit Card Import** and click **Request a New Card**. Confirm the shipping information, complete the prompts, and your new card will arrive in 2 - 3 business days. - -Selecting the “lost” or “stolen” options will deactivate your current card to prevent potentially fraudulent activity. However, choosing the “damaged” option will leave your current card active so you can use it while the new one is shipped to you. - -If you need to cancel your Expensify Card and cannot access the website or mobile app, call our interactive voice recognition phone service (available 24/7). Call 1-877-751-5848 (US) or +44 808 196 0632 (Internationally). - -It's not possible to order a replacement card over the phone, so, if applicable, you would need to handle this step from your Expensify account. - -# Card Expiration Date - -If you notice that your card expiration date is soon, it's time for a new Expensify card. Expensify will automatically input a notification in your account's Home (Inbox) tab. This notice will ask you to input your address, but this is more if you have changed your address since your card was issued to you. You can ignore it and do nothing; the new Expensify card will ship to your address on file. The new Expensify card will have a new, unique card number and will not be associated with the old one. +To start using the Expensify Card, do the following: +1. **Enable Expensify Cards:** An admin must first enable the cards. Then, an admin can assign you a card by setting a limit, which allows access to the card. +2. **Request the Card:** + - If you haven’t been assigned a limit, look for the task on your account’s homepage that says, “Ask your admin for the card!” + - Completing that task will send an in-product notification to your admin team that you requested the card. + - Once you’re assigned a card limit, you’ll receive an email notification. Click the link in the email to provide your shipping address on your account’s homepage. + - Enter your address, and the physical card will be shipped within 3-5 business days. +3. **Activate the Card:** When your physical card arrives, activate it in Expensify by entering the last four digits of the card in the activation task on your homepage. + +### Virtual Cards +Once you've been assigned a limit, a virtual card is available immediately. You can view its details via _**Settings > Account > Credit Card Import > Show Details**_. + +To protect your account and card spend, enable two-factor authentication under _**Settings > Account > Account Details**_. + +### Notifications +- Download the Expensify mobile app and enable push notifications to stay updated on your card’s limit and spending. +- Each transaction triggers a push notification. +- You’ll also get notifications for potentially fraudulent activity, allowing you to confirm or dispute charges. + +## Request a Replacement Expensify Card +### If the card is lost, stolen, or damaged Card: + - Go to _**Settings > Account > Credit Card Import** and click **Request a New Card**_. + - Confirm your shipping information and complete the prompts. The new card will arrive in 2-3 business days. + - Selecting “lost” or “stolen” deactivates your current card to prevent fraud. Choosing “damaged” keeps the current card active until the new one arrives. + - If you can’t access the website or app, call 1-877-751-5848 (US) or +44 808 196 0632 (Internationally) to cancel your card. + +### If the card is expiring +- If your card is about to expire, Expensify will notify you via your account’s Home (Inbox) tab. +- Enter your address if it has changed; otherwise, do nothing, and the new card will ship to your address on file. +- The new card will have a unique number and will not be linked to the old one. {% include faq-begin.md %} diff --git a/docs/articles/expensify-classic/expensify-card/Statements.md b/docs/articles/expensify-classic/expensify-card/Statements.md index 894dfa3d8b9a..eb797f0cee4b 100644 --- a/docs/articles/expensify-classic/expensify-card/Statements.md +++ b/docs/articles/expensify-classic/expensify-card/Statements.md @@ -1,73 +1,62 @@ --- title: — Expensify Card Statements and Settlements -description: Learn how the Expensify Card statement and settlements work! +description: Understand how to access your Expensify Card Statement --- -# Overview -Expensify offers several settlement types and a statement that provides a detailed view of transactions and settlements. We discuss specifics on both below. +## Expensify Card Statements +Expensify offers several settlement types and a detailed statement of transactions and settlements. -# How to use Expensify Visa® Commercial Card Statement and Settlements -## Using the statement -If your domain uses the Expensify Card and you have a validated Business Bank Account, access the Expensify Card statement at Settings > Domains > Company Cards > Reconciliation Tab > Settlements. +### Accessing the Statement +- If your domain uses the Expensify Card and you have a validated Business Bank Account, access the statement at _**Settings > Domains > Company Cards > Reconciliation Tab > Settlements**_. +- The statement shows individual transactions (debits) and their corresponding settlements (credits). -The Expensify Card statement displays individual transactions (debits) and their corresponding settlements (credits). Each Expensify Cardholder has a Digital Card and a Physical Card, which are treated the same in settlement, reconciliation, and exporting to your accounting system. - -Here's a breakdown of crucial information in the statement: -- **Date:** For card payments, it shows the debit date; for card transactions, it displays the purchase date. -- **Entry ID:** This unique ID groups card payments and transactions together. -- **Withdrawn Amount:** This applies to card payments, matching the debited amount from the Business Bank Account. -- **Transaction Amount:** This applies to card transactions, matching the expense purchase amount. -- **User email:** Applies to card transactions, indicating the cardholder's Expensify email address. -- **Transaction ID:** A unique ID for locating transactions and assisting Expensify Support in case of issues. Transaction IDs are handy for reconciling pre-authorizations. To find the original purchase, locate the Transaction ID in the Settlements tab of the reconciliation dashboard, download the settlements as a CSV, and search for the Transaction ID within it. +### Key Information in the Statement +- **Date:** Debit date for card payments; purchase date for transactions. +- **Entry ID:** Unique ID grouping card payments and transactions. +- **Withdrawn Amount:** Amount debited from the Business Bank Account for card payments. +- **Transaction Amount:** Expense purchase amount for card transactions. +- **User Email:** Cardholder’s Expensify email address. +- **Transaction ID:** Unique ID for locating transactions and assisting support. ![Expanded card settlement that shows the various items that make up each card settlement.](https://help.expensify.com/assets/images/ExpensifyHelp_SettlementExpanded.png){:width="100%"} -The Expensify Card statement only shows payments from existing Business Bank Accounts under Settings > Account > Payments > Business Accounts. If a Business Account is deleted, the statement won't contain data for payments from that account. - -## Exporting your statement -When using the Expensify Card, you can export your statement to a CSV with these steps: +**Note:** The statement only includes payments from existing Business Bank Accounts under **Settings > Account > Payments > Business Accounts**. Deleted accounts' payments won't appear. - 1. Login to your account on the web app and click on Settings > Domains > Company Cards. - 2. Click the Reconciliation tab at the top right, then select Settlements. - 3. Enter your desired statement dates using the Start and End fields. - 4. Click Search to access the statement for that period. - 5. You can view the table or select Download to export it as a CSV. +## Exporting Statements +1. Log in to the web app and go to **Settings > Domains > Company Cards**. +2. Click the **Reconciliation** tab and select **Settlements**. +3. Enter the start and end dates for your statement. +4. Click **Search** to view the statement. +5. Click **Download** to export it as a CSV. ![Click the Download CSV button in the middle of the page to export your card settlements.](https://help.expensify.com/assets/images/ExpensifyHelp_SettlementExport.png){:width="100%"} ## Expensify Card Settlement Frequency -Paying your Expensify Card balance is simple with automatic settlement. There are two settlement frequency options: - - **Daily Settlement:** Your Expensify Card balance is paid in full every business day, meaning you’ll see an itemized debit each business day. - - **Monthly Settlement:** Expensify Cards are settled monthly, with your settlement date determined during the card activation process. With monthly, you’ll see only one itemized debit per month. (Available for Plaid-connected bank accounts with no recent negative balance.) +- **Daily Settlement:** Balance paid in full every business day with an itemized debit each day. +- **Monthly Settlement:** Balance settled monthly on a predetermined date with one itemized debit per month (available for Plaid-connected accounts with no recent negative balance). -## How settlement works -Each business day (Monday through Friday, excluding US bank holidays) or on your monthly settlement date, we calculate the total of posted Expensify Card transactions since the last settlement. The settlement amount represents what you must pay to bring your Expensify Card balance back to $0. +## How Settlement Works +- Each business day or on your monthly settlement date, the total of posted transactions is calculated. +- The settlement amount is withdrawn from the Verified Business Bank Account linked to the primary domain admin, resetting your card balance to $0. +- To change your settlement frequency or bank account, go to _**Settings > Domains > [Domain Name] > Company Cards**_, click the **Settings** tab, and select the new options from the dropdown menu. Click **Save** to confirm. -We'll automatically withdraw this settlement amount from the Verified Business Bank Account linked to the primary domain admin. You can set up this bank account in the web app under Settings > Account > Payments > Bank Accounts. +![Change your card settlement account or settlement frequency via the dropdown menus in the middle of the screen.](https://help.expensify.com/assets/images/ExpensifyHelp_CardSettings.png){:width="100%"} -Once the payment is made, your Expensify Card balance will be $0, and the transactions are considered "settled." -To change your settlement frequency or bank account, go to Settings > Domains > [Domain Name] > Company Cards. On the Company Cards page, click the Settings tab, choose a new settlement frequency or account from the dropdown menu, and click Save to confirm the change. +# FAQ -![Change your card settlement account or settlement frequency via the dropdown menus in the middle of the screen.](https://help.expensify.com/assets/images/ExpensifyHelp_CardSettings.png){:width="100%"} +## Can you pay your balance early if you’ve reached your Domain Limit? +- For Monthly Settlement, use the “Settle Now” button to manually initiate settlement. +- For Daily Settlement, balances settle automatically with no additional action required. -# Expensify Card Statement and Settlements FAQs -## Can you pay your balance early if you've reached your Domain Limit? -If you've chosen Monthly Settlement, you can manually initiate settlement using the "Settle Now" button. We'll settle the outstanding balance and then perform settlement again on your selected predetermined monthly settlement date. - -If you opt for Daily Settlement, the Expensify Card statement will automatically settle daily through an automatic withdrawal from your business bank account. No additional action is needed on your part. - ## Will our domain limit change if our Verified Bank Account has a higher balance? -Your domain limit may fluctuate based on your cash balance, spending patterns, and history with Expensify. Suppose you've recently transferred funds to the business bank account linked to Expensify card settlements. In that case, you should expect a change in your domain limit within 24 hours of the transfer (assuming your business bank account is connected through Plaid). - +Domain limits may change based on cash balance, spending patterns, and history with Expensify. If your bank account is connected through Plaid, expect changes within 24 hours of transferring funds. + ## How is the “Amount Owed” figure on the card list calculated? -The amount owed consists of all Expensify Card transactions, both pending and posted, since the last settlement date. The settlement amount withdrawn from your designated Verified Business Bank Account only includes posted transactions. - -Your amount owed decreases when the settlement clears. Any pending transactions that don't post timely will automatically expire, reducing your amount owed. - -## **How do I view all unsettled expenses?** -To view unsettled expenses since the last settlement, use the Reconciliation Dashboard's Expenses tab. Follow these steps: - 1. Note the dates of expenses in your last settlement. - 2. Switch to the Expenses tab on the Reconciliation Dashboard. - 3. Set the start date just after the last settled expenses and the end date to today. - 4. The Imported Total will show the outstanding amount, and you can click through to view individual expenses. +It includes all pending and posted transactions since the last settlement date. The settlement amount withdrawn only includes posted transactions. + +## How do I view all unsettled expenses? +1. Note the dates of expenses in your last settlement. +2. Go to the **Expenses** tab on the Reconciliation Dashboard. +3. Set the start date after the last settled expenses and the end date to today. +4. The **Imported Total** shows the outstanding amount, and you can click to view individual expenses. diff --git a/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md b/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md index 239da6518be7..fdbc178737e1 100644 --- a/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md +++ b/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md @@ -66,6 +66,8 @@ There are two different limit types that are best suited for their intended purp - _Fixed limit_ spend cards are ideal for one-time expenses or providing employees access to a card for a designated purchase. - _Monthly_ limit spend cards are perfect for managing recurring expenses such as subscriptions and memberships. +A virtual card with either of these limit types doesn't share its limit with any other cards, including the cardholder's smart limit cards. + **Where can employees see their virtual cards?** Employees can see their assigned virtual cards by navigating to **Settings** > **Account** > [**Credit Cards Import**](https://www.expensify.com/settings?param=%7B%22section%22:%22creditcards%22%7D) in their account. diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Accelo.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Accelo.md deleted file mode 100644 index fffe0abb43aa..000000000000 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/Accelo.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -title: Accelo -description: Help doc for Accelo integration ---- - - -# Overview -Accelo is a cloud-based business management software platform tailored for professional service companies, offering streamlined operations. It enables seamless integration with Expensify, allowing users to effortlessly import expense details from Expensify into Accelo, associating them with the corresponding project, ticket, or retainer within the system. - -# How to Connect Expensify to Accelo -To connect Expensify to Accelo, follow these clear steps: - -## Prerequisites -Ensure you have administrator access to Accelo. -Have a Workspace Admin role in Expensify. - -## Connecting Expensify to Accelo -1. Access the Expensify Integration Server: -- Open the Expensify Integration Server. -2. Retrieve Your Partner User ID and Partner User Secret: -- Important: These credentials are distinct from your regular Expensify username and password. -- If you haven't previously set up the integration server, click where it indicates "click here." -3. Regenerating Partner User Secret (If Necessary): -- Note: If you've previously configured the integration server, you must regenerate your Partner User Secret. Do this by clicking "click here" to regenerate your partnerUserSecret. -- If you currently use the Integration Server/API for another integration, remember to update that integration to use the new Secret. -4. Configure Accelo: -- Return to your Accelo account. -- Navigate to your Integrations page and select the Expensify tab. -5. Enter Expensify Integration Server Credentials: -- Provide your Expensify Integration Server's Partner User ID and Partner User Secret. -- Click "Save" to complete the setup. -6. Connection Established: -- Congratulations! Your Expensify account is now successfully connected to Accelo. - -With this connection in place, all Expensify users can effortlessly synchronize their expenses with Accelo, streamlining their workflow and improving efficiency. - -## How to upload your Accelo Project Codes as Tags in Expensify -Once you have connected Accelo to Expensify, the next step is to upload your Accelo Project Codes as Tags in Expensify. Simply go to Go to **Settings** > **Workspaces** > **Group** > _[Workspace Name]_ > **Tags** and upload your CSV. -If you directly integrate with Xero or QuickBooks Online, you must upload your Project Codes by appending your tags. Go to **Settings** > **Workspaces** > **Group** > _[Workspace Name]_ > **Tags** and click on “Append a custom tag list from a CSV” to upload your Project Codes via a CSV. - -# Deep Dive -## Information sync between Expensify and Accelo -The Accelo integration does a one-way sync, which means it brings expenses from Expensify into Accelo. When this happens, it transfers specific information from Expensify expenses to Accelo: - -| Expensify | Accelo | -|---------------------|-----------------------| -| Comment | Title | -| Date | Date Incurred | -| Category | Type | -| Tags | Against (relevant Project, Ticket or Retainer) | -| Distance (mileage) | Quantity | -| Hours (time expenses) | Quantity | -| Amount | Purchase Price and Sale Price | -| Reimbursable? | Reimbursable? | -| Billable? | Billable? | -| Receipt | Attachment | -| Tax Rate | Tax Code | -| Attendees | Submitted By | - -## Expense Status -The status of your expense report in Expensify is also synced in Accelo. - -| Expensify Report Status | Accelo Expense Status | -|-------------------------|-----------------------| -| Open | Submitted | -| Submitted | Submitted | -| Approved | Approved | -| Reimbursed | Approved | -| Rejected | Declined | -| Archived | Approved | -| Closed | Approved | - -## Importing expenses from Expensify to Accelo -Accelo regularly checks Expensify for new expenses once every hour. It automatically brings in expenses that have been created or changed since the last sync. diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md deleted file mode 100644 index 6c7014827ea6..000000000000 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -title: Certinia -description: Guide to connecting Expensify and Certinia FFA and PSA/SRP (formerly known as FinancialForce) ---- -# Overview -[Cetinia](https://use.expensify.com/financialforce) (formerly known as FinancialForce) is a cloud-based software solution that provides a range of financial management and accounting applications built on the Salesforce platform. There are two versions: PSA/SRP and FFA and we support both. - -# Before connecting to Certinia -Install the Expensify bundle in Certinia using the relevant installer: -* [PSA/SRP](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2M000002J0BHD%252Fpackaging%252FinstallPackage.apexp%253Fp0%253D04t2M000002J0BH) -* [FFA](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t4p000001UQVj) - -## Check contact details in Certinia -First, make sure you have a user and contact in Certinia that match your main email in Expensify. Then, create contacts for all employees who will be sending expense reports. Ensure that each contact's email matches the one they use in their Expensify account. - -## If you use PSA/SRP -Each report approver needs both a User and a Contact. The user does not need to have a SalesForce license. These can be free chatter users. -Set permission controls in Certinia for your user for each contact/resource. -* Go to Permission Controls - - Create a new permission control - - Set yourself (exporter) as the user - - Select the resource (report submitter) - - Grant all available permissions -* Set permissions on any project you are exporting to - - Go to **Projects** > _select a project_ > **Project Attributes** > **Allow Expenses Without Assignment** - - Select the project > **Edit** - - Under the Project Attributes section, check **Allow Expenses Without Assignment** -* Set up Expense Types (categories in Expensify - _SRP only_) - - Go to **Main Menu** > _+ symbol_ > **Expense Type GLA Mappings** - - Click **New** to add new mappings - -# How to connect to Certinia -1. Go to **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Connections** in Expensify -2. Click **Create a New Certinia (FinancialForce) Connection** -3. Log into your Certinia account -4. Expensify and Certinia will begin to sync (in Expensify) - -# How to configure export settings for Certinia -## Preferred Exporter -The preferred exporter is the user who will be the main exporter of reports. This person will receive the notifications for errors. - -## Payable Invoice Status and Date -Reports can be exported as Complete or In Progress, using date of last expense, submitted date or exported date. - -## Reimbursable and non-reimbursable exports -Both reimbursable and non-reimbursable reports are exported as payable invoices (FFA) or expense reports (PSA/SRP). If you have both Reimbursable and Non-Reimbursable expenses on a single report, we will create a separate payable invoice/expense report for each type. - -## Default Vendor (FFA) -Choose from the full list of vendors from your Certinia FFA account, this will be applied to the non-reimbursable payable invoices. - -# How to Configure coding for Certinia -## Company -Select which FinancialForce company to import from/export to. - -## Chart of Accounts (FFA) -Prepaid Expense Type and Profit & Loss accounts are imported to be used as categories on each expense. - -## Expense Type GLA Mappings (PSA/SRP) -Your Expense Type GLA Mappings are enabled in Expensify to use as categories on each expense when using both PSA and SRP; however, PSA will not import or export categories, while SRP will. - -## Dimensions (FFA) -We import four dimension levels and each has three options to select from: - -* Do not map: FinancialForce defaults will apply to the payable invoice, without importing into Expensify -* Tags: These are shown in the Tag section of your workspace, and employees can select them on each expense created -* Report fields: These will show in the Reports section of your workspace. Employees can select one to be applied at the header level i.e. the entire report. - -## Projects, Assignments, or Projects & Assignments (PSA/SRP) -These can be imported as tags with **Milestones** being optional. When selecting to import only projects, we will derive the account from the project. If an assignment is selected, we will derive both the account and project from the assignment. - -Note: If you are using a project that does not have an assignment, the box **Allow Expenses Without Assignment** must be checked on the project in FinancialForce. - -## Tax -Import tax rates from Certinia to apply to expenses. - -# How to configure advanced settings for Certinia -## Auto Sync -Auto Sync in Certinia performs daily updates to your coding. Additionally, it automatically exports reports after they receive final approval. For Non-Reimbursable expenses, syncing happens immediately upon final approval of the report. In the case of Reimbursable expenses, syncing occurs as soon as the report is reimbursed or marked as reimbursed. - -## Export tax as non-billable -When exporting Billable expenses, this dictates whether you will also bill the tax component to your clients/customers. - -# Deep Dive -## Multi-Currency in Certinia PSA/SRP -When exporting to Certinia PSA/SRP you may see up to three different currencies on the expense report in Certinia, if employees are submitting expenses in more than one original currency. -* Summary Total Reimbursement Amount: this currency is derived from the currency of the project selected on the expense. -* Amount field on the Expense line: this currency is derived from the Expensify workspace default report currency. -* Reimbursable Amount on the Expense line: this currency is derived from the currency of the resource with an email matching the report submitter. - -{% include faq-begin.md %} -## What happens if the report can’t be exported to Certinia? -* The preferred exporter will receive an email outlining the issue and any specific error messages -* Any error messages preventing the export from taking place will be recorded in the report’s history -* The report will be listed in the exporter’s Expensify Inbox as awaiting export. - -## If I enable Auto Sync, what happens to existing approved and reimbursed reports? -You can activate Auto Sync without worry because it relies on Final Approval to trigger auto-export. Existing Approved reports won't be affected. However, for Approved reports that haven't been exported to Certinia, you'll need to either manually export them or mark them as manually entered. - -## How do I export tax? -Tax rates are created in Expensify through the tax tracking feature under **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Tax**. We export the tax amount calculated on the expenses. - -## How do reports map to Payable Invoices in Certinia FFA? -* Account Name - Account associated with Expensify submitter’s email address -* Reference 1 - Report URL -* Invoice Description - Report title - -## How do reports map to Expense Reports in Certinia PSA/SRP? -* Expense report name - Report title -* Resource - User associated with Expensify submitter’s email address -* Description - Report URL -* Approver - Expensify report approver - -# Sync and Export Errors - -## ExpensiError FF0047: You must have an Ops Edit permission to edit approved records. -This error indicates that the permission control setup between the connected user and the report submitter or region is missing Ops Edit permission. - -In Certinia go to Permission Controls and click the one you need to edit. Make sure that Expense Ops Edit is selected under Permissions. - -## ExpensiError FF0076: Could not find employee in Certinia -Go to Contacts in Certinia and add the report creator/submitter's Expensify email address to their employee record, or create a record with that email listed. - -If a record already exists then search for their email address to confirm it is not associated with multiple records. - -## ExpensiError FF0089: Expense Reports for this Project require an Assignment -This error indicates that the project needs to have the permissions adjusted in Certinia - -Go to Projects > [project name] > Project Attributes and check Allow Expense Without Assignment. - -## ExpensiError FF0091: Bad Field Name — [field] is invalid for [object] -This means the field in question is not accessible to the user profile in Certinia for the user whose credentials were used to make the connection within Expensify. - -To correct this: -* Go to Setup > Build > expand Create > Object within Certinia -* Then go to Payable Invoice > Custom Fields and Relationships -* Click View Field Accessibility -* Find the employee profile in the list and select Hidden -* Make sure both checkboxes for Visible are selected - -Once this step has been completed, sync the connection within Expensify by going to **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Connections** > **Sync Now** and then attempt to export the report again. - -## ExpensiError FF0132: Insufficient access. Make sure you are connecting to Certinia with a user that has the 'Modify All Data' permission - -Log into Certinia and go to Setup > Manage Users > Users and find the user whose credentials made the connection. - -* Click on their profile on the far right side of the page -* Go to System > System Permissions -* Enable Modify All Data and save - -Sync the connection within Expensify by going to **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Connections** > **Sync Now** and then attempt to export the report again - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md deleted file mode 100644 index 8fe31f3ec4f4..000000000000 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -title: QuickBooks Desktop -description: How to connect Expensify to QuickBooks Desktop and troubleshoot issues. ---- -# Overview -QuickBooks Desktop is an accounting package developed by Intuit. It is designed for small and medium-sized businesses to help them manage their financial and accounting tasks. You can connect Expensify to QuickBooks Desktop to make expense management seamless. -To connect Expensify with QuickBooks Desktop, using Right Networks as your hosting platform is best. Right Networks is a cloud-based service we recommend for this integration. If you need a Right Networks account, complete [this form](https://info.rightnetworks.com/partner-expensify) and contact a Sales Consultant to start the process. - -# How to connect to QuickBooks Desktop -Before you link your Expensify policy with QuickBooks Desktop, ensure you log in as an Admin in QuickBooks. Also, check that the company file you want to connect to is the only one open. - -## Set up submitters in QuickBooks Desktop -For a seamless integration, here are the steps to follow: -* Make sure all report submitters are set up as Vendors in QuickBooks Desktop and their Expensify email is in the "Main Email" field of their Vendor record. You can do this in the vendor section of QuickBooks. -* If you want to export reports to your users' employee records instead of vendor records, select Check or Journal Entry as your reimbursable export option. -* To set up Expensify users as employees, activate QuickBooks Desktop Payroll. This module is necessary to access the Employee Profile tab, where you can enter the submitter's email addresses. - -## Enable/install the Expensify Sync Manager -Navigate to **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Connections**, select the Connect to QuickBooks Desktop radio button and click Connect to QuickBooks. - -**Enable the Expensify Sync Manager in Right Networks (recommended)** -*Please note: Single-user mode in QuickBooks Desktop is required.* -If you don't yet have an account with Right Networks, you must first contact Right Networks [here](https://info.rightnetworks.com/partner-expensify). You can enable the Expensify Sync Manager yourself from your Right Networks portal's **My Account** section or contact Right Networks for assistance. - -**OR, install the Expensify Sync Manager on Your Third-Party Remote Desktop.** -To download the Sync Manager to your desktop, you must contact your third-party remote desktop provider and request permission. They might have security restrictions, so it's best to communicate with them directly to avoid potential problems with the Sync Manager. Remember that the Sync Manager program file should be stored in the same location (i.e., the same drive) as your QuickBooks Desktop program. - -## Complete the connection -1. Open QuickBooks and access your desired Company File using the QuickBooks Admin credentials. Admin credentials are necessary for creating the connection due to permission requirements, but you won't need to stay logged in as an admin for syncing or exporting. -2. Navigate to your Expensify policy settings by going to **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Connections**. Copy the Token by selecting the copy icon. -3. While QuickBooks is still running, launch the Expensify Sync Manager. Paste your Token into the Sync Manager and click **Save**. -4. Once the Sync Manager status displays **Connected**, return to Expensify and click the *Continue* button. - -## Allow access -1. Return to QuickBooks, and you'll encounter an **Application Certificate** screen. On the first page of the Certificate screen, click **Yes, always; allow access even if QuickBooks is not running** and then click **Continue**. -2. On the second page of the Certificate screen, choose the Admin user from the dropdown menu, and then click *Done* to complete this step. Note that selecting Admin here does not require you to be logged in as an admin to use this connection; it's simply selecting the appropriate permissions. -3. Head back to Expensify and patiently wait for the sync process to finish, then move on to the configuration. - -# How to configure export settings for QuickBooks Desktop -To Configure Settings, go to **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Connections** and click **Configure**. - -## Preferred Exporter -This person is used in QuickBooks Desktop as the export user. They will also receive notifications for errors. - -## Date -Choose either the report's submitted date, the report's exported date, or the date of the last expense on the report when exporting reports to QuickBooks Desktop. - -## Use unique reference numbers -Enable this to allow use of a unique reference number for each transaction. Disable this to use the same Report ID for all expenses from a certain report. - -## Reimbursable expenses -* **Vendor Bill (recommended):** A single itemized vendor bill for each Expensify report. An A/P account is required to export to a vendor bill. -* **Check:** A single itemized check for each Expensify report. -* **Journal Entry:** A single itemized journal entry for each Expensify report. - -## Non-reimbursable expenses -**Credit Card Expenses:** -* Each expense will appear as a separate credit card transaction. -* The posting date will match your credit card statement. -* To display the merchant name in the payee field in QuickBooks Desktop, ensure that a matching Vendor exists in QuickBooks. Expensify searches for an exact match during export. If no match is found, the payee is mapped to a **Credit Card Misc.** Vendor created by Expensify. -* If you're centrally managing company cards through Domain Control, you can export expenses from each card to a specific QuickBooks account (detailed instructions available). - -**Debit Card Expenses:** -* Expenses export as individual itemized checks for each Expensify report. -* The check is written to the "vendor," which is the person who created or submitted the report in Expensify. - -**Vendor Bill:** -* Each Expensify report results in a single itemized vendor bill. -* The bill is associated with the "vendor," which is the individual responsible for creating or submitting the report in Expensify. - -# How to configure coding for QuickBooks Desktop -## Categories -Expensify's integration with QuickBooks brings in your Chart of Accounts as Categories in Expensify automatically. Here's how to manage them: -1. After connecting, go to **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Categories** to view the accounts imported from QuickBooks Desktop. -2. You can use the enable/disable button to choose which Categories your employees can access. Additionally, you can set specific rules for each Category via the blue settings cog. -3. Expensify offers Auto-Categorization to automatically assign expenses to the appropriate expense categories. -4. If needed, you can edit the names of the imported Categories to simplify expense coding for your employees. Keep in mind that if you make changes to these accounts in QuickBooks Desktop, the category names in Expensify will update to match them during the next sync. -5. _**Important:**_ Each expense must have a category selected to export to QuickBooks Desktop. The selected category must be one imported from QuickBooks Desktop; you cannot manually create categories within Expensify policy settings. - -## Classes -Classes can be imported from QuickBooks as either tags (line-item level) or report fields (header level). - -## Customers/Projects -You can bring in Customers/Projects from QuickBooks into Expensify in two ways: as tags (at the line-item level) or as report fields (at the header level). If you're utilizing Billable Expenses in Expensify, here's what you need to know: -* Customers/Projects must be enabled if you're using Billable Expenses. -* Expenses marked as "Billable" need to be tagged with a Customer/Project to successfully export them to QuickBooks. - -## Items -Items can be imported from QuickBooks as categories alongside your expense accounts. - -{% include faq-begin.md %} -## How do I sync my connection? -1: Ensure that both the Expensify Sync Manager and QuickBooks Desktop are running. -2: On the Expensify website, navigate to **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Connections** > **QuickBooks Desktop**, and click **Sync now**. -3: Wait for the syncing process to finish. Typically, this takes about 2-5 minutes, but it might take longer, depending on when you last synced and the size of your QuickBooks company file. The page will refresh automatically once syncing is complete. - -We recommend syncing at least once a week or whenever you make changes in QuickBooks Desktop that could impact how your reports export from Expensify. Changes could include adjustments to your Chart of Accounts, Vendors, Employees, Customers/Jobs, or Items. Remember, both the Sync Manager and QuickBooks Desktop need to be running for syncing or exporting to work. - -## Can I export negative expenses? -Generally, you can export negative expenses to QuickBooks Desktop successfully, regardless of your option. However, please keep in mind that if you have *Check* selected as your export option, the report's total cannot be negative. - -## How does multi-currency work with QuickBooks Desktop? -When using QuickBooks Desktop Multi-Currency, there are some limitations to consider based on your export options: -1. **Vendor Bills and Checks:** The currency of the vendor and the currency of the account must match, but they do not have to be in the home currency. -2. **Credit Card:** If an expense doesn't match an existing vendor in QuickBooks, it exports to the **Credit Card Misc.** vendor created by Expensify. When exporting a report in a currency other than your home currency, the transaction will be created under the vendor's currency with a 1:1 conversion. For example, a transaction in Expensify for $50 CAD will appear in QuickBooks as $50 USD. -3. **Journal Entries:** Multi-currency exports will fail because the account currency must match both the vendor currency and the home currency. - -# Sync and export errors -## Error: No Vendor Found For Email in QuickBooks -To address this issue, ensure that each submitter's email is saved as the **Main Email** in their Vendor record within QuickBooks Desktop. Here's how to resolve it: -1. Go to your Vendor section in QuickBooks. -2. Verify that the email mentioned in the error matches the **Main Email** field in the respective vendor's record. It's important to note that this comparison is case-sensitive, so ensure that capitalization matches as well. -3. If you prefer to export reports to your users' employee records instead of their vendor records, select either **Check** or **Journal Entry** as your reimbursable export option. If you are setting up Expensify users as employees, activate QuickBooks Desktop Payroll to access the Employee Profile tab where submitter email addresses need to be entered. -4. Once you've added the correct email to the vendor record, save this change, and then sync your policy before attempting to export the report again. - -## Error: Do Not Have Permission to Access Company Data File -To resolve this error, follow these steps: -1. Log into QuickBooks Desktop as an Admin in single-user mode. -2. Go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences**. -3. Select the Expensify Sync Manager and click on **Properties**. -4. Ensure that **Allow this application to login automatically** is checked, and then click **OK**. Close all windows within QuickBooks. -5. If you still encounter the error after following the above steps, go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences**, and remove the Expensify Sync Manager from the list. -6. Next, attempt to sync your policy again in Expensify. You'll be prompted to re-authorize the connection in QuickBooks. -7. Click **Yes, always; allow access even if QuickBooks is not running.** -8. From the dropdown, select the Admin user, and then click **Continue**. Note that selecting **Admin** here doesn't mean you always have to be logged in as an admin to use the connection; it's just required for setting up the connection. -9. Click **Done** on the pop-up window and return to Expensify, where your policy should complete the syncing process. - -## Error: The Wrong QuickBooks Company is Open. -This error suggests that the wrong company file is open in QuickBooks Desktop. To resolve this issue, follow these steps: -1. First, go through the general troubleshooting steps as outlined. -2. If you can confirm that the incorrect company file is open in QuickBooks, go to QuickBooks and select **File** > **Open or Restore Company** > _[Company Name]_ to open the correct company file. After doing this, try syncing your policy again. -3. If the correct company file is open, but you're still encountering the error, completely close QuickBooks Desktop, reopen the desired company file and then attempt to sync again. -4. If the error persists, log into QuickBooks as an admin in single-user mode. Then, go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences** and remove the Expensify Sync Manager from the list. -5. Next, try syncing your policy again in Expensify. You'll be prompted to re-authorize the connection in QuickBooks, allowing you to sync successfully. -6. If the error continues even after trying the steps above, double-check that the token you see in the Sync Manager matches the token in your connection settings. - -## Error: The Expensify Sync Manager Could Not Be Reached. -To resolve this error, follow these steps: -*Note: You must be in single-user mode to sync.* - -1. Ensure that both the Sync Manager and QuickBooks Desktop are running. -2. Confirm that the Sync Manager is installed in the correct location. It should be in the same location as your QuickBooks application. If QuickBooks is on your local desktop, the Sync Manager should be there, too. If QuickBooks is on a remote server, install the Sync Manager there. -Verify that the Sync Manager's status is **Connected**. -3. If the Sync Manager status is already **Connected**, click **Edit** and then *Save* to refresh the connection. Afterwards, try syncing your policy again. -4. If the error persists, double-check that the token you see in the Sync Manager matches the token in your connection settings. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md deleted file mode 100644 index 623e5f1dd997..000000000000 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md +++ /dev/null @@ -1,324 +0,0 @@ ---- -title: QuickBooks Online -description: Everything you need to know about using Expensify's direct integration with QuickBooks Online. ---- -# Overview - -The Expensify integration with QuickBooks Online brings in your expense accounts and other data and even exports reports directly to QuickBooks for easy reconciliation. Plus, with advanced features in QuickBooks Online, you can fine-tune coding settings in Expensify for automated data export to optimize your accounting workflow. - -## Before connecting - -It's crucial to understand the requirements based on your specific QuickBooks subscription: - -- While all the features are available in Expensify, their accessibility may vary depending on your QuickBooks Online subscription. -- An error will occur if you try to export to QuickBooks with a feature enabled that isn't part of your subscription. -- Please be aware that Expensify does not support the Self-Employed subscription in QuickBooks Online. - -![QuickBooks Online - Subscription types]({{site.url}}/assets/images/QBO1.png){:width="100%"} - -# How to connect to QuickBooks Online - -## Step 1: Setup employees in QuickBooks Online - -Employees must be set up as either Vendors or Employees in QuickBooks Online. Make sure to include the submitter's email in their record. - -If you use vendor records, you can export as Vendor Bills, Checks, or Journal Entries. If you use employee records, you can export as Checks or Journal Entries (if exporting against a liability account). - -Additional Options for Streamlined Setup: - -- Automatic Vendor Creation: Enable “Automatically Create Entities” in your connection settings to automatically generate Vendor or Employee records upon export for submitters that don't already exist in QBO. -- Employee Setup Considerations: If setting up submitters as Employees, ensure you activate QuickBooks Online Payroll. This will grant access to the Employee Profile tab to input employee email addresses. - -## Step 2: Connect Expensify and QuickBooks Online - -- Navigate to Settings > Workspaces > Group > [Workspace Name] > Connections > QuickBooks Online. Click Connect to QuickBooks. -- Enter your QuickBooks Online Administrator’s login information and choose the QuickBooks Online Company File you want to connect to Expensify (you can connect one Company File per Workspace). Then click Authorize. -- Enter your QuickBooks Online Administrator’s login information and choose the QuickBooks Online Company File you want to connect to Expensify (you can connect one Company File per Workspace): - -## Exporting historical Reports to QuickBooks Online: - -After connecting QuickBooks Online to Expensify, you may receive a prompt to export all historical reports from Expensify. To export multiple reports at once, follow these steps: - -- Go to the Reports page on the web. -- Tick the checkbox next to the reports you want to export. -- Click 'Export To' and select 'QuickBooks Online' from the drop-down list. - -If you don't want to export specific reports, click “Mark as manually entered” on the report. - -# How to configure export settings for QuickBooks Online - -Our QuickBooks Online integration offers a range of features. This section will focus on Export Settings and how to set them up. - -## Preferred Exporter - -Any Workspace admin can export to your accounting integration, but the Preferred Exporter can be chosen to automate specific steps. You can set this role from Settings > Workspaces > Group > [Workspace Name] > Connections > Configure > Export > Preferred Exporter. - -The Preferred Exporter: - -- Is the user whose Concierge performs all automated exports on behalf of. -- Is the only user who will see reports awaiting export in their **Home.** -- Must be a **Domain Admin** if you have set individual GL accounts for Company Card export. -- Must be a **Domain Admin** if this is the Preferred Workspace for any Expensify Card domain using Automatic Reconciliation. - -## Date - -When exporting reports to QuickBooks Online, you can choose the report's **submitted date**, the report's **exported date**, or the **date of the last expense on the report.** - -Most export options (Check, Journal Entry, and Vendor Bill) will create a single itemized entry with one date. -Please note that if you choose a Credit Card or Debit Card for non-reimbursable expenses, we'll use the transaction date on each expense during export. - -# Reimbursable expenses - -Reimbursable expenses export to QuickBooks Online as: - -- Vendor Bills -- Checks -- Journal Entries - -## Vendor bill (recommended) - -This is a single itemized vendor bill for each Expensify report. If the accounting period is closed, we will post the vendor bill on the first day of the next open period. If you export as Vendor Bills, you can also choose to Sync reimbursed reports (set on the Advanced tab). **An A/P account is required to export to a vendor bill.** - -The submitter will be listed as the vendor in the vendor bill. - -![Vendor Bill]({{site.url}}/assets/images/QBO2-Bill.png){:width="100%"} - -## Check - -This is a single itemized check for each Expensify report. You can mark a check to be printed later in QuickBooks Online. - -![Check to print]({{site.url}}/assets/images/QBO3-Checktoprint.png){:width="100%"} - -## Journal entry - -This is a single itemized journal entry for each Expensify report. - -![Journal Entry]({{site.url}}/assets/images/QBO4-JournalEntry.png){:width="100%"} - -# Non-reimbursable expenses - -Non-reimbursable expenses export to QuickBooks Online as: - -- Credit Card expenses -- Debit Card Expenses -- Vendor Bills - -## Credit/debit card - -Using Credit/Debit Card Transactions: - -- Each expense will be exported as a bank transaction with its transaction date. -- If you split an expense in Expensify, we'll consolidate it into a single credit card transaction in QuickBooks with multiple line items posted to the corresponding General Ledger accounts. - -Pro-Tip: To ensure the payee field in QuickBooks Online reflects the merchant name for Credit Card expenses, ensure there's a matching Vendor in QuickBooks Online. Expensify checks for an exact match during export. If none are found, the payee will be mapped to a vendor we create and labeled as Credit Card Misc. or Debit Card Misc. - -![Expense]({{site.url}}/assets/images/QBO5-Expense.png){:width="100%"} - -If you centrally manage your company cards through Domains, you can export expenses from each card to a specific account in QuickBooks. - -## Vendor Bill - -- A single detailed vendor bill is generated for each Expensify report. If the accounting period is closed, the vendor bill will be posted on the first day of the next open period. If you choose to export non-reimbursable expenses as Vendor Bills, you can assign a default vendor to the bill. -- The export will use your default vendor if you have Default Vendor enabled. If the Default Vendor is disabled, the report's submitter will be set as the Vendor in QuickBooks. - -## Billable Expenses - -- In Expensify, you can designate expenses as billable. These will be exported to QuickBooks Online with the billable flag. This feature applies only to expenses exported as Vendor Bills or Checks. To maximize this functionality, ensure that any billable expense is associated with a Customer/Job. - -## Export Invoices - -If you are creating Invoices in Expensify and exporting these to QuickBooks Online, this is the account the invoice will appear against. - -# Configure coding for QuickBooks Online - -The coding tab is where your information is configured for Expensify; this will allow employees to code expenses and reports accurately. - -- Categories -- Classes and/or Customers/Projects -- Locations -- Items -- Tax - -## Categories - -QuickBooks Online expense accounts will be automatically imported into Expensify as Categories. - -## Account Import - -Equity type accounts will also be imported as categories. - -Important notes: - -- Other Current Liabilities can only be exported as Journal Entries if the submitter is set up as an Employee in QuickBooks. -- Exchange Gain or Loss detail type does not import. - -Recommended steps to take after importing the expense accounts from QuickBooks to Expensify: - -- Go to Settings > Workspaces > Groups > [Workspace Name] > Categories to see the accounts imported from QuickBooks Online. -- Use the enable/disable button to choose which Categories to make available to your employees, and set Category specific rules via the blue settings cog. -- If necessary, edit the names of imported Categories to make expense coding easier for your employees. (Please Note: If you make any changes to these accounts in QuickBooks Online, the category names on Expensify's side will revert to match the name of the account in QuickBooks Online the next time you sync). -- If you use Items in QuickBooks Online, you can import them into Expensify as Categories. - -Please note that each expense has to have a category selected to export to QuickBooks Online. The chosen category has to be imported from QuickBooks Online and cannot be manually created within the Workspace settings. - -## Classes and Customers/Projects - -If you use Classes or Customers/Projects in QuickBooks Online, you can import those into Expensify as Tags or Report Fields: - -- Tags let you apply a Class and/or Customer/Project to each expense. -- Report Fields enables you to apply a Class and/or Customer/Project to all expenses on a report. - -Note: Although Projects can be imported into Expensify and coded to expenses, due to the limitations of the QuickBooks API, expenses cannot be created within the Projects module in QuickBooks. - -## Locations - -Locations can be imported into Expensify as a Report Field or, if you export reimbursable expenses as Journal Entries and non-reimbursable expenses as Credit/Debit Card, you can import Locations as Tags. - -## Items - -If you use Items in QuickBooks Online, you can import Items defined with Purchasing Information (with or without Sales Information) into Expensify as Categories. - -## Tax - -- Using our tax tracking feature, you can assign a tax rate and amount to each expense. -- To activate tax tracking, go to connection configuration and enable it. This will automatically import purchasing taxes from QuickBooks Online into Expensify. -- After the connection is set, navigate to Settings > Workspaces > Groups > [Workspace Name] > Tax. Here, you can view the taxes imported from QuickBooks Online. -- Use the enable/disable button to choose which taxes are accessible to your employees. -- Set a default tax for the Company Workspace, which will automatically apply to all new expenses. -- Please note that, at present, tax cannot be exported to Journal Entries in QuickBooks Online. -- Expensify performs a daily sync to ensure your information is up-to-date. This minimizes errors from outdated QuickBooks Online data and saves you time on syncing. - -# How to configure advanced settings for QuickBooks Online - -The advanced settings are where functionality for automating and customizing the QuickBooks Online integration can be enabled. -Navigate to this section of your Workspace by following Settings > Workspaces > Group > [Workspace Name] > Connections > Configure button > Advanced tab. - -## Auto Sync -With QuickBooks Online auto-sync, once a non-reimbursable report is final approved in Expensify, it's automatically queued for export to QuickBooks Online. For expenses eligible for reimbursement with a linked business bank account, they'll sync when marked as reimbursed. - -## Newly Imported Categories - -This setting determines the default status of newly imported categories from QuickBooks Online to Expensify, either enabled or disabled. - -## Invite Employees - -Enabling this automatically invites all Employees from QuickBooks Online to the connected Expensify Company Workspace. If not, you can manually invite or import them using a CSV file. - -## Automatically Create Entities - -When exporting reimbursable expenses as Vendor Bills or Journal Entries, Expensify will automatically create a vendor in QuickBooks if one doesn't exist. It will also generate a customer when exporting Invoices. - -## Sync Reimbursed Reports - -Enabling this marks the Vendor Bill as paid in QuickBooks Online when you reimburse a report via ACH direct deposit in Expensify. If reimbursing outside Expensify, marking the Vendor Bill as paid will automatically in QuickBooks Online update the report as reimbursed in Expensify. Note: After enabling this feature, select your QuickBooks Account in the drop-down, indicating the bank account for reimbursements. - -## Collection Account - -If you are exporting Invoices from Expensify to Quickbooks Online, this is the account the Invoice will appear against once marked as Paid. - -# Deep Dive - -## Preventing Duplicate Transactions in QuickBooks - -When importing a banking feed directly into QuickBooks Online while also importing transactions from Expensify, it's possible to encounter duplicate entries in QuickBooks. To prevent this, follow these steps: - -Step 1: Complete the Approval Process in Expensify - -- Before exporting any expenses to QuickBooks Online, ensure they are added to a report and the report receives approval. Depending on your Workspace setup, reports may require approval from one or more individuals. The approval process concludes when the last user who views the report selects "Final Approve." - -Step 2: Exporting Reports to QuickBooks Online - -- To ensure expenses exported from Expensify match seamlessly in the QuickBooks Banking platform, make sure these expenses are marked as non-reimbursable within Expensify and that “Credit Card” is selected as the non-reimbursable export option for your expenses. - -Step 3: Importing Your Credit Card Transactions into QuickBooks Online - -- After completing Steps 1 and 2, you can import your credit card transactions into QuickBooks Online. These imported banking transactions will align with the ones brought in from Expensify. QuickBooks Online will guide you through the process of matching these transactions, similar to the example below: - -![Transactions]({{site.url}}/assets/images/QBO7-Transactions.png){:width="100%"} - -## Tax in QuickBooks Online - -If your country applies taxes on sales (like GST, HST, or VAT), you can utilize Expensify's Tax Tracking along with your QuickBooks Online tax rates. Please note: Tax Tracking is not available for Workspaces linked to the US version of QuickBooks Online. If you need assistance applying taxes after reports are exported, contact QuickBooks. - -To get started: - -- Go to Settings > Workspaces > Group > [Workspace Name] > Connections, and click Configure. -- Navigate to the Coding tab. -- Turn on **Tax**. -- Click Save. This imports the Tax Name and rate from QuickBooks Online. -- Visit Settings > Workspaces > Group > [Workspace Name] > Tax to view the imported taxes. -- Use the enable/disable button in the Tax tab to choose which taxes your employees can use. - -Remember, you can also set a default tax rate for the entire Workspace. This will be automatically applied to all new expenses. The user can still choose a different tax rate for each expense. - -Tax information can't be sent to Journal Entries in QuickBooks Online. Also, when dealing with multiple tax rates, where one receipt has different tax rates (like in the EU, UK, and Canada), users should split the expense into the respective parts and set the appropriate tax rate for each part. - -## Multi-currency - -When working with QuickBooks Online Multi-Currency, there are some things to remember when exporting Vendor Bills and Check! Make sure the vendor's currency and the Accounts Payable (A/P) bank account match. - -In QuickBooks Online, the currency conversion rates are not applied when exporting. All transactions will be exported with a 1:1 conversion rate, so for example, if a vendor's currency is CAD (Canadian Dollar) and the home currency is USD (US Dollar), the export will show these currencies without applying conversion rates. - -![Check]({{site.url}}/assets/images/QBO6-Check.png){:width="100%"} - -To correct this, you must manually update the conversion rate after the report has been exported to QuickBooks Online. - -Specifically for Vendor Bills: - -If multi-currency is enabled and the Vendor's currency is different from the Workspace currency, OR if QuickBooks Online home currency is foreign from the Workspace currency, then: - -- We create the Vendor Bill in the Vendor's currency (this is a QuickBooks Online requirement - we don't have a choice) -- We set the exchange rate between the home currency and the Vendor's currency -- We convert line item amounts to the vendor's currency - -Let's consider this example: - -- QuickBooks Online home currency is USD -- Vendor's currency is VND -- Workspace (report) currency is JPY - -Upon export, we: - -1. Specified the bill is in VND -2. Set the exchange rate between VND and USD (home currency), computed at the time of export. -3. Converted line items from JPY (currency in Expensify) to VND -4. QuickBooks Online automatically computed the USD amount (home currency) based on the exchange rate we specified -5. Journal Entries, Credit Card, and Debit Card: - -Multi-currency exports will fail as the account currency must match both the vendor and home currencies. - -## Report Fields - -Report fields are a handy way to collect specific information for a report tailored to your organization's needs. They can specify a project, business trip, client, location, and more! - -When integrating Expensify with Your Accounting Software, you can create your report fields in your accounting software so the next time you sync your Workspace, these fields will be imported into Expensify. - -To select how a specific field imports to Expensify, head to Settings > Workspaces > Group > -[Workspace Name] > Connections > Accounting Integrations > QuickBooks Online > Configure > Coding. - -Here are the QuickBooks Online fields that can be mapped as a report field within Expensify: - -- Classes -- Customers/Projects -- Locations - -{% include faq-begin.md %} - -## What happens if the report can't be exported to QuickBooks Online automatically? - -If a report encounters an issue during automatic export to QuickBooks Online, you'll receive an email with details about the problem, including any specific error messages. These messages will also be recorded in the report's history section. - -The report will be placed in your Home for your attention. You can address the issues there. If you need further assistance, refer to our QuickBooks Online Export Errors page or export the report manually. - -## How can I ensure that I final approve reports before they're exported to QuickBooks Online? - -To ensure reports are reviewed before export, set up your Workspaces with the appropriate workflow in Expensify. Additionally, consider changing your Workspace settings to enforce expense Workspace workflows strictly. This guarantees that your Workspace's workflow is consistently followed. - -## What happens to existing approved and reimbursed reports if I enable Auto Sync? - -- If Auto Sync was disabled when your Workspace was linked to QuickBooks Online, enabling it won't impact existing reports that haven't been exported. -- If a report has been exported and reimbursed via ACH, it will be automatically marked as paid in QuickBooks Online during the next sync. -- If a report has been exported and marked as paid in QuickBooks Online, it will be automatically marked as reimbursed in Expensify during the next sync. -- Reports that have yet to be exported to QuickBooks Online won't be automatically exported. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/workspaces/Configure-Reimbursement-Settings.md b/docs/articles/expensify-classic/workspaces/Configure-Reimbursement-Settings.md new file mode 100644 index 000000000000..aea84d338934 --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Configure-Reimbursement-Settings.md @@ -0,0 +1,55 @@ +--- +title: Configure Reimbursement Settings +description: Set up direct or indirect reimbursements for your workspace. +--- + + +Reimbursing employees in Expensify is quick, easy, and completely free. Let Expensify do the tedious work for you by taking advantage of the features available to automate employee reimbursement. + +# Configure a Workspace's Reimbursement Settings +There are a few ways to reimburse employees in Expensify. The option that's best suited for you and your business will depend on a few different factors: +- **Direct Reimbursement**: For companies with a business bank account located in the US that reimburse employees within the US. +- **Indirect Reimbursement**: This option is available to all members, and connecting a bank account to Expensify is not required. Indirect reimbursement indicates that all reports are reimbursed outside of Expensify. +- **Global Reimbursement**: If your company’s business bank account is in the US, Canada, the UK, Europe, or Australia, you can reimburse employees directly in nearly any country worldwide. + +## Set Up Direct Reimbursement + +Once a [business bank account is connected to Expensify](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-USD#how-to-add-a-verified-business-bank-account), a workspace admin can enable indirect reimbursement via **Settings > Workspaces > Workspace Name > Reimbursement > Direct**. + +#### Additional features available with Direct Reimbursement: +- **Select a default reimburser for the Workspace from the dropdown menu**: + - The default reimburser will receive notifications to reimburse reports in Expensify. + - Any workspace admin who also has access to the business bank account can be added as a default reimburser. +- **Set a default withdrawal account for the Workspace**: + - The default bank account is used to reimburse all of the reports submitted on the corresponding workspace. +- **Set a manual reimbursement threshold to automate reimbursement**: + - If the total of a given report is less than the threshold set, reimbursement will occur automatically upon final approval. + - If the total of a given report is more than the threshold, it will need to be reimbursed manually. + +## Set Up Indirect Reimbursement + +A Workspace admin can enable indirect reimbursement via **Settings > Workspaces > Workspace Name > Reimbursement > Indirect**. + +**Additional features under Reimbursement > Indirect:** +If you reimburse through a separate system or through payroll, Expensify can collect and export employee bank account details for you. Reach out to your Account Manager or Concierge to have the Reimbursement Details Export format added to the account. + +## Set Up Global Reimbursement + +Once [a business bank account is connected to Expensify](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-USD#how-to-add-a-verified-business-bank-account), a workspace admin can enable indirect reimbursement via **Settings > Workspaces > Workspace Name > Reimbursement > Direct > Enable Global Reimbursements**. + +More information on setting up global reimbursements can be found **[here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Global-Reimbursements)**. + +{% include faq-begin.md %} + +## How do I export employee bank account details once the Reimbursement Details Export format is added to my account? + +Employee bank account details can be exported from the Reports page by selecting the relevant Approved reports and then clicking **Export to > Reimbursement Details Export**. + +## Is it possible to change the name of a verified business bank account in Expensify? + +Bank account names can be updated by going to _**Settings > Accounts > Payments**_ and clicking the pencil icon next to the bank account name. + +## What is the benefit of setting a default reimburser? + +Setting a default reimburser on the Workspace ensures that all outstanding reports are reimbursed as this member will receive notifications alerting them to reports that require their action. +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/workspaces/Expenses.md b/docs/articles/expensify-classic/workspaces/Expense-Settings.md similarity index 61% rename from docs/articles/expensify-classic/workspaces/Expenses.md rename to docs/articles/expensify-classic/workspaces/Expense-Settings.md index 4a2dc56c430f..c3a8ab31394d 100644 --- a/docs/articles/expensify-classic/workspaces/Expenses.md +++ b/docs/articles/expensify-classic/workspaces/Expense-Settings.md @@ -2,19 +2,29 @@ title: Expensify Workspace Expense Settings description: Expense Settings --- -# Overview +Expensify offers multiple ways to customize how expenses are created and managed at the workspace level. Whether you’re using an individual workspace or managing expenses in a group workspace, there are various expense settings you can customize. -Expensify offers multiple ways to customize how expenses are created in your workspace. In this doc, you’ll learn how to set up expense basics, distance expenses, and time expenses. +# Set up the expense settings on a workspace -Whether you’re flying solo with your Individual workspace or submitting with a team on your Group workspace, we have settings to support how you use Expensify. +You can manage the expense settings on a workspace under **Settings** > **Workspaces** > **Individual** or **Group** > [_Workspace Name_] > **Expenses**. From here you can customize the following expense-level settings: +- **Violations**: When enabled, employee expenses that fall outside of workspace preferences are flagged as violations. +- **Preferences**: Configure the reimbursable and billable settings for the expenses submitted to the corresponding workspace. +- **Distance**: This is where you can set the reimbursable mileage rates for yourself or your employees. +- **Time**: Set an hourly billable rate so members of the workspace can create time expenses for reimbursement. -# How to manage expense settings in your workspace +## Violations +A workspace admin can customize the following parameters at the expense level: +- **Max Expense Age (Days)** +- **Max Expense Amount** +- **Receipt Required Amount** -Let’s cover the expense basics first! In the following sections, we’ll go through each part of managing expense settings in your workspace. +If an expense is submitted that falls outside of those parameters, Expensify will automatically detect it as a violation and alert both the expense creator and reviewer that it needs to be corrected. -## Controlling cash expenses +More information on violations can be found [**here**](https://help.expensify.com/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations). -A cash expense is any expense created manually or by uploading a receipt for SmartScan; a cash expense does not mean the expense was paid for with cash. The other type of expense you’ll most commonly see is credit card expenses, which means the expenses imported from a credit card or bank connection. +## Preferences + +A cash expense is any expense created manually or by uploading a receipt for SmartScan; it does not mean the expense was paid for with cash. The other type of expense you’ll most commonly see is credit card expenses, which are expenses imported from a credit card or bank connection. There are four options for cash expenses: @@ -23,7 +33,7 @@ There are four options for cash expenses: - **Forced always reimbursable** - All cash expenses are forced to be reimbursable; they cannot be marked as non-reimbursable. - **Forced always non-reimbursable** - All cash expenses are forced to be non-reimbursable; they cannot be marked as reimbursable. -## Setting up billable expenses +### Billable expenses Billable expenses refer to expenses you or your employees incur that need to be re-billed to a specific client or vendor. @@ -37,7 +47,7 @@ Under Expense Basics, you can choose the setting that is best for you. If your Group workspace is connected to Xero, QuickBooks Online, NetSuite, or Sage Intacct, you can export billable expenses to be invoiced to customers. To set this up, go to the Coding tab in the connection configuration settings. -## Using eReceipts +### eReceipts eReceipts are full digital replacements of their paper equivalents for purchases of $75 or less. @@ -46,65 +56,57 @@ Click the toggle to your preferred configuration. - **Enabled** - All imported credit card expenses in US dollars of $75 or less will have eReceipts in the receipt image. - **Disabled** - No expenses will generate an eReceipt. -Note: _We will not generate an eReceipt for lodging expenses._ +Note: Expensify will not generate an eReceipt for lodging expenses. -## Securing receipt images +### Secure receipt images Whether you’re sharing your receipts with your accountant, having an auditor review exported expenses, or simply wanting to export to keep a hard copy for yourself, receipt visibility will be an essential consideration. Under _Public Receipt Visibility_, you can determine who can view receipts on your workspace. - **Enabled** means receipts are viewable by anyone with the URL. They don't need to be an Expensify user or a workspace member to view receipts. -- **Disabled** means receipts are viewable by users of Expensify, who would have access to view the receipt in the application. You must be an Expensify user with access to the report a receipt is on and logged into your account to view a receipt image via URL. +- **Disabled** means receipts are viewable by Expensify users, who would have access to view the receipt in the application. You must be an Expensify user with access to the report a receipt is on and logged into your account to view a receipt image via URL. -## Track mileage expenses +## Distance Expenses +How to set up distance expenses: +1. Select whether you want to capture _miles_ or _kilometers_, +2. Set the default category to be used on distance expenses, +3. Click **Add A Mileage Rate** to add as many rates as you need, +4. Set the reimbursable amount per mile or kilometer. -Whether using the Individual or Group workspace, you can create distance rates to capture expenses in miles or kilometers. +**Note:** If a rate is toggled off it is immediately disabled. This means that users are no longer able to select it when creating a new distance expense. If only one rate is available then that rate will be toggled on by default. -Preliminary setup steps include: +### Track tax on mileage expenses +If you’re tracking tax in Expensify you can also track tax on distance expenses. The first step is to enable tax in the workspace. You can do this by going to **Settings** > **Workspaces** > **Individual** or **Group** > [_Workspace Name_] > **Tax**. -1. Selecting whether you want to capture _miles_ or _kilometers_, -2. Setting the default category to be used on distance expenses, -3. Click **Add A Mileage Rate** to add as many rates as you need, -4. Set the reimbursable amount per mile or kilometer. +Once tax is enabled on a workspace level you will see a toggle to _Track Tax_ in the Distance section of the workspace settings. If tax is disabled on the workspace the Track Tax toggle will not display. -Note: _If a rate is toggled off it is immediately disabled. This means that users are no longer able to select it when creating a new distance expense. If only one rate is available then that rate will be toggled on by default._ +When Track Tax is enabled, you will need to enter additional information about the rates you have set. This includes the _Tax Reclaimable on_ and _Tax Rate_ fields. With that information, Expensify will work out the correct tax reclaim for each expense. -## Set an hourly rate +If you enable tax but don’t select a tax rate or enter a tax reclaimable amount, we will not calculate any tax amount for that rate. If, at any point, you switch the tax rate or enter a different reclaimable portion for an existing distance rate, the mileage rate will need to be re-selected on expenses for the tax amount to update according to the new values. -Using Expensify you can track time-based expenses to bill your clients at an hourly rate or allow employees to claim an hourly stipend. +**Note:** Expensify won’t automatically track cumulative mileage. If you need to track cumulative mileage per employee, we recommend building a mileage report using our custom export formulas. -Click the toggle under the _Time_ section to enable the feature and set a default hourly rate. After that, you and your users will be able to create time-based expenses from the [**Expenses**](https://expensify.com/expenses) page of the account. +## Time Expenses -# Deep dives +Using Expensify you can track time-based expenses to bill your clients at an hourly rate or allow employees to claim an hourly stipend. -## What is Concierge Receipt Audit for the Control Plan? +Click the toggle under the _Time_ section to enable the feature and set a default hourly rate. Then, you and your users can create time-based expenses from the [**Expenses**](https://expensify.com/expenses) page of the account. -Concierge Receipt Audit is a real-time audit and compliance of receipts submitted by employees and workspace users. Concierge checks every receipt for accuracy and compliance, flagging any expenses that seem fishy before expense reports are even submitted for approval. All risky expenses are highlighted for manual review, leaving you with more control over and visibility into expenses. When a report is submitted and there are risky expenses on it, you will be immediately prompted to review the risky expenses and determine the next steps. +## Concierge Receipt Audit -**Why you should use Concierge Receipt Audit** +Concierge Receipt Audit is a real-time audit and compliance of receipts submitted by employees and workspace users. Concierge checks every receipt for accuracy and compliance, flagging any expenses that seem fishy before expense reports are even submitted for approval. All risky expenses are highlighted for manual review, leaving you with more control over and visibility into expenses. When a report is submitted and there are risky expenses on it, you will be immediately prompted to review the risky expenses and determine the next steps. +**Benefits of Concierge Receipt Audit** - To make sure you don't miss any risky expenses that need human oversight. - To avoid needing to manually review all your company receipts. - It's included at no extra cost with the [Control Plan](https://www.expensify.com/pricing). - Instead of paying someone to audit your company expenses or being concerned that your expenses might be audited by a government agency. -- It's easy to use! Concierge will alert you to the risky expense and present it to you in an easy-to-follow review tutorial. +- It's easy -- Concierge will alert you to the risky expense and present it to you in an easy-to-follow review tutorial. - In addition to the risky expense alerts, Expensify will include a Note with audit details on every report. -Note: _If a report has audit alerts on it, you'll need to Review the report and Accept the alerts before it can be approved._ - -## Tracking tax on mileage expenses - -If you’re tracking tax in Expensify you can also track tax on distance expenses. The first step is to enable tax in the workspace. You can do this by going to **Settings** > **Workspaces** > **Individual** or **Group** > [_Workspace Name_] > **Tax**. - -Once tax is enabled on a workspace level you will see a toggle to _Track Tax_ in the Distance section of the workspace settings. If tax is disabled on the workspace the Track Tax toggle will not display. - -When Track Tax is enabled you will need to enter additional information to the rates you have set, this includes the _Tax Reclaimable on_ and _Tax Rate_ fields. With that information, Expensify will work out the correct tax reclaim for each expense. - -If you enable tax but don’t select a tax rate or enter a tax reclaimable amount, we will not calculate any tax amount for that rate. If, at any point, you switch the tax rate or enter a different reclaimable portion for an existing distance rate, the mileage rate will need to be re-selected on expenses for the tax amount to update according to the new values. - -Note: _Expensify won’t automatically track cumulative mileage. If you need to track cumulative mileage per employee, we recommend building a mileage report using our custom export formulas._ +**Note:** If a report has audit alerts on it, you'll need to Review the report and Accept the alerts before it can be approved. {% include faq-begin.md %} diff --git a/docs/articles/expensify-classic/workspaces/Invoicing.md b/docs/articles/expensify-classic/workspaces/Invoicing.md deleted file mode 100644 index f692f8f8d62e..000000000000 --- a/docs/articles/expensify-classic/workspaces/Invoicing.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: Expensify Invoicing -description: Expensify Invoicing supports your business with unlimited invoice sending and receiving, payments, and status tracking in one single location. ---- -# Overview -Expensify Invoicing lets you create and send invoices, receive payments, and track the status of your invoices with Expensify, regardless of whether your customer has an Expensify account. Invoicing is included with all Expensify subscriptions, no matter the plan — just pay the processing fee (2.9%) per transaction. - -# How to Set Up Expensify Invoicing - -**If you have a Group Workspace:** - -1. Log into your Expensify account from the web (not the mobile app) -3. Head to **Settings** > **Workspaces** > **Group** > [_Workspace Name_] > [**Invoices**](https://expensify.com/policy?param={"policyID":"20AB6A03EB9CE54D"}#invoices). - -**If you have an Individual Workspace:** - -1. Log into your Expensify account from the web (not the mobile app) -2. Head to **Settings** > **Workspaces** > **Individual** > [_Workspace Name_]> [**Invoices**](https://expensify.com/policy?param={"policyID":"BD5FB746D3B220D6"}#invoices). - -Here, you’ll be able to create a markup or add a payment account. Don’t forget you need a verified bank account to send or accept invoice payments via ACH. - -# Deep Dive - -To help your invoice stand out and look more professional, you can: - -- Add your logo -- Set your workspace currency to add default report-level fields -- Create additional report-level fields to display more details - -## Add a Logo - -From your Expensify account on the web (not the mobile app), go to **Settings** > **Account** > **Account Details**. Then click **Edit Photo** under _Your Details_ to upload your logo. - -## Set the Workspace Currency - -To set your currency, head to **Settings** > **Workspaces** > **Individual** or **Group** > **Reports**. This will add default report-level fields to your invoices. You can see these at the bottom of your [**Reports**](https://expensify.com/reports) page. - -Here are the default report-level fields based on common currencies: - -- GBP: VAT Number & Supplier Address (your company address) -- EUR: VAT Number & Supplier Address (your company address) -- AUD: ABN Number & Supplier Address (your company address) -- NZD: GST Number & Supplier Address (your company address) -- CAD: Business Number & Supplier Address (your company address) - -## Adding Additional Fields to Your Invoices - -In addition to the default report-level fields, you can create custom invoice fields. - -At the bottom of the same Reports page, under the _Add New Field_ section, you’ll have multiple options. - -- **Field Title**: This is the name of the field as displayed on your invoice. -- **Type**: You have the option to select a _text-based_ field, a _dropdown_ of selections, or a _date_ selector. -- **Report Type**: Select _Invoice_ to add the field to your invoices. - -Don’t forget to click the **Add** button once you’ve set your field parameters! - -For example, you may want to add a PO number, business address, website, or any other custom fields. - -_Please check the regulations in your local jurisdiction to ensure tax and business compliance._ - -## Removing Fields from Your Invoices - -If you want to delete a report field, click the red trashcan on the field in your **Workspace** > **Individual** or **Group** > **Report** settings to remove it from all future invoices. Unsent invoices will have a red **X** next to the report field, which you can click to remove before sending the invoice to your customer. diff --git a/docs/articles/expensify-classic/workspaces/Reimbursement.md b/docs/articles/expensify-classic/workspaces/Reimbursement.md deleted file mode 100644 index ed2384d12006..000000000000 --- a/docs/articles/expensify-classic/workspaces/Reimbursement.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Reimbursement -description: Enable reimbursement and reimburse expense reports ---- - - -# Overview -Reimbursement in Expensify is quick, easy, and completely free. Let Expensify do the tedious work for you by taking advantage of features to automate employee reimbursement. - -# How to Enable Reimbursement -There are several options for reimbursing employees in Expensify. The options available will depend on which country your business bank account is domiciled in. - -## Direct Reimbursement - -Direct reimbursement is available to companies who have a verified US bank account and are reimbursing employees within the US. To use direct reimbursement, you must have a US business bank account verified in Expensify. - -A Workspace admin can enable direct reimbursement via **Settings > Workspaces > Workspace Name > Reimbursement > Direct**. - -**Additional features under Reimbursement > Direct:** - - Select a **default reimburser** for the Workspace from the dropdown menu. The default reimburser is the person who will receive notifications to reimburse reports in Expensify. You’ll be able to choose among all Workspace Admins who have access to the business bank account. - - Set a **default withdrawal account** for the Workspace. This will set a default bank account that report reimbursements are withdrawn from. - - Set a **manual reimbursement threshold** to automate reimbursement. Reports whose total falls under the manual reimbursement threshhold will be reimbursed automatocally upon final approval; reports whose total falls above the threshhold will need to be reimbursed manually by the default reimburser. - -Expensify also offers direct global reimbursement to some companies with verified bank accounts in USD, GBP, EUR and AUD who are reimbursing employees internationally. For more information about Global Reimbursement, see LINK - -## Indirect Reimbursement - -Indirect reimbursement is available to all companies in Expensify and no bank account is required. Indirect reimbursement indicates that the report will be reimbursed outside of Expensify. - -A Workspace admin can enanble indirect reimbursement via **Settings > Workspaces > Workspace Name > Reimbursement > Indirect**. - -**Additional features under Reimbursement > Indirect:** -If you reimburse through a seperate system or through payroll, Expensify can collect and export employee bank account details for you. Just reach out to your Account Manager or concierge@expensify.com for us to add the Reimbursement Details Export format to the account. - -{% include faq-begin.md %} - -## How do I export employee bank account details once the Reimbursement Details Export format is added to my account? - -Employee bank account details can be exported from the Reports page by selecting the relevant Approved reports and then clicking **Export to > Reimbursement Details Export**. - -## Is it possible to change the name of a verified business bank account in Expensify? - -Bank account names can be updated via **Settings > Accounts > Payments** and clicking the pencil icon next to the bank account name. - -## What is the benefit of setting a default reimburser? - -The main benefit of being defined as the "reimburser" in the Workspace settings is that this user will receive notifications on their Home page alerting them when reports need to be reimbursed. -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/workspaces/Currency.md b/docs/articles/expensify-classic/workspaces/Set-Currency.md similarity index 80% rename from docs/articles/expensify-classic/workspaces/Currency.md rename to docs/articles/expensify-classic/workspaces/Set-Currency.md index 77b5fbbb3ebc..4537f510b8b7 100644 --- a/docs/articles/expensify-classic/workspaces/Currency.md +++ b/docs/articles/expensify-classic/workspaces/Set-Currency.md @@ -1,18 +1,17 @@ --- -title: Report Currency -description: Understanding expense and report currency +title: Setting Up Report Currency +description: Define a currency in your workspace's settings --- # Overview As a workspace admin, you can choose a default currency for your employees' expense reports, and we’ll automatically convert any expenses into that currency. -Here are a few essential things to remember: - +Here are a few things to remember: - Currency settings for a workspace apply to all expenses under that workspace. If you need different default currencies for certain employees, creating separate workspaces and configuring the currency settings is best. - As an admin, the currency settings you establish in the workspace will take precedence over any currency settings individual users may have in their accounts. - Currency is a workspace-level setting, meaning the currency you set will determine the currency for all expenses submitted on that workspace. -# How to select the currency on a workspace +# Select the currency on a workspace ## As an admin on a group workspace @@ -28,11 +27,9 @@ Here are a few essential things to remember: Please note the currency setting on an individual workspace is overridden when you submit a report on a group workspace. -# Deep Dive - ## Conversion Rates -Using data from Open Exchange Rates, Expensify takes the average rate on the day the expense occurred to convert an expense from one currency to another. The conversion rate can vary depending on when the expense happened since the rate is determined after the market closes on that specific date. +Using data from Open Exchange Rates, Expensify converts expenses from one currency to another using the average rate on the day the expense occurred. The conversion rate can vary depending on when the expense occurred since it is determined after the market closes on that specific date. If the markets aren’t open on the day the expense takes place (i.e., on a Saturday), Expensify will use the daily average rate from the last available market day before the purchase took place. @@ -40,7 +37,7 @@ When an expense is logged for a future date, possibly to anticipate a purchase t ## Managing expenses for employees in several different countries -Suppose you have employees scattered across the globe who submit expense reports in various currencies. The best way to manage those expenses is to create separate group workspaces for each location or region where your employees are based. +If you have employees scattered across the globe who submit expense reports in various currencies, the best way to manage those expenses is to create separate group workspaces for each location or region where your employees are based. Then, set the default currency for that workspace to match the currency in which the employees are reimbursed. diff --git a/docs/articles/expensify-classic/workspaces/Set-Up-Invoicing.md b/docs/articles/expensify-classic/workspaces/Set-Up-Invoicing.md new file mode 100644 index 000000000000..8ec279da29a6 --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Set-Up-Invoicing.md @@ -0,0 +1,51 @@ +--- +title: Expensify Invoicing +description: Expensify Invoicing offers the ability to send, receive, and track the status of payments in one location. +--- +Invoicing lets you create and send invoices, receive payments, and track the status of your invoices, regardless of whether the customer has an Expensify account. This feature is included with all Expensify subscriptions, no matter the plan — you'll just pay the processing fee (2.9%) per transaction. + +# Set Up Expensify Invoicing +Before using the invoice feature, you'll need to [connect a business bank account](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-USD) to Expensify. + +Then, do the following: +1. Log into your Expensify account from the web (not the mobile app) +2. Head to _**Settings > Workspaces > Workspace Name > [Invoices](https://expensify.com/policy?param={"policyID":"20AB6A03EB9CE54D"}#invoices)**_. + +Here, you’ll be able to create a markup or add a payment account. + +## Add a Logo + +From your Expensify account on the web, go to _**Settings > Account > Account Details**_. Then click **Edit Photo** under _Your Details_ to upload your company logo. + +## Set the Workspace Currency + +To set the currency, head to _**Settings** > **Workspaces** > **Reports**_. This will add default report-level fields to your invoices. You can see these at the bottom of the [**Reports**](https://expensify.com/reports) page. + +Below are the default report-level fields based on common currencies: +- GBP: VAT Number & Supplier Address (your company address) +- EUR: VAT Number & Supplier Address (your company address) +- AUD: ABN Number & Supplier Address (your company address) +- NZD: GST Number & Supplier Address (your company address) +- CAD: Business Number & Supplier Address (your company address) + +## Adding Additional Fields to Invoices + +In addition to the default report-level fields, you can create custom invoice fields. + +At the bottom of the same Reports page, under the _Add New Field_ section, you’ll have multiple options. + +- **Field Title**: This is the name of the field as displayed on your invoice. +- **Type**: You have the option to select a _text-based_ field, a _dropdown_ of selections, or a _date_ selector. +- **Report Type**: Select _Invoice_ to add the field to your invoices. + +Don’t forget to click the **Add** button once you’ve set your field parameters! + +For example, you may want to add a PO number, business address, website, or any other custom fields. + +_Please check the regulations in your local jurisdiction to ensure tax and business compliance._ + +## Removing Fields from Invoices + +If you want to delete a report field, click the red trashcan on the field under _**Settings** > **Workspaces** > **Reports**_. This will remove that field from all future invoices. + +Unsent invoices will have a red **X** next to the report field, which you can click to remove before sending the invoice to your customer. diff --git a/docs/articles/new-expensify/connections/Set-Up-NetSuite-Connection.md b/docs/articles/new-expensify/connections/Set-Up-NetSuite-Connection.md new file mode 100644 index 000000000000..5c6678e068be --- /dev/null +++ b/docs/articles/new-expensify/connections/Set-Up-NetSuite-Connection.md @@ -0,0 +1,375 @@ +--- +title: Set up NetSuite connection +description: Integrate NetSuite with Expensify +--- +
      + +# Connect to NetSuite + +## Overview +Expensify’s integration with NetSuite allows you to sync data between the two systems. Before you start connecting Expensify with NetSuite, there are a few things to note: + +- You must use NetSuite administrator credentials to initiate the connection +- A Control Plan in Expensify is required to integrate with NetSuite +- Employees don’t need NetSuite access or a NetSuite license to submit expense reports and sync them to NetSuite +- Each NetSuite subsidiary must be connected to a separate Expensify workspace +- The workspace currency in Expensify must match the NetSuite subsidiary's default currency + +## Step 1: Install the Expensify Bundle in NetSuite +1. While logged into NetSuite as an administrator, go to **Customization > SuiteBundler > Search & Install Bundles**, then search for “Expensify” +2. Click on the Expensify Connect bundle (Bundle ID 283395) +3. Click **Install** +4. If you already have the Expensify Connect bundle installed, head to **Customization > SuiteBundler > Search & Install Bundles > List**, and update it to the latest version +5. Select "Show on Existing Custom Forms" for all available fields + +## Step 2: Enable Token-Based Authentication +1. In NetSuite, go to **Setup > Company > Enable Features > SuiteCloud > Manage Authentication** +2. Make sure “Token Based Authentication” is enabled +3. Click **Save** + + +## Step 3: Add Expensify Integration Role to a User +1. In NetSuite, head to **Lists > Employees**, and find the user who you would like to add the Expensify Integration role to. The user you select must at least have access to the permissions included in the Expensify Integration Role, and Admin access works too, but Admin access is not required. +2. Click **Edit > Access**, then find the Expensify Integration role in the dropdown and add it to the user +3. Click **Save** + +Remember that Tokens are linked to a User and a Role, not solely to a User. It’s important to note that you cannot establish a connection with tokens using one role and then switch to another role afterward. Once you’ve initiated a connection with tokens, you must continue using the same token/user/role combination for all subsequent sync or export actions. + +## Step 4: Create Access Tokens +1. In NetSuite, enter “page: tokens” in the Global Search +2. Click **New Access Token** +3. Select Expensify as the application (this must be the original Expensify integration from the bundle) +4. Select the role Expensify Integration +5. Click **Save** +6. Copy and paste the token and token ID to a saved location on your computer (this is the only time you will see these details) + +## Step 5: Confirm Expense Reports are enabled in NetSuite +Expense Reports must be enabled in order to use Expensify’s integration with NetSuite. + +1. In NetSuite, go to **Setup > Company > Enable Features > Employees** +2. Confirm the checkbox next to "Expense Reports" is checked +3. If not, click the checkbox and then click **Save** to enable Expense Reports + +## Step 6: Confirm Expense Categories are set up in NetSuite +Once Expense Reports are enabled, Expense Categories can be set up in NetSuite. Expense Categories are synced to Expensify as Categories. Each Expense Category is an alias mapped to a General Ledger account so that employees can more easily categorize expenses. + +1. In NetSuite, go to **Setup > Accounting > Expense Categories** (a list of Expense Categories should show) +2. If no Expense Categories are visible, click **New** to create new ones + +## Step 7: Confirm Journal Entry Transaction Forms are Configured Properly +1. In NetSuite, go to **Customization > Forms > Transaction Forms** +2. Click **Customize** or **Edit** next to the Standard Journal Entry form +3. Click **Screen Fields > Main**. Please verify the “Created From” label has “Show” checked and the "Display Type" is set to "Normal" +4. Click the sub-header **Lines** and verify that the “Show” column for “Receipt URL” is checked +5. Go to **Customization > Forms > Transaction Forms** and ensure that all other transaction forms with the journal type have this same configuration + +## Step 8: Confirm Expense Report Transaction Forms are Configured Properly +1. In NetSuite, go to **Customization > Forms > Transaction Forms** +2. Click **Customize** or **Edit** next to the Standard Expense Report form, then click **Screen Fields > Main** +3. Verify the “Created From” label has “Show” checked and the "Display Type" is set to "Normal" +4. Click the second sub-header, **Expenses**, and verify that the "Show" column for "Receipt URL" is checked +5. Go to **Customization > Forms > Transaction Forms** and ensure that all other transaction forms with the expense report type have this same configuration + +## Step 9: Confirm Vendor Bill Transactions Forms are Configured Properly +1. In NetSuite, go to **Customization > Forms > Transaction Forms** +2. Click **Customize** or **Edit** next to your preferred Vendor Bill form +3. Click **Screen Fields > Main** and verify that the “Created From” label has “Show” checked and that Departments, Classes, and Locations have the “Show” label unchecked +4. Under the **Expenses** sub-header (make sure to click the “Expenses” sub-header at the very bottom and not “Expenses & Items”), ensure “Show” is checked for Receipt URL, Department, Location, and Class +5. Go to **Customization > Forms > Transaction Forms** and ensure that all other transaction forms with the vendor bill type have this same configuration + +## Step 10: Confirm Vendor Credit Transactions Forms are Configured Properly +1. In NetSuite, go to **Customization > Forms > Transaction Forms** +2. Click **Customize** or **Edit** next to your preferred Vendor Credit form, then click **Screen Fields > Main** and verify that the “Created From” label has “Show” checked and that Departments, Classes, and Locations have the “Show” label unchecked +3. Under the **Expenses** sub-header (make sure to click the “Expenses” sub-header at the very bottom and not “Expenses & Items”), ensure “Show” is checked for Receipt URL, Department, Location, and Class +4. Go to **Customization > Forms > Transaction Forms** and ensure that all other transaction forms with the vendor credit type have this same configuration + +## Step 11: Set up Tax Groups (only applicable if tracking taxes) +Expensify imports NetSuite Tax Groups (not Tax Codes), which you can find in NetSuite under **Setup > Accounting > Tax Groups**. + +Tax Groups are an alias for Tax Codes in NetSuite and can contain one or more Tax Codes (Please note: for UK and Ireland subsidiaries, please ensure your Tax Groups do not have more than one Tax Code). We recommend naming Tax Groups so your employees can easily understand them, as the name and rate will be displayed in Expensify. + +To set up Tax Groups in NetSuite: + +1. Go to **Setup > Accounting > Tax Groups** +2. Click **New** +3. Select the country for your Tax Group +4. Enter the Tax Name (this is what employees will see in Expensify) +5. Select the subsidiary for this Tax Group +6. Select the Tax Code from the table you wish to include in this Tax Group +7. Click **Add** +8. Click **Save** +9. Create one NetSuite Tax Group for each tax rate you want to show in Expensify + +Ensure Tax Groups can be applied to expenses by going to **Setup > Accounting > Set Up Taxes** and setting the Tax Code Lists Include preference to “Tax Groups And Tax Codes” or “Tax Groups Only.” If this field does not display, it’s not needed for that specific country. + +## Step 12: Connect Expensify to NetSuite +1. Log into Expensify as a workspace admin +2. Click your profile image or icon in the bottom left menu +3. Scroll down and click **Workspaces** in the left menu +4. Select the workspace you want to connect to NetSuite +5. Click **More features** in the left menu +6. Scroll down to the Integrate section and enable Accounting +7. Click **Accounting** in the left menu +8. Click **Set up** next to NetSuite +9. Click **Next** until you reach setup step 5 (If you followed the instructions above, then the first four setup steps will be complete) +10. On setup step 5, enter your NetSuite Account ID, Token ID, and Token Secret (the NetSuite Account ID can be found in NetSuite by going to **Setup > Integration > Web Services Preferences**) +11. Click **Confirm** to complete the setup + +After completing the setup, the NetSuite connection will sync. It can take 1-2 minutes to sync with NetSuite. + +Once connected, all reports exported from Expensify will be generated in NetSuite using SOAP Web Services (the term NetSuite employs when records are created through the integration). + +## FAQ +### What type of Expensify plan is required to connect to NetSuite? +You need a Control workspace to integrate with NetSuite. If you have a Collect workspace, you will need to upgrade to Control. + +### Page size +Make sure your page size is set to 1000 in NetSuite for importing your customers and vendors. Go to **Setup > Integration > Web Services Preferences** and search **Page Size** to determine your page size. + + +# Configure NetSuite integration +## Step 1: Configure import settings + +The following section will help you determine how data will be imported from NetSuite into Expensify. To change your import settings, navigate to the Accounting settings for your workspace, then click **Import** under the NetSuite connection. + +### Expense Categories +Your NetSuite Expense Categories are automatically imported into Expensify as categories. This cannot be amended, and any new categories you'd like to add must be added as Expense Categories in NetSuite. + +Once imported, you can turn specific Categories on or off under **Settings > Workspaces > [Workspace Name] > Categories**. + +### Departments, Classes, and Locations +The NetSuite integration allows you to import departments, classes, and locations from NetSuite into Expensify as Tags, Report Fields, or using the NetSuite Employee Default. + +- **NetSuite Employee Default:** If default Department, Class, and Locations have been configured on NetSuite employee records, then you can choose to have the NetSuite employee default applied upon export from Expensify to NetSuite. With this selection, employees will not make a selection in Expensify. +- **Tags:** Employees can select the department, class, or location on each individual expense. If the employee's NetSuite employee record has a default value, then each expense will be defaulted to that tag upon creation, with the option for the employee to select a different value on each expense. +- **Report Fields:** Employees can select one department/class/location for each expense report. + + +New departments, classes, and locations must be added in NetSuite. Once imported, you can turn specific tags on or off under **Settings > Workspaces > [Workspace Name] > Tags**. You can turn specific report fields on or off under **Settings > Workspaces > [Workspace Name] > Report Fields**. + +### Customers and Projects +The NetSuite integration allows you to import customers and projects into Expensify as Tags or Report Fields. + +- **Tags:** Employees can select the customer or project on each individual expense. +- **Report Fields:** Employees can select one department/class/location for each expense report. + +New customers and projects must be added in NetSuite. Once imported, you can turn specific tags on or off under **Settings > Workspaces > [Workspace Name] > Tags**. You can turn specific report fields on or off under **Settings > Workspaces > [Workspace Name] > Report Fields**. + +When importing customers or projects, you can also choose to enable **Cross-subsidiary customers/projects**. This setting allows you to import Customers and Projects across all NetSuite subsidiaries to a single Expensify workspace. This setting requires you to enable “Intercompany Time and Expense” in NetSuite. To enable that feature in NetSuite, go to **Setup > Company > Setup Tasks: Enable Features > Advanced Features**. + +### Tax +The NetSuite integration allows users to apply a tax rate and amount to each expense for non-US NetSuite subsidiaries. To do this, import Tax Groups from NetSuite: + +1. In NetSuite, head to **Setup > Accounting > Tax Groups** +2. Once imported, go to the NetSuite connection configuration page in Expensify (under **Settings > Workspaces > [Workspace Name] > Accounting > NetSuite > Import**) +3. Enable Tax +4. Go back to the Accounting screen, click the three dots next to NetSuite, and click **Sync now** +5. All Tax Groups for the connected NetSuite subsidiary will be imported to Expensify as taxes. +6. After syncing, go to **Settings > Workspace > [Workspace Name] > Tax** to see the tax groups imported from NetSuite + +### Custom Segments +You can import one or more Custom Segments from NetSuite for selection in Expensify. To add a Custom Segment to your Expensify workspace: + +1. Go to **Settings > Workspaces > [Workspace Name] > Accounting** +2. Click **Import** under NetSuite +3. Click **Custom segments/records** +4. Click **Add custom segment/record** + +From there, you'll walk through a simple setup wizard. You can find detailed instructions below for each setup step. + +1. In Step 1, you'll select whether you'd like to import a custom segment or a custom record. For a Custom Segment, continue. We have separate instructions for [Custom Records](link) and [Custom Lists](link). +2. **Segment Name** + a. Log into NetSuite as an administrator + b. Go to **Customization > Lists, Records, & Fields > Custom Segments** + c. You’ll see the Segment Name on the Custom Segments page +3. Internal ID + a. Ensure you have internal IDs enabled in NetSuite under **Home > Set Preferences** + b. Navigate back to the **Custom Segments** page + c. Click the **Custom Record Type** link + d. You’ll see the Internal ID on the Custom Record Type page +4. **Script ID/Field ID** + a. If configuring Custom Segments as Report Fields, use the Field ID on the Transactions tab (under **Custom Segments > Transactions**). If no Field ID is shown, use the unified ID (just called “ID” right below the “Label”). + b. If configuring Custom Segments as Tags, use the Field ID on the Transaction Columns tab (under **Custom Segments > Transaction Columns**). If no Field ID is shown, use the unified ID (just called “ID” right below the “Label”). + c. Note that as of 2019.1, any new custom segments that you create automatically use the unified ID, and the "Use as Field ID" box is not visible. If you are editing a custom segment definition that was created before 2019.1, the "Use as Field ID" box is available. To use a unified ID for the entire custom segment definition, check the "Use as Field ID" box. When the box is checked, no field ID fields or columns are shown on the Application & Sourcing subtabs because one ID is used for all fields. +5. Select whether you'd like to import the custom segment as Tags or Report Fields +6. Finally, confirm that all the details look correct + +**Note:** Don’t use the “Filtered by” feature available for Custom Segments. Expensify can’t make these dependent on other fields. If you do have a filter selected, we suggest switching that filter in NetSuite to “Subsidiary” and enabling all subsidiaries to ensure you don’t receive any errors upon exporting reports. + +### Custom Records +You can import one or more Custom Records from NetSuite for selection in Expensify. To add a Custom Record to your Expensify workspace: + +1. Go to **Settings > Workspaces > [Workspace Name] > Accounting** +2. Click **Import** under NetSuite +3. Click **Custom segments/records** +4. Click **Add custom segment/record** + +From there, you'll walk through a simple setup wizard. You can find detailed instructions below for each setup step. + +1. In Step 1, you'll select whether you'd like to import a custom segment or a custom record. For a Custom Record, continue. We have separate instructions for [Custom Segments](link) and [Custom Lists](link). +2. **Segment Name** + a. Log into NetSuite as an administrator + b. Go to **Customization > Lists, Records, & Fields > Custom Segments** + c. You’ll see the Custom Record Name on the Custom Segments page +3. **Internal ID** + a. Make sure you have Internal IDs enabled in NetSuite under **Home > Set Preferences** + b. Navigate back to the **Custom Segment** page + c. Click the **Custom Record Type** hyperlink + d. You’ll see the Internal ID on the Custom Record Type page +4. **Transaction Column ID** + a. If configuring Custom Records as Report Fields, use the Field ID on the Transactions tab (under **Custom Segments > Transactions**). + b. If configuring Custom Records as Tags, use the Field ID on the Transaction Columns tab (under **Custom Segments > Transaction Columns**). +5. Select whether you'd like to import the custom record as Tags or Report Fields +6. Finally, confirm that all the details look correct + +### Custom Lists +You can import one or more Custom Lists from NetSuite for selection in Expensify. To add a Custom List to your Expensify workspace: + +1. Go to **Settings > Workspaces > [Workspace Name] > Accounting** +2. Click **Import** under NetSuite +3. Click **Custom list** +4. Click **Add custom list** + +From there, you'll walk through a simple setup wizard. You can find detailed instructions below for each setup step. + +1. In Step 1, you'll select which Custom List you'd like to import from a pre-populated list +2. **Transaction Line Field ID** + a. Log into NetSuite as an admin + b. Search **“Transaction Line Fields”** in the global search + c. Click into the desired Custom List + d. You'll find the transaction Line Field ID along the left-hand side of the page +3. Select whether you'd like to import the custom list as Tags or Report Fields +4. Finally, confirm that all the details look correct + +From there, you should see the values for the Custom Lists under the Tag or Report Field settings in Expensify. +## Step 2: Configure export settings +There are numerous options for exporting data from Expensify to NetSuite. To access these settings, head to **Settings > Workspaces > [Workspace name] > Accounting** and click **Export** under NetSuite. + +### Preferred Exporter +Any workspace admin can export reports to NetSuite. For auto-export, Concierge will export on behalf of the preferred exporter. The preferred exporter will also be notified of any expense reports that fail to export to NetSuite due to an error. + +### Date +You can choose which date to use for the records created in NetSuite. There are three date options: + +1. **Date of last expense:** This will use the date of the previous expense on the report +2. **Submitted date:** The date the employee submitted the report +3. **Exported date:** The date you export the report to NetSuite + +### Export out-of-pocket expenses as +**Expense Reports** +Out-of-pocket expenses will be exported to NetSuite as expense reports, which will be posted to the payables account designated in NetSuite. + +**Vendor Bills** +Out-of-pocket expenses will be exported to NetSuite as vendor bills. Each report will be posted as payable to the vendor associated with the employee who submitted the report. You can also set an approval level in NetSuite for vendor bills. + +**Journal Entries** +Out-of-pocket expenses will be exported to NetSuite as journal entries. All the transactions will be posted to the payable account specified in the workspace. You can also set an approval level in NetSuite for the journal entries. + +Note: By default, journal entry forms do not contain a customer column, so it is not possible to export customers or projects with this export option. Also, The credit line and header level classifications are pulled from the employee record. + +### Export company card expenses as +**Expense Reports** +To export company card expenses as expense reports, you will need to configure your default corporate cards in NetSuite. To do this, you must select the correct card on the NetSuite employee records (for individual accounts) or the subsidiary record (If you use a non-One World account, the default is found in your accounting preferences). + +To update your expense report transaction form in NetSuite: + +1. Go to **Customization > Forms > Transaction Forms** +2. Click **Edit** next to the preferred expense report form +3. Go to the **Screen Fields > Main** tab +4. Check “Show” for "Account for Corporate Card Expenses" +5. Go to the **Screen Fields > Expenses** tab +6. Check “Show” for "Corporate Card" + +You can also select the default account on your employee record to use individual corporate cards for each employee. Make sure you add this field to your employee entity form in NetSuite. If you have multiple cards assigned to a single employee, you cannot export to each account. You can only have a single default per employee record. + +**Vendor Bills** +Company card expenses will be posted as a vendor bill payable to the default vendor specified in your workspace Accounting settings. You can also set an approval level in NetSuite for the bills. + + +**Journal Entries** +Company Card expenses will be posted to the Journal Entries posting account selected in your workspace Accounting settings. + +Important Notes: + +- Expensify Card expenses will always export as Journal Entries, even if you have Expense Reports or Vendor Bills configured for non-reimbursable expenses on the Export tab +- Journal entry forms do not contain a customer column, so it is not possible to export customers or projects with this export option +- The credit line and header level classifications are pulled from the employee record + +### Export invoices to +Select the Accounts Receivable account where you want your Invoice reports to export. In NetSuite, the invoices are linked to the customer, corresponding to the email address where the invoice was sent. + +### Export foreign currency amount +Enabling this feature allows you to send the original amount of the expense rather than the converted total when exporting to NetSuite. This option is only available when exporting out-of-pocket expenses as Expense Reports. + +### Export to next open period +When this feature is enabled and you try exporting an expense report to a closed NetSuite period, we will automatically export to the next open period instead of returning an error. + + +## Step 3: Configure advanced settings +To access the advanced settings of the NetSuite integration, head to **Settings > Workspaces > [Workspace name] > Accounting** and click **Advanced** under NetSuite. + + +Let’s review the different advanced settings and how they interact with the integration. + +### Auto-sync +We strongly recommend enabling auto-sync to ensure that the information in NetSuite and Expensify is always in sync. The following will occur when auto-sync is enabled: + +**Daily sync from NetSuite to Expensify:** Once a day, Expensify will sync any changes from NetSuite into Expensify. This includes any new, updated, or removed departments/classes/locations/projects/etc. + +**Auto-export:** When an expense report reaches its final state in Expensify, it will be automatically exported to NetSuite. The final state will either be reimbursement (if you reimburse members through Expensify) or final approval (if you reimburse members outside of Expensify). + +**Reimbursement-sync:** If Sync Reimbursed Reports (more details below) is enabled, then we will sync the reimbursement status of reports between Expensify and NetSuite. + +### Sync reimbursed reports +When Sync reimbursed reports is enabled, the reimbursement status will be synced between Expensify and NetSuite. + +**If you reimburse members through Expensify:** Reimbursing an expense report will trigger auto-export to NetSuite. When the expense report is exported to NetSuite, a corresponding bill payment will also be created in NetSuite. + +**If you reimburse members outside of Expensify:** Expense reports will be exported to NetSuite at time of final approval. After you mark the report as paid in NetSuite, the reimbursed status will be synced back to Expensify the next time the integration syncs. + +To ensure this feature works properly for expense reports, make sure that the reimbursement account you choose within the settings matches the default account for Bill Payments in NetSuite. When exporting invoices, once marked as Paid, the payment is marked against the account selected after enabling the Collection Account setting. + +### Invite employees and set approvals +Enabling this feature will invite all employees from the connected NetSuite subsidiary to your Expensify workspace. Once imported, Expensify will send them an email letting them know they’ve been added to a workspace. + +In addition to inviting employees, this feature enables a custom set of approval workflow options, which you can manage in Expensify Classic: + +- **Basic Approval:** A single level of approval, where all users submit directly to a Final Approver. The Final Approver defaults to the workspace owner but can be edited on the people page. +- **Manager Approval (default):** Two levels of approval route reports first to an employee’s NetSuite expense approver or supervisor, and second to a workspace-wide Final Approver. By NetSuite convention, Expensify will map to the supervisor if no expense approver exists. The Final Approver defaults to the workspace owner but can be edited on the people page. +- **Configure Manually:** Employees will be imported, but all levels of approval must be manually configured on the workspace’s People settings page. If you enable this setting, it’s recommended you review the newly imported employees and managers on the **Settings > Workspaces > Group > [Workspace Name] > People** page. + +### Auto-create employees/vendors +With this feature enabled, Expensify will automatically create a new employee or vendor in NetSuite (if one doesn’t already exist) using the name and email of the report submitter. + +### Enable newly imported categories +With this feature enabled, anytime a new Expense Category is created in NetSuite, it will be imported into Expensify as an enabled category. If the feature is disabled, then new Expense Categories will be imported into Expensify as disabled. + +### Setting approval levels +You can set the NetSuite approval level for each different export type: + +- **Expense report approval level:** Choose from "NetSuite default preference," “Only supervisor approved,” “Only accounting approved,” or “Supervisor and accounting approved.” +- **Vendor bill approval level and Journal entry approval level:** Choose from "NetSuite default preference," “Pending approval,” or “Approved for posting.” + +If you have Approval Routing selected in your accounting preference, this will override the selections in Expensify. If you do not wish to use Approval Routing in NetSuite, go to **Setup > Accounting > Accounting Preferences > Approval Routing** and ensure Vendor Bills and Journal Entries are not selected. + +### Custom form ID +By default, Expensify will create entries using the preferred transaction form set in NetSuite. Alternatively, you have the option to designate a specific transaction form to be used. + + + +## FAQ + +### How does Auto-sync work with reimbursed reports? +If a report is reimbursed via ACH or marked as reimbursed in Expensify and then exported to NetSuite, the report is automatically marked as paid in NetSuite. + +If a report is exported to NetSuite, then marked as paid in NetSuite, the report will automatically be marked as reimbursed in Expensify during the next sync. + +### Will enabling auto-sync affect existing approved and reimbursed reports? +Auto-sync will only export newly approved reports to NetSuite. Any reports that were approved or reimbursed before enabling auto-sync will need to be manually exported in order to sync them to NetSuite. + + +### When using multi-currency features in NetSuite, can expenses be exported with any currency? +When using multi-currency features with NetSuite, remember these points: + +**Employee/Vendor currency:** The currency set for a NetSuite vendor or employee record must match the subsidiary currency for whichever subsidiary you export that user's reports to. A currency mismatch will cause export errors. +**Bank Account Currency:** When synchronizing bill payments, your bank account’s currency must match the subsidiary’s currency. Failure to do so will result in an “Invalid Account” error. diff --git a/docs/articles/new-expensify/connections/Set-Up-Sage-Intacct-connection.md b/docs/articles/new-expensify/connections/Set-Up-Sage-Intacct-connection.md new file mode 100644 index 000000000000..1f5d9662bb4f --- /dev/null +++ b/docs/articles/new-expensify/connections/Set-Up-Sage-Intacct-connection.md @@ -0,0 +1,317 @@ +--- +title: Set up Sage Intacct connection +description: Integrate Sage Intacct with Expensify +--- +
      + +# Connect to Sage Intacct + +## Overview +Expensify’s integration with Sage Intacct allows you to connect using either role-based permissions or user-based permissions and exporting either expense reports or vendor bills. + +Checklist of items to complete: + +1. Create a web services user and configure permissions +1. Enable the T&E module (only required if exporting out-of-pocket expenses as Expense Reports) +1. Set up Employees in Sage Intacct (only required if exporting expenses as Expense Reports) +1. Set up Expense Types (only required if exporting expenses as Expense Reports) +1. Enable Customization Services +1. Download the Expensify Package +1. Upload the Expensify Package in Sage Intacct +1. Add web services authorization +1. Enter credentials and connect Expensify and Sage Intacct +1. Configure integration sync options + +## Step 1a: Create a web services user (Connecting with User-based permissions) +Note: If the steps in this section look different in your Sage Intacct instance, you likely use role-based permissions. If that’s the case, follow the steps [here]. + +To connect to Sage Intacct, you’ll need to create a special web services user (please note that Sage Intacct does not charge extra for web services users). + +1. Go to **Company > Web Services Users > New**. +2. Configure the user as outlined below: + - **User ID**: “xmlgateway_expensify” + - **Last Name and First Name:** “Expensify” + - **Email Address:** Your shared accounting team email + - **User Type:** “Business” + - **Admin Privileges:** “Full” + - **Status:** “Active” + +Next, configure correct permissions for the new web services user. + +1. Go to the subscription link for this user in the user list +1. Click on the checkbox next to the Application/Module +1. Click **Permissions** + +These are the permissions required for this integration when exporting out-of-pocket expenses as vendor bills: + +- **Administration (All)** +- **Company (Read-only)** +- **Cash Management (All)** +- **General Ledger (All)** +- **Time & Expense (All)** - Only required if exporting out-of-pocket expenses as expense reports +- **Projects (Read-only)** - Only required if using Projects or Customers +- **Accounts Payable (All)** - Only required if exporting any expenses expenses as vendor bills + +## Step 1b: Create a web services user (Connecting with Role-based permissions) +Note: If the steps in this section look different in your Sage Intacct instance, you likely use role-based permissions. If that’s the case, follow the steps [here]. + +**First, you'll need to create the new role:** + +1. In Sage Intacct, click **Company**, then click on the **+ button** next to Roles +1. Name the role "Expensify", then click **Save** +1. Go to **Roles > Subscriptions** and find the “Expensify” role you just created +1. Configure correct permissions for this role by clicking the checkbox and then clicking on the Permissions hyperlink. These are the permissions required for this integration when exporting out-of-pocket expenses as vendor bills: + - **Administration (All)** + - **Company (Read-only)** + - **Cash Management (All)** + - **General Ledger (All)** + - **Time & Expense (All)** - Only required if exporting out-of-pocket expenses as expense reports + - **Projects (Read-only)** - Only required if using Projects or Customers + - **Accounts Payable (All)** - Only required if exporting any expenses expenses as vendor bills + +**Next, you’ll create a web services user and assign the role to that user:** + +1. Go to **Company > Web Services Users > New** +2. Set up the user as described below: + - **User ID:** “xmlgateway_expensify” + - **Last name and First name:** “Expensify” + - **Email address:** your shared accounting team email + - **User type:** “Business” + - **Admin privileges:** “Full” + - **Status:** “Active” +3. Assign the role to that user: click the **+ button**, then select the “Expensify” role and click **Save** + + +## Step 2: Enable and configure the Time & Expenses Module +**Note: This step is only required if exporting out-of-pocket expenses from Expensify to Sage Intacct as Expense Reports.** + +Enabling the T&E module is a paid subscription through Sage Intacct and the T&E module is often included in your Sage Intacct instance. For information on the costs of enabling this module, please contact your Sage Intacct account manager. + +**To enable the Time & Expenses module:** + +In Sage Intacct, go to **Company menu > Subscriptions > Time & Expenses** and toggle the switch to subscribe. + +**After enabling T&E, configure it as follows:** + +1. Ensure that **Expense types** is checked +2. Under **Auto-numbering sequences** set the following: + - **Expense Report:** EXP + - **Employee:** EMP + - **Duplicate Numbers:** Select “Do not allow creation” + - To create the EXP sequence, click on the down arrow on the expense report line and select **Add: + 1. **Sequence ID:** EXP + 1. **Print Title:** EXPENSE REPORT + 1. **Starting Number:** 1 + 1. **Next Number:** 2 +3. Select **Advanced Settings** and configure the following: + a. **Fixed Number Length:** 4 + b. **Fixed Prefix:** EXP +4. Click **Save** +5. Under Expense Report approval settings, ensure that **Enable expense report approval** is unchecked +6. Click **Save** to confirm your configuration + + +## Step 3: Set up Employees in Sage Intacct +**Note: This step is only required if exporting out-of-pocket expenses from Expensify to Sage Intacct as Expense Reports.** + +To set up employees in Sage Intacct: + +1. Navigate to Time & Expenses and click the **plus button** next to Employees. + - If you don’t see the Time & Expense option in the top ribbon, you may need to adjust your settings. Go to **Company > Roles > Time & Expenses** and enable all permissions. +2. To create an employee, you’ll need to provide the following information: + - Employee ID + - Primary contact name + - Email address + 1. In the "Primary contact name" field, click the dropdown arrow. + 1. Select the employee if they’ve already been created. + 1. Otherwise, click **+ Add** to create a new employee. + 1. Fill in their Primary Email Address along with any other required information + + +## Step 4: Set up Expense Types in Sage Intacct +**Note: This step is only required if exporting out-of-pocket expenses from Expensify to Sage Intacct as Expense Reports.** + +Expense Types provide a user-friendly way to display the names of your expense accounts to your employees. To set up expense types in Sage Intacct: + +1. **Setup Your Chart of Accounts** + - Before configuring Expense Types, ensure your Chart of Accounts is set up. You can set up accounts in bulk by going to **Company > Open Setup > Company Setup Checklist** and clicking **Import**. +2. **Set up Expense Types** + - Go to Time & Expense + - Open Setup and click the **plus button** next to Expense Types +3. For each Expense Type, provide the following information: + - **Expense Type** + - **Description** + - **Account Number** (from your General Ledger) + +## Step 5: Enable Customization Services +**Note:** If you already have Platform Services enabled, you can skip this step. + +To enable Customization Services, go to **Company > Subscriptions > Customization Services**. + + +## Step 6: Download the Expensify Package +1. In Expensify, go to Settings > Workspaces +1. Click into the workspace where you'd like to connect to Sage Intacct + - If you already use Expensify, you can optionally create a test workspace by clicking **New Workspace** at the top-right of the Workspaces page. A test workspace allows you to have a sandbox environment for testing before implementing the integration live. +1. Go to **Connections > Sage Intacct > Connect to Sage Intacct** +1. Select **Download Package** (You only need to download the file; we’ll upload it from your Downloads folder later) + +## Step 7: Upload Package in Sage Intacct +If you use Customization Services: + +1. Go to **Customization Services > Custom Packages > New Package** +1. Click on **Choose File** and select the Package file from your downloads folder +1. Click **Import** + +If you use Platform Services: + +1. Go to **Platform Services > Custom Packages > New Package** +1. Click on **Choose File** and select the Package file from your downloads folder +1. Click **Import** + + +## Step 8: Add Web Services Authorization +1. Go to **Company > Company Info > Security** in Sage Intacct and click **Edit** +2. Scroll down to **Web Services Authorizations** and add “expensify” (all lower case) as a Sender ID + +## Step 9: Enter Credentials and Connect Expensify and Sage Intacct +1. In Expensify, go to **Settings > Workspaces > [Workspace Name] > Accounting** +1. Click **Set up** next to Sage Intacct +1. Enter the credentials you set for your web services user in Step 1 +1. Click **Confirm** + + + +# Configure Sage Intacct integration + +## Step 1: Select entity (multi-entity setups only) +If you have a multi-entity setup in Sage Intacct, you will be able to select in Expensify which Sage Intacct entity to connect each workspace to. Each Expensify workspace can either be connected to a single entity or connected at the Top Level. + +To select or change the Sage Intacct entity that your Expensify workspace is connected to, navigate to the Accounting settings for your workspace and click **Entity** under the Sage Intacct connection. + +## Step 2: Configure import settings +The following section will help you determine how data will be imported from Sage Intacct into Expensify. To change your import settings, navigate to the Accounting settings for your workspace, then click **Import** under the Sage Intacct connection. + +### Expense Types / Chart of Accounts +The categories in Expensify depend on how you choose to export out-of-pocket expenses: + +- If you choose to export out-of-pocket expenses as Expense Reports, your categories in Expensify will be imported from your Sage Intacct Expense Types +- If you choose to export out-of-pocket expenses as Vendor Bills, your categories will be imported directly from your Chart of Accounts (also known as GL Codes or Account Codes). + +You can disable unnecessary categories in Expensify by going to **Settings > Workspaces > [Workspace Name] > Categories**. Note that every expense must be coded with a Category, or it will fail to export. + +### Billable Expenses +Enabling billable expenses allows you to map your expense types or accounts to items in Sage Intacct. To do this, you’ll need to enable the correct permissions on your Sage Intacct user or role. This may vary based on the modules you use in Sage Intacct, so you should enable read-only permissions for relevant modules such as Projects, Purchasing, Inventory Control, and Order Entry. + +Once permissions are set, you can map categories to specific items, which will then export to Sage Intacct. When an expense is marked as Billable in Expensify, users must select the correct billable Category (Item), or there will be an error during export. + + +### Standard dimensions: Departments, Classes, and Locations +The Sage Intacct integration allows you to import standard dimensions into Expensify as tags, report fields, or using the Sage Intacct employee default. + +- **Sage Intacct Employee default:** This option is only available when exporting as expense reports. When this option is selected, nothing will be imported into Expensify - instead, the employee default will be applied to each expense upon export. +- **Tags:** Employees can select the department, class, or location on each individual expense. If the employee's Sage Intacct employee record has a default value, then each expense will default to that tag, with the option for the employee to select a different value on each expense. +- **Report Fields:** Employees can select one department/class/location for each expense report. + +New departments, classes, and locations must be added in Sage Intacct. Once imported, you can turn specific tags on or off under **Settings > Workspaces > [Workspace Name] > Tags**. You can turn specific report fields on or off under **Settings > Workspaces > [Workspace Name] > Report Fields**. + +Please note that when importing departments as tags, expense reports may show the tag name as "Tag" instead of "Department." + +### Customers and Projects +The Sage Intacct integration allows you to import customers and projects into Expensify as Tags or Report Fields. + +- **Tags:** Employees can select the customer or project on each individual expense. +- **Report Fields:** Employees can select one department/class/location for each expense report. + +New customers and projects must be added in Sage Intacct. Once imported, you can turn specific tags on or off under **Settings > Workspaces > [Workspace Name] > Tags**. You can turn specific report fields on or off under **Settings > Workspaces > [Workspace Name] > Report Fields**. + + +### Tax +The Sage Intacct integration supports native VAT and GST tax. To enable this feature, go to **Settings > Workspaces > [Workspace Name] > Accounting**, click **Import** under Sage Intacct, and enable Tax. Enabling this option will import your native tax rates from Sage Intacct into Expensify. From there, you can select default rates for each category under **Settings > Workspaces > [Workspace Name] > Categories**. + +For older Sage Intacct connections that don't show the Tax option, simply resync the connection by going to **Settings > Workspaces > [Workspace Name] > Accounting** and clicking the three dots next to Sage Intacct, and the tax toggle will appear. + +### User-Defined Dimensions +You can add User-Defined Dimensions (UDDs) to your workspace by locating the “Integration Name” in Sage Intacct. Please note that you must be logged in as an administrator in Sage Intacct to find the required fields. + +To find the Integration Name in Sage Intacct: + +1. Go to **Platform Services > Objects > List** +1. Set “filter by application” to “user-defined dimensions” +1. In Expensify, go to **Settings > Workspaces > [Workspace Name] > Accounting** and click **Import** under Sage Intacct +1. Enable User Defined Dimensions +1. Enter the “Integration name” and choose whether to import it into Expensify as expense-level tags or as report fields +1. Click **Save** + +Once imported, you can turn specific tags on or off under **Settings > Workspaces > [Workspace Name] > Tags**. You can turn specific report fields on or off under **Settings > Workspaces > [Workspace Name] > Report Fields**. + + +## Step 5: Configure export settings +To access export settings, head to **Settings > Workspaces > [Workspace name] > Accounting** and click **Export** under Sage Intacct. + +### Preferred exporter +Any workspace admin can export reports to Sage Intacct. For auto-export, Concierge will export on behalf of the preferred exporter. The preferred exporter will also be notified of any expense reports that fail to export to Sage Intacct due to an error. + +### Export date +You can choose which date to use for the records created in Sage Intacct. There are three date options: + +1. **Date of last expense:** This will use the date of the previous expense on the report +1. **Export date:** The date you export the report to Sage Intacct +1. **Submitted date:** The date the employee submitted the report + +### Export out-of-pocket expenses as +Out-of-pocket expenses can be exported to Sage Intacct as **expense reports** or as **vendor bills**. If you choose to export as expense reports, you can optionally select a **default vendor**, which will apply to reimbursable expenses that don't have a matching vendor in Sage Intacct. + +### Export company card expenses as +Company Card expenses are exported separately from out-of-pocket expenses, and can be exported to Sage Intacct as credit card charges** or as **vendor bills**. + +- **Credit card charges:** When exporting as credit card charges, you must select a credit card account. You can optionally select a default vendor, which will apply to company card expenses that don't have a matching vendor in Sage Intacct. + - Credit card charges cannot be exported to Sage Intacct at the top-level if you have multi-currency enabled, so you will need to select an individual entity if you have this setup. +- **Vendor bills:** When exporting as vendor bills, you can select a default vendor, which will apply to company card expenses that don't have a matching vendor in Sage Intacct. + +If you centrally manage your company cards through Domains in Expensify Classic, you can export expenses from each individual card to a specific account in Sage Intacct in the Expensify Company Card settings. + +### 6. Configure advanced settings +To access the advanced settings of the Sage Intacct integration, head to **Settings > Workspaces > [Workspace name] > Accounting** and click **Advanced** under Sage Intacct. + + +Let’s review the different advanced settings and how they interact with the integration. + +### Auto-sync +We strongly recommend enabling auto-sync to ensure that the information in Sage Intacct and Expensify is always in sync. The following will occur when auto-sync is enabled: + +**Daily sync from Sage Intacct to Expensify:** Once a day, Expensify will sync any changes from Sage Intacct into Expensify. This includes any changes or additions to your Sage Intacct dimensions. + +**Auto-export:** When an expense report reaches its final state in Expensify, it will be automatically exported to Sage Intacct. The final state will either be reimbursement (if you reimburse members through Expensify) or final approval (if you reimburse members outside of Expensify). + +**Reimbursement-sync:** If Sync Reimbursed Reports (more details below) is enabled, then we will sync the reimbursement status of reports between Expensify and Sage Intacct. + +### Invite employees +Enabling this feature will invite all employees from the connected Sage Intacct entity to your Expensify workspace. Once imported, each employee who has not already been invited to that Expensify workspace will receive an email letting them know they’ve been added to the workspace. + +In addition to inviting employees, this feature enables a custom set of approval workflow options, which you can manage in Expensify Classic: + +- **Basic Approval:** A single level of approval, where all users submit directly to a Final Approver. The Final Approver defaults to the workspace owner but can be edited on the people page. +- **Manager Approval (default):** Each user submits to their manager, who is imported from Sage Intacct. You can optionally select a final approver who each manager forwards to for second-level approval. +- **Configure Manually:** Employees will be imported, but all levels of approval must be manually configured in Expensify. If you enable this setting, you can configure approvals by going to **Settings > Workspaces > [Workspace Name] > People**. + + +### Sync reimbursed reports +When Sync reimbursed reports is enabled, the reimbursement status will be synced between Expensify and Sage Intacct. + +**If you reimburse employees through Expensify:** Reimbursing an expense report will trigger auto-export to Sage Intacct. When the expense report is exported to Sage Intacct, a corresponding bill payment will also be created in Sage Intacct in the selected Cash and Cash Equivalents account. If you don't see the account you'd like to select in the dropdown list, please confirm that the account type is Cash and Cash Equivalents. + +**If you reimburse employees outside of Expensify:** Expense reports will be exported to Sage Intacct at time of final approval. After you mark the report as paid in Sage Intacct, the reimbursed status will be synced back to Expensify the next time the integration syncs. + +To ensure this feature works properly for expense reports, make sure that the account you choose within the settings matches the default account for Bill Payments in NetSuite. When exporting invoices, once marked as Paid, the payment is marked against the account selected after enabling the Collection Account setting. +## FAQ + +### Why wasn't my report automatically exported to Sage Intacct? +There are a number of factors that can cause auto-export to fail. If this happens, you will find the specific export error in the report comments for the report that failed to export. Once you’ve resolved any errors, you can manually export the report to Sage Intacct. + +### Will enabling auto-sync affect existing approved and reimbursed reports? +Auto-sync will only export newly approved reports to Sage Intacct. Any reports that were approved or reimbursed before enabling auto-sync will need to be manually exported in order to sync them to Sage Intacct. + + +### Can I export negative expenses to Sage Intacct? +Yes, you can export negative expenses to Sage Intacct. If you are exporting out-of-pocket expenses as expense reports, then the total of each exported report cannot be negative. 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 155512866a8f..79d5b17055f7 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 @@ -52,6 +52,12 @@ Log in to QuickBooks Online and ensure all of your employees are setup as either ![The QuickBooks Online Connect button]({{site.url}}/assets/images/ExpensifyHelp-QBO-3.png){:width="100%"} +![The QuickBooks Online Connect Accounting button]({{site.url}}/assets/images/ExpensifyHelp-QBO-4.png){:width="100%"} + +![The QuickBooks Online Connect Connect button]({{site.url}}/assets/images/ExpensifyHelp-QBO-5.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/expenses-&-payments/Duplicate-detection.md b/docs/articles/new-expensify/expenses-&-payments/Duplicate-detection.md new file mode 100644 index 000000000000..9fab52bca76a --- /dev/null +++ b/docs/articles/new-expensify/expenses-&-payments/Duplicate-detection.md @@ -0,0 +1,73 @@ +--- +title: Manage duplicate expenses +description: Identify and manage duplicate expense requests +--- + +
      + +Duplicate Detection helps prevent duplicate expense requests by flagging expense requests that have the same date and amount as another request in the same member's account. + +When an expense has been flagged as a potential duplicate, a red dot appears in the left menu or the expense’s chat room, and it is put on “hold.” + +{% include info.html %} +This feature is available exclusively for Collect & Control plans. +{% include end-info.html %} + +# Review & resolve duplicates + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the red dot in the left menu or open the expense’s chat room to open the flagged request. +2. Click the green **Review duplicates** button at the top of the request. +3. Review the list of potential duplicates. +4. To resolve a duplicate, click either **Keep all** or **Keep this one**. + - **Keep all**: Keeps all expenses as their separate charges and removes the hold. + - **Keep this one**: Keeps this expense and discards its other related duplicates. +5. If discrepancies exist between the duplicates (e.g., category, tags), choose which details to keep. +6. Confirm your selection to merge the requests or keep all. + +The expenses are removed from the duplicates list and the hold is removed. + +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the red dot in the left menu or open the expense’s chat room to open the flagged request. +2. Tap the green **Review duplicates** button at the top of the request. +3. Review the list of potential duplicates. +4. To resolve a duplicate, tap either **Keep all** or **Keep this one**. + - **Keep all**: Keeps all expenses as their separate charges and removes the hold. + - **Keep this one**: Keeps this expense and discards its other related duplicates. +5. If discrepancies exist between the duplicates (e.g., category, tags), choose which details to keep. +6. Confirm your selection to merge the requests or keep all. + +The expenses are removed from the duplicates list and the hold is removed. +{% include end-option.html %} + +{% include end-selector.html %} + + + + +{% include faq-begin.md %} +**Can I review a discarded duplicate later?** + +Yes, approvers can review discarded duplicates to ensure accuracy and prevent fraud. + +**Can I edit a duplicate request once resolved?** + +Yes, you can edit the details of a duplicate request once it has been resolved, but the hold must be removed first. + +**If two expenses are SmartScanned on the same day for the same amount, will they be flagged as duplicates?** + +Yes, the expenses will be flagged as duplicates unless one of the following is true: +- The expenses were split from a single expense +- The expenses were imported from a credit card +- Matching email receipts sent to receipts@expensify.com were received with different timestamps + +**What happens if Concierge flags a receipt as a duplicate?** + +If Concierge lets you know it has flagged a receipt as a duplicate, scanning the receipt again will trigger the same duplicate flagging. You can still find these in the [deleted](https://www.expensify.com/expenses?reportStatusList=Deleted) filter on Expensify Classic. +{% include faq-end.md %} + +
      diff --git a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md index 588f0da20154..6546f57073ee 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md +++ b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md @@ -38,6 +38,9 @@ Once an invoice is sent, the customer receives an automated email or text messag 4. Click **Pay Elsewhere**, which will mark the invoice as Paid. Currently, invoices must be paid outside of Expensify. However, the ability to make payments through Expensify is coming soon. + +![A photo of the pay button]({{site.url}}/assets/images/ExpensifyHelp-Invoice-1.png){:width="100%"} + # FAQs diff --git a/docs/articles/new-expensify/travel/Expensify-Travel-demo-video.md b/docs/articles/new-expensify/travel/Expensify-Travel-demo-video.md new file mode 100644 index 000000000000..ceb40254c607 --- /dev/null +++ b/docs/articles/new-expensify/travel/Expensify-Travel-demo-video.md @@ -0,0 +1,8 @@ +--- +title: Expensify Travel demo video +description: Check out a demo of Expensify Travel +--- + +Check out a video of how Expensify Travel works below: + + diff --git a/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_01.png b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_01.png new file mode 100644 index 000000000000..e7332f65e832 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_01.png differ diff --git a/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_02.png b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_02.png new file mode 100644 index 000000000000..b5c176e829c9 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_02.png differ diff --git a/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_03.png b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_03.png new file mode 100644 index 000000000000..ffed1ff6ade2 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_03.png differ diff --git a/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_04.png b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_04.png new file mode 100644 index 000000000000..88cb7d2704a8 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_04.png differ diff --git a/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_05.png b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_05.png new file mode 100644 index 000000000000..2175697461dc Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_05.png differ diff --git a/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_06.png b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_06.png new file mode 100644 index 000000000000..cce21e52e185 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_06.png differ diff --git a/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_07.png b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_07.png new file mode 100644 index 000000000000..5e9297f0c100 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-WorkspaceFeeds_07.png differ diff --git a/docs/expensify-classic/hubs/connect-credit-cards/business-bank-accounts.html b/docs/expensify-classic/hubs/bank-accounts-and-payments/payments.html similarity index 100% rename from docs/expensify-classic/hubs/connect-credit-cards/business-bank-accounts.html rename to docs/expensify-classic/hubs/bank-accounts-and-payments/payments.html diff --git a/docs/expensify-classic/hubs/connect-credit-cards/deposit-accounts.html b/docs/expensify-classic/hubs/connections/accelo.html similarity index 100% rename from docs/expensify-classic/hubs/connect-credit-cards/deposit-accounts.html rename to docs/expensify-classic/hubs/connections/accelo.html diff --git a/docs/expensify-classic/hubs/expenses/expenses.html b/docs/expensify-classic/hubs/connections/certinia.html similarity index 100% rename from docs/expensify-classic/hubs/expenses/expenses.html rename to docs/expensify-classic/hubs/connections/certinia.html diff --git a/docs/expensify-classic/hubs/integrations/index.html b/docs/expensify-classic/hubs/connections/index.html similarity index 100% rename from docs/expensify-classic/hubs/integrations/index.html rename to docs/expensify-classic/hubs/connections/index.html diff --git a/docs/expensify-classic/hubs/expenses/reports.html b/docs/expensify-classic/hubs/connections/netsuite.html similarity index 100% rename from docs/expensify-classic/hubs/expenses/reports.html rename to docs/expensify-classic/hubs/connections/netsuite.html diff --git a/docs/expensify-classic/hubs/getting-started/approved-accountants.html b/docs/expensify-classic/hubs/connections/quickbooks-desktop.html similarity index 100% rename from docs/expensify-classic/hubs/getting-started/approved-accountants.html rename to docs/expensify-classic/hubs/connections/quickbooks-desktop.html diff --git a/docs/expensify-classic/hubs/getting-started/support.html b/docs/expensify-classic/hubs/connections/quickbooks-online.html similarity index 100% rename from docs/expensify-classic/hubs/getting-started/support.html rename to docs/expensify-classic/hubs/connections/quickbooks-online.html diff --git a/docs/expensify-classic/hubs/getting-started/tips-and-tricks.html b/docs/expensify-classic/hubs/connections/sage-intacct.html similarity index 100% rename from docs/expensify-classic/hubs/getting-started/tips-and-tricks.html rename to docs/expensify-classic/hubs/connections/sage-intacct.html diff --git a/docs/expensify-classic/hubs/integrations/HR-integrations.html b/docs/expensify-classic/hubs/connections/xero.html similarity index 100% rename from docs/expensify-classic/hubs/integrations/HR-integrations.html rename to docs/expensify-classic/hubs/connections/xero.html diff --git a/docs/expensify-classic/hubs/integrations/accounting-integrations.html b/docs/expensify-classic/hubs/integrations/accounting-integrations.html deleted file mode 100644 index 86641ee60b7d..000000000000 --- a/docs/expensify-classic/hubs/integrations/accounting-integrations.html +++ /dev/null @@ -1,5 +0,0 @@ ---- -layout: default ---- - -{% include section.html %} diff --git a/docs/expensify-classic/hubs/integrations/other-integrations.html b/docs/expensify-classic/hubs/integrations/other-integrations.html deleted file mode 100644 index 86641ee60b7d..000000000000 --- a/docs/expensify-classic/hubs/integrations/other-integrations.html +++ /dev/null @@ -1,5 +0,0 @@ ---- -layout: default ---- - -{% include section.html %} diff --git a/docs/expensify-classic/hubs/integrations/travel-integrations.html b/docs/expensify-classic/hubs/integrations/travel-integrations.html deleted file mode 100644 index 86641ee60b7d..000000000000 --- a/docs/expensify-classic/hubs/integrations/travel-integrations.html +++ /dev/null @@ -1,5 +0,0 @@ ---- -layout: default ---- - -{% include section.html %} diff --git a/docs/expensify-classic/hubs/workspaces/reports.html b/docs/expensify-classic/hubs/workspaces/reports.html deleted file mode 100644 index 86641ee60b7d..000000000000 --- a/docs/expensify-classic/hubs/workspaces/reports.html +++ /dev/null @@ -1,5 +0,0 @@ ---- -layout: default ---- - -{% include section.html %} diff --git a/docs/redirects.csv b/docs/redirects.csv index 67ca238c1aed..897cd4e95775 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -203,6 +203,9 @@ 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/expensify-classic/bank-accounts-and-payments/Reimbursements.html,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-Payments +https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Pay-Bills.html,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills +https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports.html,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills https://help.expensify.com/articles/expensify-classic/expensify-card/Auto-Reconciliation,https://help.expensify.com/articles/expensify-classic/expensify-card/Expensify-Card-Reconciliation.md 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 @@ -214,4 +217,61 @@ https://help.expensify.com/articles/new-expensify/expenses/Set-up-your-wallet,ht 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 +https://help.expensify.com/expensify-classic/hubs/integrations/HR-integrations,https://help.expensify.com/expensify-classic/hubs/connections +https://help.expensify.com/expensify-classic/hubs/integrations/accounting-integrations,https://help.expensify.com/expensify-classic/hubs/connections +https://help.expensify.com/expensify-classic/hubs/integrations/other-integrations,https://help.expensify.com/expensify-classic/hubs/connections +https://help.expensify.com/expensify-classic/hubs/integrations/travel-integrations,https://help.expensify.com/expensify-classic/hubs/connections +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/ADP,https://help.expensify.com/articles/expensify-classic/connections/ADP +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Accelo,https://help.expensify.com/expensify-classic/hubs/connections/accelo +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Additional-Travel-Integrations,https://help.expensify.com/articles/expensify-classic/connections/Additional-Travel-Integrations +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Certinia,https://help.expensify.com/expensify-classic/hubs/connections/certinia +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Egencia,https://help.expensify.com/articles/expensify-classic/connections/Egencia +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Global-VaTax,https://help.expensify.com/articles/expensify-classic/connections/Global-VaTax +https://help.expensify.com/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO,https://help.expensify.com/articles/expensify-classic/connections/Google-Apps-SSO +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Greenhouse,https://help.expensify.com/articles/expensify-classic/connections/Greenhouse +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Gusto,https://help.expensify.com/articles/expensify-classic/connections/Gusto +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Indirect-Accounting-Integrations,https://help.expensify.com/articles/expensify-classic/connections/Indirect-Accounting-Integrations +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Lyft,https://help.expensify.com/articles/expensify-classic/connections/Lyft +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Navan,https://help.expensify.com/articles/expensify-classic/connections/Navan +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/NetSuite,https://help.expensify.com/expensify-classic/hubs/connections/netsuite +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/QuickBooks-Time,https://help.expensify.com/articles/expensify-classic/connections/QuickBooks-Time +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop,https://help.expensify.com/expensify-classic/hubs/connections/quickbooks-desktop +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online,https://help.expensify.com/expensify-classic/hubs/connections/quickbooks-online +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Rippling,https://help.expensify.com/articles/expensify-classic/connections/Rippling +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct,https://help.expensify.com/expensify-classic/hubs/connections/sage-intacct +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/TravelPerk,https://help.expensify.com/articles/expensify-classic/connections/TravelPerk +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Uber,https://help.expensify.com/articles/expensify-classic/connections/Uber +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Workday,https://help.expensify.com/articles/expensify-classic/connections/Workday +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Xero,https://help.expensify.com/expensify-classic/hubs/connections/xero +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Zenefits,https://help.expensify.com/articles/expensify-classic/connections/Zenefits +https://help.expensify.com/articles/expensify-classic/workspaces/tax-tracking,https://help.expensify.com/articles/expensify-classic/workspaces/Tax-Tracking +https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approval-Workflows,https://help.expensify.com/articles/expensify-classic/reports/Assign-report-approvers-to-specific-employees +https://help.expensify.com/articles/expensify-classic/settings/Notification-Troubleshooting,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-Notifications +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 +https://help.expensify.com/articles/expensify-classic/workspaces/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Set-Currency +https://help.expensify.com/articles/expensify-classic/workspaces/Expenses,https://help.expensify.com/articles/expensify-classic/workspaces/Expense-Settings +https://help.expensify.com/articles/expensify-classic/workspaces/Reimbursement,https://help.expensify.com/articles/expensify-classic/workspaces/Configure-Reimbursement-Settings +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/ADP.html,https://help.expensify.com/articles/expensify-classic/connections/ADP +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Accelo.html,https://help.expensify.com/expensify-classic/hubs/connections/accelo +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Additional-Travel-Integrations.html,https://help.expensify.com/articles/expensify-classic/connections/Additional-Travel-Integrations +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Certinia.html,https://help.expensify.com/expensify-classic/hubs/connections/certinia +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Egencia.html,https://help.expensify.com/articles/expensify-classic/connections/Egencia +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.html,https://help.expensify.com/articles/expensify-classic/connections/Global-VaTax +https://help.expensify.com/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO.html,https://help.expensify.com/articles/expensify-classic/connections/Google-Apps-SSO +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Greenhouse.html,https://help.expensify.com/articles/expensify-classic/connections/Greenhouse +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Gusto.html,https://help.expensify.com/articles/expensify-classic/connections/Gusto +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Indirect-Accounting-Integrations.html,https://help.expensify.com/articles/expensify-classic/connections/Indirect-Accounting-Integrations +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Lyft.html,https://help.expensify.com/articles/expensify-classic/connections/Lyft +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Navan.html,https://help.expensify.com/articles/expensify-classic/connections/Navan +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/NetSuite.html,https://help.expensify.com/expensify-classic/hubs/connections/netsuite +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/QuickBooks-Time.html,https://help.expensify.com/articles/expensify-classic/connections/QuickBooks-Time +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.html,https://help.expensify.com/expensify-classic/hubs/connections/quickbooks-desktop +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.html,https://help.expensify.com/expensify-classic/hubs/connections/quickbooks-online +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Rippling.html,https://help.expensify.com/articles/expensify-classic/connections/Rippling +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.html,https://help.expensify.com/expensify-classic/hubs/connections/sage-intacct +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/TravelPerk.html,https://help.expensify.com/articles/expensify-classic/connections/TravelPerk +https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Uber.html,https://help.expensify.com/articles/expensify-classic/connections/Uber +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Workday.html,https://help.expensify.com/articles/expensify-classic/connections/Workday +https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Xero.html,https://help.expensify.com/expensify-classic/hubs/connections/xero +https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Zenefits.html,https://help.expensify.com/articles/expensify-classic/connections/Zenefits +https://help.expensify.com/articles/expensify-classic/settings/Close-or-reopen-account,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Close-or-reopen-account diff --git a/fastlane/Fastfile b/fastlane/Fastfile index af9e798d2343..2560e48728c5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -10,6 +10,8 @@ # https://docs.fastlane.tools/plugins/available-plugins # +require 'ostruct' + skip_docs opt_out_usage diff --git a/ios/NewApp_AdHoc.mobileprovision.gpg b/ios/NewApp_AdHoc.mobileprovision.gpg index 32ed6ba30059..d5f3c582327d 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 5712b0d86b19..8300bd34ef76 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.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index f86952ca7aca..3afc8d4e6cf0 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -388,6 +388,7 @@ FBC7D704E4E9CC08E91D7919 /* [CP] Copy Pods Resources */, 9FF963998EFF771D82D473D2 /* [CP-User] [RNFB] Core Configuration */, A2BE84E8C8EFD6C81A2B41F1 /* [CP-User] [RNFB] Crashlytics Configuration */, + 498240F82C49553900C15857 /* Run Fullstory Asset Uploader */, ); buildRules = ( ); @@ -620,6 +621,24 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 498240F82C49553900C15857 /* Run Fullstory Asset Uploader */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Fullstory Asset Uploader"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/FullStory/tools/FullStoryCommandLine\" \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}\"\n"; + }; 5CF45ABA52C0BB0D7B9D139A /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -1561,7 +1580,11 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-Wl", + "-ld_classic", + ); PRODUCT_BUNDLE_IDENTIFIER = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -1629,7 +1652,11 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-Wl", + "-ld_classic", + ); PRODUCT_BUNDLE_IDENTIFIER = ""; PRODUCT_NAME = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; @@ -1707,7 +1734,11 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-Wl", + "-ld_classic", + ); PRODUCT_BUNDLE_IDENTIFIER = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -1852,7 +1883,11 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-Wl", + "-ld_classic", + ); PRODUCT_BUNDLE_IDENTIFIER = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -1989,7 +2024,11 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-Wl", + "-ld_classic", + ); PRODUCT_BUNDLE_IDENTIFIER = ""; PRODUCT_NAME = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; @@ -2124,7 +2163,11 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-Wl", + "-ld_classic", + ); PRODUCT_BUNDLE_IDENTIFIER = ""; PRODUCT_NAME = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index d1878e52d37c..539321700674 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.5 + 9.0.11 CFBundleSignature ???? CFBundleURLTypes @@ -40,11 +40,13 @@ CFBundleVersion - 9.0.5.4 + 9.0.11.2 FullStory OrgId o-1WN56P-na1 + RecordOnStart + ITSAppUsesNonExemptEncryption diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index d5de484924d7..327cd7a82247 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.5 + 9.0.11 CFBundleSignature ???? CFBundleVersion - 9.0.5.4 + 9.0.11.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 97f1e45585b2..4a78648c596a 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.5 + 9.0.11 CFBundleVersion - 9.0.5.4 + 9.0.11.2 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a5ffdcb4b63c..a2d1e0617a1d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -282,7 +282,7 @@ PODS: - nanopb/encode (= 2.30908.0) - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - - Onfido (29.7.1) + - Onfido (29.7.2) - onfido-react-native-sdk (10.6.0): - glog - hermes-engine @@ -303,7 +303,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - Plaid (5.2.1) + - Plaid (5.6.0) - PromisesObjC (2.4.0) - RCT-Folly (2022.05.16.00): - boost @@ -1257,7 +1257,7 @@ PODS: - React-Codegen - React-Core - ReactCommon/turbomodule/core - - react-native-geolocation (3.2.1): + - react-native-geolocation (3.3.0): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1400,10 +1400,10 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-plaid-link-sdk (11.5.0): + - react-native-plaid-link-sdk (11.11.0): - glog - hermes-engine - - Plaid (~> 5.2.0) + - Plaid (~> 5.6.0) - RCT-Folly (= 2022.05.16.00) - RCTRequired - RCTTypeSafety @@ -1439,7 +1439,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-release-profiler (0.1.6): + - react-native-release-profiler (0.2.1): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1871,7 +1871,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.88): + - RNLiveMarkdown (0.1.107): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1889,9 +1889,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.88) + - RNLiveMarkdown/common (= 0.1.107) - Yoga - - RNLiveMarkdown/common (0.1.88): + - RNLiveMarkdown/common (0.1.107): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1935,7 +1935,7 @@ PODS: - ReactCommon/turbomodule/core - Turf - Yoga - - RNPermissions (3.9.3): + - RNPermissions (3.10.1): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2034,7 +2034,7 @@ PODS: - RNSound/Core (= 0.11.2) - RNSound/Core (0.11.2): - React-Core - - RNSVG (14.1.0): + - RNSVG (15.4.0): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2052,9 +2052,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNSVG/common (= 14.1.0) + - RNSVG/common (= 15.4.0) - Yoga - - RNSVG/common (14.1.0): + - RNSVG/common (15.4.0): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2531,9 +2531,9 @@ SPEC CHECKSUMS: MapboxMaps: 87ef0003e6db46e45e7a16939f29ae87e38e7ce2 MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 - Onfido: 342cbecd7a4383e98dfe7f9c35e98aaece599062 + Onfido: f3af62ea1c9a419589c133e3e511e5d2c4f3f8af onfido-react-native-sdk: 3e3b0dd70afa97410fb318d54c6a415137968ef2 - Plaid: 7829e84db6d766a751c91a402702946d2977ddcb + Plaid: c32f22ffce5ec67c9e6147eaf6c4d7d5f8086d89 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 RCTRequired: ab7f915c15569f04a49669e573e6e319a53f9faa @@ -2562,7 +2562,7 @@ SPEC CHECKSUMS: react-native-cameraroll: f373bebbe9f6b7c3fd2a6f97c5171cda574cf957 react-native-config: 5ce986133b07fc258828b20b9506de0e683efc1c react-native-document-picker: 8532b8af7c2c930f9e202aac484ac785b0f4f809 - react-native-geolocation: f9e92eb774cb30ac1e099f34b3a94f03b4db7eb3 + react-native-geolocation: 580c86eb531c0aaf7a14bc76fd2983ce47ca58aa react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: 28ccfa09520e7d7e30739480dea4df003493bfe8 react-native-keyboard-controller: 47c01b0741ae5fc84e53cf282e61cfa5c2edb19b @@ -2571,9 +2571,9 @@ SPEC CHECKSUMS: react-native-pager-view: ccd4bbf9fc7effaf8f91f8dae43389844d9ef9fa react-native-pdf: 762369633665ec02ac227aefe2f4558b92475c23 react-native-performance: fb21ff0c9bd7a10789c69d948f25b0067d29f7a9 - react-native-plaid-link-sdk: 2a91ef7e257ae16d180a1ca14ba3041ae0836fbf + react-native-plaid-link-sdk: ba40d1b13cca4b946974fafd9ae278e0fb697d87 react-native-quick-sqlite: e3ab3e0a29d8c705f47a60aaa6ceaa42eb6a9ec1 - react-native-release-profiler: 14ccdc0eeb03bedf625cf68d53d80275a81b19dd + react-native-release-profiler: a77d4f291b92e48d3d4a574deed19bd1b513ac98 react-native-render-html: 96c979fe7452a0a41559685d2f83b12b93edac8c react-native-safe-area-context: 9d79895b60b8be151fdf6faef9d2d0591eeecc63 react-native-view-shot: 6b7ed61d77d88580fed10954d45fad0eb2d47688 @@ -2614,16 +2614,16 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 74b7b3d06d667ba0bbf41da7718f2607ae0dfe8f RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: e33d2c97863d5480f8f4b45f8b25f801cc43c7f5 + RNLiveMarkdown: f0c641a0bcf5fdea3ec1bb52a64b30ff88d25c1f RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: df8fe93dbd251f25022f4023d31bc04160d4d65c - RNPermissions: 0b61d30d21acbeafe25baaa47d9bae40a0c65216 + RNPermissions: d2392b754e67bc14491f5b12588bef2864e783f3 RNReactNativeHapticFeedback: 616c35bdec7d20d4c524a7949ca9829c09e35f37 RNReanimated: 323436b1a5364dca3b5f8b1a13458455e0de9efe RNScreens: abd354e98519ed267600b7ee64fdcb8e060b1218 RNShare: 2a4cdfc0626ad56b0ef583d424f2038f772afe58 RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852 - RNSVG: 18f1381e046be2f1c30b4724db8d0c966238089f + RNSVG: 8c067e7203053d4c82f456cbeab1fe509ac797dd SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9 SDWebImageAVIFCoder: 8348fef6d0ec69e129c66c9fe4d74fbfbf366112 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c @@ -2631,8 +2631,8 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 PODFILE CHECKSUM: d5e281e5370cb0211a104efd90eb5fa7af936e14 -COCOAPODS: 1.15.2 +COCOAPODS: 1.15.2 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 907b97e2334a..7b99df5d2b5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "new.expensify", - "version": "9.0.5-4", + "version": "9.0.11-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.5-4", + "version": "9.0.11-2", "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.88", + "@expensify/react-native-live-markdown": "^0.1.107", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -26,12 +26,12 @@ "@fullstory/react-native": "^1.4.2", "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", - "@kie/act-js": "^2.6.0", + "@kie/act-js": "^2.6.2", "@kie/mock-github": "2.0.1", "@onfido/react-native-sdk": "10.6.0", "@react-native-camera-roll/camera-roll": "7.4.0", "@react-native-clipboard/clipboard": "^1.13.2", - "@react-native-community/geolocation": "3.2.1", + "@react-native-community/geolocation": "3.3.0", "@react-native-community/netinfo": "11.2.1", "@react-native-firebase/analytics": "^12.3.0", "@react-native-firebase/app": "^12.3.0", @@ -55,7 +55,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "2.0.19", + "expensify-common": "2.0.49", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -79,7 +79,7 @@ "react-content-loader": "^7.0.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "1.0.13", + "react-fast-pdf": "1.0.14", "react-map-gl": "^7.1.3", "react-native": "0.73.4", "react-native-android-location-enabler": "^2.0.1", @@ -106,19 +106,19 @@ "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", - "react-native-permissions": "^3.9.3", + "react-native-permissions": "^3.10.0", "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf", - "react-native-plaid-link-sdk": "11.5.0", - "react-native-qrcode-svg": "^6.2.0", + "react-native-plaid-link-sdk": "11.11.0", + "react-native-qrcode-svg": "git+https://github.com/Expensify/react-native-qrcode-svg", "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-quick-sqlite#abc91857d4b3efb2020ec43abd2a508563b64316", "react-native-reanimated": "^3.8.0", - "react-native-release-profiler": "^0.1.6", + "react-native-release-profiler": "^0.2.1", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.8.2", "react-native-screens": "3.32.0", "react-native-share": "^10.0.2", "react-native-sound": "^0.11.2", - "react-native-svg": "14.1.0", + "react-native-svg": "15.4.0", "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "3.8.0", @@ -155,7 +155,7 @@ "@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/profiler": "^0.10.11", "@perf-profiler/reporter": "^0.9.0", "@perf-profiler/types": "^0.8.0", "@react-native-community/eslint-config": "3.2.0", @@ -200,7 +200,7 @@ "babel-jest": "29.4.1", "babel-loader": "^9.1.3", "babel-plugin-module-resolver": "^5.0.0", - "babel-plugin-react-compiler": "^0.0.0-experimental-c23de8d-20240515", + "babel-plugin-react-compiler": "0.0.0-experimental-696af53-20240625", "babel-plugin-react-native-web": "^0.18.7", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-remove-console": "^6.9.4", @@ -212,14 +212,14 @@ "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", "electron": "^29.4.1", - "electron-builder": "24.13.2", + "electron-builder": "25.0.0", "eslint": "^8.57.0", "eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-expensify": "^2.0.52", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-jsdoc": "^46.2.6", - "eslint-plugin-react-compiler": "^0.0.0-experimental-53bb89e-20240515", + "eslint-plugin-react-compiler": "0.0.0-experimental-0998c1e-20240625", "eslint-plugin-react-native-a11y": "^3.3.0", "eslint-plugin-storybook": "^0.8.0", "eslint-plugin-testing-library": "^6.2.2", @@ -233,9 +233,11 @@ "memfs": "^4.6.0", "onchange": "^7.1.0", "patch-package": "^8.0.0", + "peggy": "^4.0.3", "portfinder": "^1.0.28", "prettier": "^2.8.8", "pusher-js-mock": "^0.3.3", + "react-compiler-healthcheck": "^0.0.0-experimental-b130d5f-20240625", "react-is": "^18.3.1", "react-native-clean-project": "^4.0.0-alpha4.0", "react-test-renderer": "18.2.0", @@ -2980,8 +2982,9 @@ }, "node_modules/@develar/schema-utils": { "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" @@ -2996,8 +2999,9 @@ }, "node_modules/@develar/schema-utils/node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3011,16 +3015,18 @@ }, "node_modules/@develar/schema-utils/node_modules/ajv-keywords": { "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, - "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } }, "node_modules/@develar/schema-utils/node_modules/json-schema-traverse": { "version": "0.4.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", @@ -3105,9 +3111,10 @@ } }, "node_modules/@electron/asar": { - "version": "3.2.8", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.10.tgz", + "integrity": "sha512-mvBSwIBUeiRscrCeJE1LwctAriBj65eUDm0Pc11iE5gRwzkmsdbS7FnZ1XUWjpSeQWL1L5g12Fc/SchPM9DUOw==", "dev": true, - "license": "MIT", "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", @@ -3122,8 +3129,9 @@ }, "node_modules/@electron/asar/node_modules/commander": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 6" } @@ -3186,9 +3194,10 @@ } }, "node_modules/@electron/notarize": { - "version": "2.2.1", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.3.2.tgz", + "integrity": "sha512-zfayxCe19euNwRycCty1C7lF7snk9YwfRpB5M8GLr1a4ICH63znxaPNAubrMvj0yDvVozqfgsdYpXVUnpWBDpg==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", @@ -3199,9 +3208,10 @@ } }, "node_modules/@electron/osx-sign": { - "version": "1.0.5", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.0.tgz", + "integrity": "sha512-TEXhxlYSDRr9JWK5nWdOv5MtuUdaZ412uxIIEQ0hLt80o0HYWtQJBlW5QmrQDMtebzATaOjKG9UfCzLyA90zWQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", @@ -3220,8 +3230,9 @@ }, "node_modules/@electron/osx-sign/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -3233,8 +3244,9 @@ }, "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8.0.0" }, @@ -3242,21 +3254,172 @@ "url": "https://github.com/sponsors/gjtorikian/" } }, + "node_modules/@electron/rebuild": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.6.0.tgz", + "integrity": "sha512-zF4x3QupRU3uNGaP5X1wjpmcjfw1H87kyqZ00Tc3HvriV+4gmOGuvQjGNkrJuXdsApssdNyVwLsy+TaeTGGcVw==", + "dev": true, + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "fs-extra": "^10.0.0", + "got": "^11.7.0", + "node-abi": "^3.45.0", + "node-api-version": "^0.2.0", + "node-gyp": "^9.0.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^6.0.5", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/@electron/rebuild/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@electron/rebuild/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@electron/rebuild/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@electron/rebuild/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@electron/rebuild/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/rebuild/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@electron/rebuild/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@electron/universal": { - "version": "1.5.1", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.1.tgz", + "integrity": "sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==", "dev": true, - "license": "MIT", "dependencies": { - "@electron/asar": "^3.2.1", - "@malept/cross-spawn-promise": "^1.1.0", + "@electron/asar": "^3.2.7", + "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", - "dir-compare": "^3.0.0", - "fs-extra": "^9.0.1", - "minimatch": "^3.0.4", - "plist": "^3.0.4" + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" }, "engines": { - "node": ">=8.6" + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { @@ -3783,9 +3946,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.88", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.88.tgz", - "integrity": "sha512-78X5ACV+OL+aL6pfJAXyHkNuMGUc4Rheo4qLkIwLpmUIAiAxmY0B2lch5XHSNGf1a5ofvVbdQ6kl84+4E6DwlQ==", + "version": "0.1.107", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.107.tgz", + "integrity": "sha512-0Yhqo1azCu3cTmzv/KkILZX2yPiyFUZNRx+AdMdT18pMxpqTAuBtFV4HM44rlimmpT3vgwQ1F/0C0AfRAk5dZA==", "workspaces": [ "parser", "example", @@ -6122,28 +6285,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/console/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/console/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -6247,28 +6388,6 @@ } } }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/core/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -6337,86 +6456,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { - "version": "17.0.31", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/@jest/create-cache-key-function/node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/environment": { "version": "29.7.0", "license": "MIT", @@ -6430,86 +6469,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/environment/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/environment/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/@jest/environment/node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/expect": { "version": "29.6.2", "license": "MIT", @@ -6546,86 +6505,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/fake-timers/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/@jest/fake-timers/node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/fake-timers/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/globals": { "version": "29.5.0", "license": "MIT", @@ -6639,86 +6518,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/globals/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/globals/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/@jest/globals/node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/reporters": { "version": "29.4.1", "license": "MIT", @@ -6760,28 +6559,6 @@ } } }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/reporters/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -6901,86 +6678,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/test-result/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/@jest/test-result/node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-result/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/test-sequencer": { "version": "29.4.1", "license": "MIT", @@ -7018,28 +6715,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/transform/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -7117,10 +6792,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -7135,10 +6807,7 @@ }, "node_modules/@jest/types/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -7151,10 +6820,7 @@ }, "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7168,10 +6834,7 @@ }, "node_modules/@jest/types/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -7181,27 +6844,18 @@ }, "node_modules/@jest/types/node_modules/color-name": { "version": "1.1.4", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true + "license": "MIT" }, "node_modules/@jest/types/node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", - "optional": true, - "peer": true, "engines": { "node": ">=8" } }, "node_modules/@jest/types/node_modules/supports-color": { "version": "7.2.0", - "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -7283,9 +6937,10 @@ } }, "node_modules/@kie/act-js": { - "version": "2.6.0", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@kie/act-js/-/act-js-2.6.2.tgz", + "integrity": "sha512-i366cfWluUi55rPZ6e9/aWH4tnw3Q6W1CKh9Gz6QjTvbAtS4KnUUy33I9aMXS6uwa0haw6MSahMM37vmuFCVpQ==", "hasInstallScript": true, - "license": "SEE LICENSE IN LICENSE", "dependencies": { "@kie/mock-github": "^2.0.0", "adm-zip": "^0.5.10", @@ -7415,7 +7070,9 @@ } }, "node_modules/@malept/cross-spawn-promise": { - "version": "1.1.1", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", "dev": true, "funding": [ { @@ -7427,18 +7084,18 @@ "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" } ], - "license": "Apache-2.0", "dependencies": { "cross-spawn": "^7.0.1" }, "engines": { - "node": ">= 10" + "node": ">= 12.13.0" } }, "node_modules/@malept/flatpak-bundler": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", @@ -7878,14 +7535,41 @@ "react-native": ">=0.70.0 <1.0.x" } }, + "node_modules/@peggyjs/from-mem": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@peggyjs/from-mem/-/from-mem-1.3.0.tgz", + "integrity": "sha512-kzGoIRJjkg3KuGI4bopz9UvF3KguzfxalHRDEIdqEZUe45xezsQ6cx30e0RKuxPUexojQRBfu89Okn7f4/QXsw==", + "dev": true, + "dependencies": { + "semver": "7.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@peggyjs/from-mem/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "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==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@perf-profiler/android/-/android-0.13.0.tgz", + "integrity": "sha512-4lUQjJNHFAYB5npts5JLrPaPNpIOEAAjfpeTQOOgBNLT1NW50WWSGuvV2pAdnMi7T28cXs3aUziJJ30cNrSvNg==", "dev": true, "dependencies": { "@perf-profiler/logger": "^0.3.3", - "@perf-profiler/profiler": "^0.10.10", + "@perf-profiler/profiler": "^0.10.11", "@perf-profiler/types": "^0.8.0", "commander": "^12.0.0", "lodash": "^4.17.21" @@ -7904,24 +7588,24 @@ } }, "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==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@perf-profiler/ios/-/ios-0.3.3.tgz", + "integrity": "sha512-dbb9lVKOyj1VjinuxrnbfI3FT0+uhH3xclqLQH7rQFA4d93dusjC/s3RzlnCXRNPFPDy5TTmkiIg3xM/6P3/2g==", "dev": true, "dependencies": { - "@perf-profiler/ios-instruments": "^0.3.2", + "@perf-profiler/ios-instruments": "^0.3.3", "@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==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@perf-profiler/ios-instruments/-/ios-instruments-0.3.3.tgz", + "integrity": "sha512-e3UmlWuNUOuNbJPWg6aLOVd9wRKe3RYCqwwUgxMNIAwa5QBxaVYBf2pt3+HRsYReh2qm1yvqW7LU9zP+AJ7/7g==", "dev": true, "dependencies": { "@perf-profiler/logger": "^0.3.3", - "@perf-profiler/profiler": "^0.10.10", + "@perf-profiler/profiler": "^0.10.11", "@perf-profiler/types": "^0.8.0", "commander": "^12.0.0", "fast-xml-parser": "^4.2.7" @@ -7962,13 +7646,13 @@ } }, "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==", + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/@perf-profiler/profiler/-/profiler-0.10.11.tgz", + "integrity": "sha512-nu/zakhG5wRi0tCw4SjTCZJh9e/x9YABAOChh3lGI6CESsFzc1Gi2Vrr+2sytN8dpiTDYCCbECC2EalD7ZKvtg==", "dev": true, "dependencies": { - "@perf-profiler/android": "^0.12.1", - "@perf-profiler/ios": "^0.3.2", + "@perf-profiler/android": "^0.13.0", + "@perf-profiler/ios": "^0.3.3", "@perf-profiler/types": "^0.8.0" } }, @@ -9043,9 +8727,9 @@ } }, "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { "node": ">=8.3.0" }, @@ -9553,9 +9237,9 @@ "license": "MIT" }, "node_modules/@react-native-community/geolocation": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@react-native-community/geolocation/-/geolocation-3.2.1.tgz", - "integrity": "sha512-/+HNzuRl4UCMma7KK+KYL8k2nxAGuW+DGxqmqfpiqKBlCkCUbuFHaZZdqVD6jpsn9r/ghe583ECLmd9SV9I4Bw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/geolocation/-/geolocation-3.3.0.tgz", + "integrity": "sha512-7DFeuotH7m7ImoXffN3TmlGSFn1XjvsaphPort0XZKipssYbdHiKhVVWG+jzisvDhcXikUc6nbUJgddVBL6RDg==", "engines": { "node": ">=18.0.0" }, @@ -17625,8 +17309,9 @@ }, "node_modules/@types/debug": { "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/ms": "*" } @@ -17713,8 +17398,9 @@ }, "node_modules/@types/fs-extra": { "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*" } @@ -17901,8 +17587,9 @@ }, "node_modules/@types/ms": { "version": "0.7.34", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "dev": true }, "node_modules/@types/node": { "version": "20.11.5", @@ -17933,8 +17620,9 @@ }, "node_modules/@types/plist": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "@types/node": "*", @@ -18142,9 +17830,10 @@ "dev": true }, "node_modules/@types/verror": { - "version": "1.10.9", + "version": "1.10.10", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz", + "integrity": "sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg==", "dev": true, - "license": "MIT", "optional": true }, "node_modules/@types/webpack": { @@ -18180,10 +17869,7 @@ "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -18917,8 +18603,9 @@ }, "node_modules/7zip-bin": { "version": "5.2.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true }, "node_modules/abab": { "version": "2.0.6", @@ -18926,8 +18613,8 @@ }, "node_modules/abbrev": { "version": "1.1.1", - "license": "ISC", - "optional": true + "devOptional": true, + "license": "ISC" }, "node_modules/abort-controller": { "version": "3.0.0", @@ -19045,6 +18732,18 @@ "node": ">= 6.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dev": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "license": "MIT", @@ -19265,29 +18964,32 @@ } }, "node_modules/app-builder-bin": { - "version": "4.0.0", - "dev": true, - "license": "MIT" + "version": "5.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.4.tgz", + "integrity": "sha512-4MitKmOtfTdMONrtRoiaqJ6HtlVZXgrNX1PNdEzEHSAoXU85x7s+mo0IhAS9K9qgjyTVuLrM1E/HAMp5qGyoOA==", + "dev": true }, "node_modules/app-builder-lib": { - "version": "24.13.2", + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-25.0.0.tgz", + "integrity": "sha512-GIx0n/QvbeObY8rQTTp08UPn4pS9xSGZLq6cPRy/CyX/mTNN9pO/uU28MWgqjnYXk0bf/595vzDdAijuDyz5Zw==", "dev": true, - "license": "MIT", "dependencies": { "@develar/schema-utils": "~2.6.5", - "@electron/notarize": "2.2.1", - "@electron/osx-sign": "1.0.5", - "@electron/universal": "1.5.1", + "@electron/notarize": "2.3.2", + "@electron/osx-sign": "1.3.0", + "@electron/rebuild": "3.6.0", + "@electron/universal": "2.0.1", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "bluebird-lst": "^1.0.9", - "builder-util": "24.13.1", - "builder-util-runtime": "9.2.4", + "builder-util": "25.0.0", + "builder-util-runtime": "9.2.5", "chromium-pickle-js": "^0.2.0", "debug": "^4.3.4", "ejs": "^3.1.8", - "electron-publish": "24.13.1", + "electron-publish": "25.0.0", "form-data": "^4.0.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", @@ -19295,8 +18997,9 @@ "isbinaryfile": "^5.0.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", - "minimatch": "^5.1.1", - "read-config-file": "6.3.2", + "minimatch": "^10.0.0", + "read-config-file": "6.4.0", + "resedit": "^1.7.0", "sanitize-filename": "^1.6.3", "semver": "^7.3.8", "tar": "^6.1.12", @@ -19306,27 +19009,30 @@ "node": ">=14.0.0" }, "peerDependencies": { - "dmg-builder": "24.13.2", - "electron-builder-squirrel-windows": "24.13.2" + "dmg-builder": "25.0.0", + "electron-builder-squirrel-windows": "25.0.0" } }, "node_modules/app-builder-lib/node_modules/argparse": { "version": "2.0.1", - "dev": true, - "license": "Python-2.0" + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/app-builder-lib/node_modules/brace-expansion": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/app-builder-lib/node_modules/form-data": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, - "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -19338,8 +19044,9 @@ }, "node_modules/app-builder-lib/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -19351,8 +19058,9 @@ }, "node_modules/app-builder-lib/node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -19361,14 +19069,18 @@ } }, "node_modules/app-builder-lib/node_modules/minimatch": { - "version": "5.1.6", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/app-root-dir": { @@ -19388,13 +19100,14 @@ }, "node_modules/aproba": { "version": "1.2.0", - "license": "ISC", - "optional": true + "devOptional": true, + "license": "ISC" }, "node_modules/archiver": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "archiver-utils": "^2.1.0", @@ -19411,8 +19124,9 @@ }, "node_modules/archiver-utils": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "glob": "^7.1.4", @@ -19432,8 +19146,9 @@ }, "node_modules/archiver/node_modules/readable-stream": { "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "inherits": "^2.0.3", @@ -19705,8 +19420,9 @@ }, "node_modules/assert-plus": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, - "license": "MIT", "optional": true, "engines": { "node": ">=0.8" @@ -19749,8 +19465,9 @@ }, "node_modules/astral-regex": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, - "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -19770,8 +19487,9 @@ }, "node_modules/async-exit-hook": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -20331,9 +20049,9 @@ } }, "node_modules/babel-plugin-react-compiler": { - "version": "0.0.0-experimental-c23de8d-20240515", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-0.0.0-experimental-c23de8d-20240515.tgz", - "integrity": "sha512-0XN2gmpT55QtAz5n7d5g91y1AuO9tRhWBaLgCRyc4ExHrlr7+LfxW+YTb3mOwxngkkiggwM8HyYsaEK9MqhnlQ==", + "version": "0.0.0-experimental-696af53-20240625", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-0.0.0-experimental-696af53-20240625.tgz", + "integrity": "sha512-OUDKms8qmcm5bX0D+sJWC1YcKcd7AZ2aJ7eY6gkR+Xr7PDfkXLbqAld4Qs9B0ntjVbUMEtW/PjlQrxDtY4raHg==", "dev": true, "dependencies": { "@babel/generator": "7.2.0", @@ -20912,13 +20630,15 @@ }, "node_modules/bluebird": { "version": "3.7.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true }, "node_modules/bluebird-lst": { "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", "dev": true, - "license": "MIT", "dependencies": { "bluebird": "^3.5.5" } @@ -21238,17 +20958,6 @@ "node": "*" } }, - "node_modules/buffer-equal": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/buffer-fill": { "version": "1.0.0", "license": "MIT" @@ -21262,15 +20971,16 @@ "license": "MIT" }, "node_modules/builder-util": { - "version": "24.13.1", + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-25.0.0.tgz", + "integrity": "sha512-cI8zIsipo/gciZ5jGEA1qYL5Em1N6cWoNMpeJWZAfOs3H9s5zQWKnAS7rTdlJpsJ88gEmL5/32yeXUF2Uzxw6w==", "dev": true, - "license": "MIT", "dependencies": { "@types/debug": "^4.1.6", "7zip-bin": "~5.2.0", - "app-builder-bin": "4.0.0", + "app-builder-bin": "v5.0.0-alpha.4", "bluebird-lst": "^1.0.9", - "builder-util-runtime": "9.2.4", + "builder-util-runtime": "9.2.5", "chalk": "^4.1.2", "cross-spawn": "^7.0.3", "debug": "^4.3.4", @@ -21285,9 +20995,10 @@ } }, "node_modules/builder-util-runtime": { - "version": "9.2.4", + "version": "9.2.5", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.5.tgz", + "integrity": "sha512-HjIDfhvqx/8B3TDN4GbABQcgpewTU4LMRTQPkVpKYV3lsuxEJoIfvg09GyWTNmfVNSUAYf+fbTN//JX4TH20pg==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -21298,8 +21009,9 @@ }, "node_modules/builder-util/node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -21312,13 +21024,15 @@ }, "node_modules/builder-util/node_modules/argparse": { "version": "2.0.1", - "dev": true, - "license": "Python-2.0" + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/builder-util/node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -21332,8 +21046,9 @@ }, "node_modules/builder-util/node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -21343,13 +21058,15 @@ }, "node_modules/builder-util/node_modules/color-name": { "version": "1.1.4", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/builder-util/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -21361,16 +21078,18 @@ }, "node_modules/builder-util/node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/builder-util/node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -21380,8 +21099,9 @@ }, "node_modules/builder-util/node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -21774,8 +21494,9 @@ }, "node_modules/chromium-pickle-js": { "version": "0.2.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true }, "node_modules/ci-info": { "version": "3.8.0", @@ -21981,8 +21702,9 @@ }, "node_modules/cli-truncate": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "slice-ansi": "^3.0.0", @@ -22135,8 +21857,8 @@ }, "node_modules/color-support": { "version": "1.1.3", + "devOptional": true, "license": "ISC", - "optional": true, "bin": { "color-support": "bin.js" } @@ -22201,8 +21923,9 @@ }, "node_modules/compare-version": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -22233,8 +21956,9 @@ }, "node_modules/compress-commons": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "buffer-crc32": "^0.2.13", @@ -22248,8 +21972,9 @@ }, "node_modules/compress-commons/node_modules/readable-stream": { "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "inherits": "^2.0.3", @@ -22414,47 +22139,64 @@ } }, "node_modules/config-file-ts": { - "version": "0.2.6", + "version": "0.2.8-rc1", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.8-rc1.tgz", + "integrity": "sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==", "dev": true, - "license": "MIT", "dependencies": { - "glob": "^10.3.10", - "typescript": "^5.3.3" + "glob": "^10.3.12", + "typescript": "^5.4.3" } }, "node_modules/config-file-ts/node_modules/brace-expansion": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/config-file-ts/node_modules/glob": { - "version": "10.3.10", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, - "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.17" + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" }, "funding": { "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/config-file-ts/node_modules/minimatch": { - "version": "9.0.3", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -22466,9 +22208,10 @@ } }, "node_modules/config-file-ts/node_modules/minipass": { - "version": "7.0.4", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -22557,8 +22300,8 @@ }, "node_modules/console-control-strings": { "version": "1.1.0", - "license": "ISC", - "optional": true + "devOptional": true, + "license": "ISC" }, "node_modules/constants-browserify": { "version": "1.0.0", @@ -22776,8 +22519,9 @@ }, "node_modules/crc": { "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "buffer": "^5.1.0" @@ -22785,8 +22529,9 @@ }, "node_modules/crc-32": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, - "license": "Apache-2.0", "peer": true, "bin": { "crc32": "bin/crc32.njs" @@ -22797,8 +22542,9 @@ }, "node_modules/crc32-stream": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "crc-32": "^1.2.0", @@ -22810,8 +22556,9 @@ }, "node_modules/crc32-stream/node_modules/readable-stream": { "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "inherits": "^2.0.3", @@ -23551,8 +23298,8 @@ }, "node_modules/delegates": { "version": "1.0.0", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/denodeify": { "version": "1.2.1", @@ -23613,8 +23360,8 @@ }, "node_modules/detect-libc": { "version": "2.0.1", + "devOptional": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -23707,12 +23454,13 @@ "license": "MIT" }, "node_modules/dir-compare": { - "version": "3.3.0", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", "dev": true, - "license": "MIT", "dependencies": { - "buffer-equal": "^1.0.0", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " } }, "node_modules/dir-glob": { @@ -23726,13 +23474,14 @@ } }, "node_modules/dmg-builder": { - "version": "24.13.2", + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-25.0.0.tgz", + "integrity": "sha512-kXETWCy/JIXS8PHYc8Y0EdSWO02gpf4jleW74hkIp6o9WWTjAdBRw2fAcRBNIEBUJtVHFrgCYsEWh0wKFUB0+A==", "dev": true, - "license": "MIT", "dependencies": { - "app-builder-lib": "24.13.2", - "builder-util": "24.13.1", - "builder-util-runtime": "9.2.4", + "app-builder-lib": "25.0.0", + "builder-util": "25.0.0", + "builder-util-runtime": "9.2.5", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" @@ -23743,13 +23492,15 @@ }, "node_modules/dmg-builder/node_modules/argparse": { "version": "2.0.1", - "dev": true, - "license": "Python-2.0" + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/dmg-builder/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -23761,8 +23512,9 @@ }, "node_modules/dmg-builder/node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -23772,8 +23524,9 @@ }, "node_modules/dmg-license": { "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -23797,8 +23550,9 @@ }, "node_modules/dmg-license/node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -23813,8 +23567,9 @@ }, "node_modules/dmg-license/node_modules/json-schema-traverse": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "license": "MIT", "optional": true }, "node_modules/dns-packet": { @@ -23929,20 +23684,31 @@ } }, "node_modules/dotenv": { - "version": "16.3.1", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { - "version": "5.1.0", + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", + "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", "dev": true, - "license": "BSD-2-Clause" + "dependencies": { + "dotenv": "^16.4.4" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } }, "node_modules/duplexer": { "version": "0.1.2", @@ -24010,19 +23776,20 @@ } }, "node_modules/electron-builder": { - "version": "24.13.2", + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-25.0.0.tgz", + "integrity": "sha512-3nEqF6KnoM206mLz1C70VXWCzXmH2boL82wkpgLB1GXgK3dly6ay/cepI+2BmQT4iWkIHeG8qH9bPjPj0hn+1A==", "dev": true, - "license": "MIT", "dependencies": { - "app-builder-lib": "24.13.2", - "builder-util": "24.13.1", - "builder-util-runtime": "9.2.4", + "app-builder-lib": "25.0.0", + "builder-util": "25.0.0", + "builder-util-runtime": "9.2.5", "chalk": "^4.1.2", - "dmg-builder": "24.13.2", + "dmg-builder": "25.0.0", "fs-extra": "^10.1.0", "is-ci": "^3.0.0", "lazy-val": "^1.0.5", - "read-config-file": "6.3.2", + "read-config-file": "6.4.0", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, @@ -24035,21 +23802,23 @@ } }, "node_modules/electron-builder-squirrel-windows": { - "version": "24.13.2", + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.0.0.tgz", + "integrity": "sha512-bfARwAdye1UkFQZ7NedHZBcOek2lvDDeg/pCaXT4Nrki7gdwrvVY/Be/QJm7Smc6IR/mviozbL9ykUHQ/FSsbw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "app-builder-lib": "24.13.2", + "app-builder-lib": "25.0.0", "archiver": "^5.3.1", - "builder-util": "24.13.1", + "builder-util": "25.0.0", "fs-extra": "^10.1.0" } }, "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -24138,13 +23907,14 @@ } }, "node_modules/electron-publish": { - "version": "24.13.1", + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-25.0.0.tgz", + "integrity": "sha512-8wq3pVLq9bpd/jNKJGIXbeL8B8AovLojtCDkVSuSgrLtxEndqy5JfuadUKPAgbmh1zjholNAHsfHH9FS5yeYAg==", "dev": true, - "license": "MIT", "dependencies": { "@types/fs-extra": "^9.0.11", - "builder-util": "24.13.1", - "builder-util-runtime": "9.2.4", + "builder-util": "25.0.0", + "builder-util-runtime": "9.2.5", "chalk": "^4.1.2", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", @@ -24153,8 +23923,9 @@ }, "node_modules/electron-publish/node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -24167,8 +23938,9 @@ }, "node_modules/electron-publish/node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -24182,8 +23954,9 @@ }, "node_modules/electron-publish/node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -24193,13 +23966,15 @@ }, "node_modules/electron-publish/node_modules/color-name": { "version": "1.1.4", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/electron-publish/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -24211,16 +23986,18 @@ }, "node_modules/electron-publish/node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/electron-publish/node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -24283,6 +24060,15 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "license": "MIT", @@ -25297,9 +25083,9 @@ } }, "node_modules/eslint-plugin-react-compiler": { - "version": "0.0.0-experimental-53bb89e-20240515", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-0.0.0-experimental-53bb89e-20240515.tgz", - "integrity": "sha512-L3HV9qja1dnClRlR9aaWEJeJoGPH9cgjKq0sYqIOOH9uyWdVMH9CudsFr6yLva7dj05FpLZkiIaRSZJ3P/v6yQ==", + "version": "0.0.0-experimental-0998c1e-20240625", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-0.0.0-experimental-0998c1e-20240625.tgz", + "integrity": "sha512-npq2RomExoQI3jETs4OrifaygyJYgOcX/q74Q9OC7GmffLh5zSJaQpzjs2fi61NMNkJyIvTBD0C6sKTGGcetOw==", "dev": true, "dependencies": { "@babel/core": "^7.24.4", @@ -25973,9 +25759,9 @@ } }, "node_modules/expensify-common": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.19.tgz", - "integrity": "sha512-GdWlYiHOAapy/jxjcvL9NKGOofhoEuKIwvJNGNVHbDXcA+0NxVCNYrHt1yrLnVcE4KtK6PGT6fQ2Lp8NTCoA+g==", + "version": "2.0.49", + "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.49.tgz", + "integrity": "sha512-67QbRuR2XEl2RoNLSbyqGWATIbOXPV42azAfs2sqNT6iyWKcOgHUqRkWPhxA0GmSW35lwq66bvgPVsQUfMGCow==", "dependencies": { "awesome-phonenumber": "^5.4.0", "classnames": "2.5.0", @@ -25987,9 +25773,9 @@ "prop-types": "15.8.1", "react": "16.12.0", "react-dom": "16.12.0", - "semver": "^7.6.0", + "semver": "^7.6.2", "simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5", - "ua-parser-js": "^1.0.37" + "ua-parser-js": "^1.0.38" } }, "node_modules/expensify-common/node_modules/react": { @@ -26026,9 +25812,9 @@ } }, "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==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "funding": [ { "type": "opencollective", @@ -26351,6 +26137,12 @@ "node": ">=8" } }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true + }, "node_modules/express": { "version": "4.18.1", "license": "MIT", @@ -26514,11 +26306,12 @@ }, "node_modules/extsprintf": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", "dev": true, "engines": [ "node >=0.6.0" ], - "license": "MIT", "optional": true }, "node_modules/fast-deep-equal": { @@ -26532,6 +26325,8 @@ }, "node_modules/fast-equals": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==", "license": "MIT" }, "node_modules/fast-glob": { @@ -27767,8 +27562,8 @@ }, "node_modules/has-unicode": { "version": "2.0.1", - "license": "ISC", - "optional": true + "devOptional": true, + "license": "ISC" }, "node_modules/has-value": { "version": "1.0.0", @@ -27994,8 +27789,9 @@ }, "node_modules/hosted-git-info": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, - "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -28255,6 +28051,15 @@ "node": ">=10.17.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/husky": { "version": "1.3.1", "dev": true, @@ -28530,8 +28335,9 @@ }, "node_modules/iconv-corefoundation": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -28900,6 +28706,25 @@ "node": ">=0.10.0" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, "node_modules/ip-regex": { "version": "2.1.0", "license": "MIT", @@ -29047,8 +28872,9 @@ }, "node_modules/is-ci": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "dev": true, - "license": "MIT", "dependencies": { "ci-info": "^3.2.0" }, @@ -29283,6 +29109,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, "node_modules/is-map": { "version": "2.0.2", "dev": true, @@ -29559,8 +29391,9 @@ }, "node_modules/isbinaryfile": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.2.tgz", + "integrity": "sha512-GvcjojwonMjWbTkfMpnVHVqXW/wKMYDfEpY94/8zy8HFMOqb/VL6oeONq9v87q4ttVlaTLnGXnJD4B5B1OTGIg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 18.0.0" }, @@ -29857,28 +29690,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-circus/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -29969,28 +29780,6 @@ } } }, - "node_modules/jest-cli/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-cli/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -30092,28 +29881,6 @@ } } }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-config/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-config/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -30267,28 +30034,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-each/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -30372,28 +30117,6 @@ } } }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-environment-jsdom/node_modules/acorn": { "version": "8.11.3", "license": "MIT", @@ -30404,47 +30127,6 @@ "node": ">=0.4.0" } }, - "node_modules/jest-environment-jsdom/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, "node_modules/jest-environment-jsdom/node_modules/cssstyle": { "version": "2.3.0", "license": "MIT", @@ -30493,13 +30175,6 @@ "node": ">= 6" } }, - "node_modules/jest-environment-jsdom/node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-environment-jsdom/node_modules/html-encoding-sniffer": { "version": "3.0.0", "license": "MIT", @@ -30563,16 +30238,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/jest-environment-jsdom/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-environment-jsdom/node_modules/tr46": { "version": "3.0.0", "license": "MIT", @@ -30643,86 +30308,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "17.0.31", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-node/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/jest-environment-node/node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-expo": { "version": "50.0.1", "license": "MIT", @@ -30790,69 +30375,6 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-haste-map/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, "node_modules/jest-haste-map/node_modules/has-flag": { "version": "4.0.0", "license": "MIT", @@ -30886,16 +30408,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-leak-detector": { "version": "29.4.1", "license": "MIT", @@ -30996,28 +30508,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-message-util/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -31088,86 +30578,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-mock/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-mock/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/jest-mock/node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "license": "MIT", @@ -31307,28 +30717,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-runner/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -31453,28 +30841,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-runtime/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -31562,28 +30928,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-snapshot/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -31666,28 +31010,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-util/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -31761,28 +31083,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-validate/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -32076,28 +31376,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-watcher/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -32198,86 +31476,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest/node_modules/@jest/types": { - "version": "29.6.3", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest/node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/jest/node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jimp-compact": { "version": "0.16.1", "license": "MIT" @@ -32329,6 +31527,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jsc-android": { "version": "250231.0.0", "license": "BSD-2-Clause" @@ -32677,13 +31881,15 @@ }, "node_modules/lazy-val": { "version": "1.0.5", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true }, "node_modules/lazystream": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "readable-stream": "^2.0.5" @@ -32902,20 +32108,23 @@ }, "node_modules/lodash.defaults": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/lodash.difference": { "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/lodash.flatten": { "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/lodash.isequal": { @@ -32924,8 +32133,9 @@ }, "node_modules/lodash.isplainobject": { "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/lodash.memoize": { @@ -32945,8 +32155,9 @@ }, "node_modules/lodash.union": { "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/log-symbols": { @@ -33314,6 +32525,175 @@ "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" } }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-fetch-happen/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/makeerror": { "version": "1.0.12", "license": "BSD-3-Clause", @@ -34071,8 +33451,9 @@ } }, "node_modules/metro/node_modules/ws": { - "version": "7.5.9", - "license": "MIT", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { "node": ">=8.3.0" }, @@ -34210,6 +33591,23 @@ "node": ">= 8" } }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, "node_modules/minipass-flush": { "version": "1.0.5", "license": "ISC", @@ -34230,6 +33628,18 @@ "node": ">=8" } }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/minizlib": { "version": "2.1.2", "license": "MIT", @@ -34486,16 +33896,38 @@ "node": ">= 10.13" } }, + "node_modules/node-abi": { + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", + "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-abort-controller": { "version": "3.1.1", "license": "MIT" }, "node_modules/node-addon-api": { "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", "dev": true, - "license": "MIT", "optional": true }, + "node_modules/node-api-version": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.0.tgz", + "integrity": "sha512-fthTTsi8CxaBXMaBAD7ST2uylwvsnYxh2PfaScwpMhos6KlSFajXQPcM4ogNE1q2s3Lbz9GCGqeIHC+C6OZnKg==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + } + }, "node_modules/node-dir": { "version": "0.1.17", "license": "MIT", @@ -34553,6 +33985,95 @@ "node": ">= 6.13.0" } }, + "node_modules/node-gyp": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", + "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/node-int64": { "version": "0.4.0", "license": "MIT" @@ -34620,6 +34141,21 @@ "url": "https://github.com/sponsors/antelle" } }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "license": "BSD-2-Clause", @@ -35846,6 +35382,16 @@ "path2d-polyfill": "^2.0.1" } }, + "node_modules/pe-library": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.0.tgz", + "integrity": "sha512-JAmVv2jGxmczplhHO7UoFGJ+pM/yMBpny3vNjwNFuaeQfzKlekQidZ8Ss8EJ0qee8wEQN4lY2IwtWx2oRfMsag==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/peek-stream": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", @@ -35857,6 +35403,32 @@ "through2": "^2.0.3" } }, + "node_modules/peggy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/peggy/-/peggy-4.0.3.tgz", + "integrity": "sha512-v7/Pt6kGYsfXsCrfb52q7/yg5jaAwiVaUMAPLPvy4DJJU6Wwr72t6nDIqIDkGfzd1B4zeVuTnQT0RGeOhe/uSA==", + "dev": true, + "dependencies": { + "@peggyjs/from-mem": "1.3.0", + "commander": "^12.1.0", + "source-map-generator": "0.8.0" + }, + "bin": { + "peggy": "bin/peggy.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/peggy/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/pend": { "version": "1.2.0", "dev": true, @@ -35989,14 +35561,24 @@ } }, "node_modules/plist": { - "version": "3.0.6", - "license": "MIT", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", "dependencies": { + "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" }, "engines": { - "node": ">=6" + "node": ">=10.4.0" + } + }, + "node_modules/plist/node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" } }, "node_modules/plist/node_modules/xmlbuilder": { @@ -36758,6 +36340,98 @@ "react-dom": ">=16.8.0" } }, + "node_modules/react-compiler-healthcheck": { + "version": "0.0.0-experimental-b130d5f-20240625", + "resolved": "https://registry.npmjs.org/react-compiler-healthcheck/-/react-compiler-healthcheck-0.0.0-experimental-b130d5f-20240625.tgz", + "integrity": "sha512-vf3Ipg+f19yOYQeRP938e5jWNEpwR6EX5pwBZdJUF9rt11vJ3ckgUVcF5qGWUU/7DB0N9MH1koBqwqMYabrBiQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "chalk": "4", + "fast-glob": "^3.3.2", + "ora": "5.4.1", + "yargs": "^17.7.2", + "zod": "^3.22.4", + "zod-validation-error": "^3.0.3" + }, + "bin": { + "react-compiler-healthcheck": "dist/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" + } + }, + "node_modules/react-compiler-healthcheck/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-compiler-healthcheck/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/react-compiler-healthcheck/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/react-compiler-healthcheck/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/react-compiler-healthcheck/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-compiler-healthcheck/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/react-compiler-runtime": { "resolved": "lib/react-compiler-runtime", "link": true @@ -36894,9 +36568,9 @@ } }, "node_modules/react-fast-pdf": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/react-fast-pdf/-/react-fast-pdf-1.0.13.tgz", - "integrity": "sha512-rF7NQZ26rJAI8ysRJaG71dl2c7AIq48ibbn7xCyF3lEZ/yOjA8BeR0utRwDjaHGtswQscgETboilhaaH5UtIYg==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/react-fast-pdf/-/react-fast-pdf-1.0.14.tgz", + "integrity": "sha512-iWomykxvnZtokIKpRK5xpaRfXz9ufrY7AVANtIBYsAZtX5/7VDlpIQwieljfMZwFc96TyceCnneufsgXpykTQw==", "dependencies": { "react-pdf": "^7.7.0", "react-window": "^1.8.10" @@ -37342,8 +37016,9 @@ } }, "node_modules/react-native-permissions": { - "version": "3.9.3", - "license": "MIT", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-3.10.1.tgz", + "integrity": "sha512-Gc5BxxpjZn4QNUDiVeHOO0vXh3AH7ToolmwTJozqC6DsxV7NAf3ttap+8BSmzDR8WxuAM3Cror+YNiBhHJx7/w==", "peerDependencies": { "react": ">=16.13.1", "react-native": ">=0.63.3", @@ -37368,17 +37043,17 @@ } }, "node_modules/react-native-plaid-link-sdk": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/react-native-plaid-link-sdk/-/react-native-plaid-link-sdk-11.5.0.tgz", - "integrity": "sha512-B3fwujxBS9nZwadXFcseU3nrYG7Ptob6p9eG/gXde65cqwErMaq2k1rVv3R17s/rpKnmU5Cx5pKOMmkxPUn08w==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/react-native-plaid-link-sdk/-/react-native-plaid-link-sdk-11.11.0.tgz", + "integrity": "sha512-Kmimhr6iOwCtIzsW7gygz48TzaZsdjnpgstJ2PM1q+THulOnx+BnkFu8UpLIGGkVe19E4wkxOAYL8kJ8vefNSQ==", "peerDependencies": { "react": "*", "react-native": "*" } }, "node_modules/react-native-qrcode-svg": { - "version": "6.2.0", - "license": "MIT", + "version": "6.3.0", + "resolved": "git+ssh://git@github.com/Expensify/react-native-qrcode-svg.git#295f87d45c0f10d9b50838ad28fa70e47d054c3b", "dependencies": { "prop-types": "^15.8.0", "qrcode": "^1.5.1" @@ -37386,7 +37061,7 @@ "peerDependencies": { "react": "*", "react-native": ">=0.63.4", - "react-native-svg": "^13.2.0" + "react-native-svg": ">=13.2.0" } }, "node_modules/react-native-quick-sqlite": { @@ -37429,14 +37104,15 @@ "license": "MIT" }, "node_modules/react-native-release-profiler": { - "version": "0.1.6", - "license": "MIT", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/react-native-release-profiler/-/react-native-release-profiler-0.2.1.tgz", + "integrity": "sha512-gDOwEXypd4Gu++nlKyaVLHPfwrVkkdBrsjMrQORYTTDqcrD/OfuNZ8YK7p+u5LUNjnPD4WmBF88C5dEW7iM1lg==", "workspaces": [ "example" ], "dependencies": { - "@react-native-community/cli": "^12.2.1", - "commander": "^11.1.0" + "commander": "^11.1.0", + "hermes-profile-transformer": "^0.0.9" }, "bin": { "react-native-release-profiler": "lib/commonjs/cli.js" @@ -37456,6 +37132,25 @@ "node": ">=16" } }, + "node_modules/react-native-release-profiler/node_modules/hermes-profile-transformer": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.9.tgz", + "integrity": "sha512-JYPUE9zA+W/hpTIGBV+t2ODvntataLLMfntoEcpEpKFDwdR6+Quk9SwLnWX9y2A3ZII6N4T8w3TUBC2ejsEGBw==", + "dependencies": { + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native-release-profiler/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/react-native-render-html": { "version": "6.3.1", "license": "BSD-2-Clause", @@ -37533,11 +37228,13 @@ } }, "node_modules/react-native-svg": { - "version": "14.1.0", - "license": "MIT", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.4.0.tgz", + "integrity": "sha512-zkBEbme/Dba4yqreg/oI2P6/6LrLywWY7HhaSwpU7Pb5COpTd2fV6/ShsgZz8GRFFdidUPwWmx01FITUsjhkmw==", "dependencies": { "css-select": "^5.1.0", - "css-tree": "^1.1.3" + "css-tree": "^1.1.3", + "warn-once": "0.1.1" }, "peerDependencies": { "react": "*", @@ -37809,8 +37506,9 @@ } }, "node_modules/react-native/node_modules/ws": { - "version": "6.2.2", - "license": "MIT", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "dependencies": { "async-limiter": "~1.0.0" } @@ -37984,10 +37682,6 @@ "react": "^18.2.0" } }, - "node_modules/react-test-renderer/node_modules/react-is": { - "version": "18.2.0", - "license": "MIT" - }, "node_modules/react-test-renderer/node_modules/scheduler": { "version": "0.23.0", "license": "MIT", @@ -38416,6 +38110,18 @@ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, "node_modules/read-cmd-shim": { "version": "4.0.0", "license": "ISC", @@ -38424,16 +38130,17 @@ } }, "node_modules/read-config-file": { - "version": "6.3.2", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.4.0.tgz", + "integrity": "sha512-uB5QOBeF84PT61GlV11OTV4jUGHAO3iDEOP6v9ygxhG6Bs9PLg7WsjNT6mtIX2G+x8lJTr4ZWNeG6LDTKkNf2Q==", "dev": true, - "license": "MIT", "dependencies": { - "config-file-ts": "^0.2.4", - "dotenv": "^9.0.2", - "dotenv-expand": "^5.1.0", + "config-file-ts": "0.2.8-rc1", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", "js-yaml": "^4.1.0", - "json5": "^2.2.0", - "lazy-val": "^1.0.4" + "json5": "^2.2.3", + "lazy-val": "^1.0.5" }, "engines": { "node": ">=12.0.0" @@ -38441,21 +38148,15 @@ }, "node_modules/read-config-file/node_modules/argparse": { "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/read-config-file/node_modules/dotenv": { - "version": "9.0.2", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=10" - } + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/read-config-file/node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -38626,8 +38327,9 @@ }, "node_modules/readdir-glob": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, - "license": "Apache-2.0", "peer": true, "dependencies": { "minimatch": "^5.1.0" @@ -38635,8 +38337,9 @@ }, "node_modules/readdir-glob/node_modules/brace-expansion": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "balanced-match": "^1.0.0" @@ -38644,8 +38347,9 @@ }, "node_modules/readdir-glob/node_modules/minimatch": { "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, - "license": "ISC", "peer": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -39011,6 +38715,19 @@ "version": "1.0.0", "license": "MIT" }, + "node_modules/resedit": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.0.tgz", + "integrity": "sha512-dbsZ0gk5opWPFlKMqvxCrLCuMZUVmsW3yTPT0tT4mYwo5fjQM8c4HMN9ZJt6dRDqDV/78m9SU4rv24PN4NiYaA==", + "dev": true, + "dependencies": { + "pe-library": "^0.4.0" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/reselect": { "version": "4.1.7", "dev": true, @@ -39283,8 +39000,9 @@ }, "node_modules/sanitize-filename": { "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", "dev": true, - "license": "WTFPL OR ISC", "dependencies": { "truncate-utf8-bytes": "^1.0.0" } @@ -39380,11 +39098,9 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "bin": { "semver": "bin/semver.js" }, @@ -39826,8 +39542,9 @@ }, "node_modules/slice-ansi": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "ansi-styles": "^4.0.0", @@ -39840,8 +39557,9 @@ }, "node_modules/slice-ansi/node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "color-convert": "^2.0.1" @@ -39855,8 +39573,9 @@ }, "node_modules/slice-ansi/node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "color-name": "~1.1.4" @@ -39867,8 +39586,9 @@ }, "node_modules/slice-ansi/node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "license": "MIT", "optional": true }, "node_modules/slugify": { @@ -39880,9 +39600,9 @@ }, "node_modules/smart-buffer": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, - "license": "MIT", - "optional": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -40088,6 +39808,34 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/sort-asc": { "version": "0.2.0", "license": "MIT", @@ -40135,6 +39883,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-generator": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/source-map-generator/-/source-map-generator-0.8.0.tgz", + "integrity": "sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "license": "BSD-3-Clause", @@ -40365,8 +40122,9 @@ }, "node_modules/stat-mode": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 6" } @@ -41107,8 +40865,9 @@ }, "node_modules/temp-file": { "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", "dev": true, - "license": "MIT", "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" @@ -41116,8 +40875,9 @@ }, "node_modules/temp-file/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -41459,16 +41219,18 @@ }, "node_modules/tmp": { "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.14" } }, "node_modules/tmp-promise": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", "dev": true, - "license": "MIT", "dependencies": { "tmp": "^0.2.0" } @@ -41609,8 +41371,9 @@ }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", "dev": true, - "license": "WTFPL", "dependencies": { "utf8-byte-length": "^1.0.1" } @@ -42415,9 +42178,10 @@ "license": "MIT" }, "node_modules/utf8-byte-length": { - "version": "1.0.4", - "dev": true, - "license": "WTFPL" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true }, "node_modules/util": { "version": "0.11.1", @@ -42503,8 +42267,9 @@ }, "node_modules/verror": { "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "assert-plus": "^1.0.0", @@ -43007,9 +42772,10 @@ } }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { - "version": "7.5.9", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.3.0" }, @@ -43632,8 +43398,8 @@ }, "node_modules/wide-align": { "version": "1.1.5", + "devOptional": true, "license": "ISC", - "optional": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -43771,8 +43537,9 @@ } }, "node_modules/ws": { - "version": "8.16.0", - "license": "MIT", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, @@ -43920,8 +43687,9 @@ }, "node_modules/zip-stream": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "archiver-utils": "^3.0.4", @@ -43934,8 +43702,9 @@ }, "node_modules/zip-stream/node_modules/archiver-utils": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "glob": "^7.2.3", @@ -43955,8 +43724,10 @@ }, "node_modules/zip-stream/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "ISC", "peer": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -43975,8 +43746,9 @@ }, "node_modules/zip-stream/node_modules/readable-stream": { "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "inherits": "^2.0.3", diff --git a/package.json b/package.json index 7af1d82e2569..683729319cad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.5-4", + "version": "9.0.11-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -60,13 +60,15 @@ "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", "workflow-test:generate": "ts-node workflow_tests/utils/preGenerateTest.ts", "setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem dev.new.expensify.com localhost 127.0.0.1", - "e2e-test-runner-build": "ncc build tests/e2e/testRunner.ts -o tests/e2e/dist/" + "e2e-test-runner-build": "ncc build tests/e2e/testRunner.ts -o tests/e2e/dist/", + "react-compiler-healthcheck": "react-compiler-healthcheck --verbose", + "generate-search-parser": "peggy --format es -o src/libs/SearchParser/searchParser.js src/libs/SearchParser/searchParser.peggy " }, "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.88", + "@expensify/react-native-live-markdown": "^0.1.107", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -79,12 +81,12 @@ "@fullstory/react-native": "^1.4.2", "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", - "@kie/act-js": "^2.6.0", + "@kie/act-js": "^2.6.2", "@kie/mock-github": "2.0.1", "@onfido/react-native-sdk": "10.6.0", "@react-native-camera-roll/camera-roll": "7.4.0", "@react-native-clipboard/clipboard": "^1.13.2", - "@react-native-community/geolocation": "3.2.1", + "@react-native-community/geolocation": "3.3.0", "@react-native-community/netinfo": "11.2.1", "@react-native-firebase/analytics": "^12.3.0", "@react-native-firebase/app": "^12.3.0", @@ -108,7 +110,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "2.0.19", + "expensify-common": "2.0.49", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -132,7 +134,7 @@ "react-content-loader": "^7.0.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "1.0.13", + "react-fast-pdf": "1.0.14", "react-map-gl": "^7.1.3", "react-native": "0.73.4", "react-native-android-location-enabler": "^2.0.1", @@ -159,19 +161,19 @@ "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", - "react-native-permissions": "^3.9.3", + "react-native-permissions": "^3.10.0", "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf", - "react-native-plaid-link-sdk": "11.5.0", - "react-native-qrcode-svg": "^6.2.0", + "react-native-plaid-link-sdk": "11.11.0", + "react-native-qrcode-svg": "git+https://github.com/Expensify/react-native-qrcode-svg", "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-quick-sqlite#abc91857d4b3efb2020ec43abd2a508563b64316", "react-native-reanimated": "^3.8.0", - "react-native-release-profiler": "^0.1.6", + "react-native-release-profiler": "^0.2.1", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.8.2", "react-native-screens": "3.32.0", "react-native-share": "^10.0.2", "react-native-sound": "^0.11.2", - "react-native-svg": "14.1.0", + "react-native-svg": "15.4.0", "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "3.8.0", @@ -208,7 +210,7 @@ "@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/profiler": "^0.10.11", "@perf-profiler/reporter": "^0.9.0", "@perf-profiler/types": "^0.8.0", "@react-native-community/eslint-config": "3.2.0", @@ -253,7 +255,7 @@ "babel-jest": "29.4.1", "babel-loader": "^9.1.3", "babel-plugin-module-resolver": "^5.0.0", - "babel-plugin-react-compiler": "^0.0.0-experimental-c23de8d-20240515", + "babel-plugin-react-compiler": "0.0.0-experimental-696af53-20240625", "babel-plugin-react-native-web": "^0.18.7", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-remove-console": "^6.9.4", @@ -265,14 +267,14 @@ "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", "electron": "^29.4.1", - "electron-builder": "24.13.2", + "electron-builder": "25.0.0", "eslint": "^8.57.0", "eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-expensify": "^2.0.52", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-jsdoc": "^46.2.6", - "eslint-plugin-react-compiler": "^0.0.0-experimental-53bb89e-20240515", + "eslint-plugin-react-compiler": "0.0.0-experimental-0998c1e-20240625", "eslint-plugin-react-native-a11y": "^3.3.0", "eslint-plugin-storybook": "^0.8.0", "eslint-plugin-testing-library": "^6.2.2", @@ -286,9 +288,11 @@ "memfs": "^4.6.0", "onchange": "^7.1.0", "patch-package": "^8.0.0", + "peggy": "^4.0.3", "portfinder": "^1.0.28", "prettier": "^2.8.8", "pusher-js-mock": "^0.3.3", + "react-compiler-healthcheck": "^0.0.0-experimental-b130d5f-20240625", "react-is": "^18.3.1", "react-native-clean-project": "^4.0.0-alpha4.0", "react-test-renderer": "18.2.0", diff --git a/patches/@expensify+react-native-live-markdown+0.1.85.patch b/patches/@expensify+react-native-live-markdown+0.1.85.patch deleted file mode 100644 index f745786a088e..000000000000 --- a/patches/@expensify+react-native-live-markdown+0.1.85.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/@expensify/react-native-live-markdown/lib/module/web/cursorUtils.js b/node_modules/@expensify/react-native-live-markdown/lib/module/web/cursorUtils.js -index e975fb2..6a4b510 100644 ---- a/node_modules/@expensify/react-native-live-markdown/lib/module/web/cursorUtils.js -+++ b/node_modules/@expensify/react-native-live-markdown/lib/module/web/cursorUtils.js -@@ -53,7 +53,7 @@ function setCursorPosition(target, start, end = null) { - // 3. Caret at the end of whole input, when pressing enter - // 4. All other placements - if (prevChar === '\n' && prevTextLength !== undefined && prevTextLength < textCharacters.length) { -- if (nextChar !== '\n') { -+ if (nextChar !== '\n' && i !== n - 1 && nextChar) { - range.setStart(textNodes[i + 1], 0); - } else if (i !== textNodes.length - 1) { - range.setStart(textNodes[i], 1); diff --git a/patches/@perf-profiler+android+0.12.1.patch b/patches/@perf-profiler+android+0.12.1.patch deleted file mode 100644 index e6e4a90d6ab4..000000000000 --- a/patches/@perf-profiler+android+0.12.1.patch +++ /dev/null @@ -1,26 +0,0 @@ -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-community+netinfo+11.2.1+002+turbomodule.patch b/patches/@react-native-community+netinfo+11.2.1+002+turbomodule.patch index f8e171008e14..c679bdbf73b9 100644 --- a/patches/@react-native-community+netinfo+11.2.1+002+turbomodule.patch +++ b/patches/@react-native-community+netinfo+11.2.1+002+turbomodule.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@react-native-community/netinfo/android/build.gradle b/node_modules/@react-native-community/netinfo/android/build.gradle -index 0d617ed..e93d64a 100644 +index 0d617ed..97439e6 100644 --- a/node_modules/@react-native-community/netinfo/android/build.gradle +++ b/node_modules/@react-native-community/netinfo/android/build.gradle @@ -3,9 +3,10 @@ buildscript { @@ -105,7 +105,6 @@ index 0d617ed..e93d64a 100644 + implementation 'com.facebook.react:react-native:+' + } } -\ No newline at end of file diff --git a/node_modules/@react-native-community/netinfo/android/src/main/java/com/reactnativecommunity/netinfo/NetInfoModuleImpl.java b/node_modules/@react-native-community/netinfo/android/src/main/java/com/reactnativecommunity/netinfo/NetInfoModuleImpl.java index 2c3280b..296bbfd 100644 --- a/node_modules/@react-native-community/netinfo/android/src/main/java/com/reactnativecommunity/netinfo/NetInfoModuleImpl.java @@ -1609,10 +1608,10 @@ index 095dd3b..596ace1 100644 +{"version":3,"names":["NetInfoStateType","exports","NetInfoCellularGeneration"],"sources":["types.ts"],"sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @format\n */\n\nexport enum NetInfoStateType {\n unknown = 'unknown',\n none = 'none',\n cellular = 'cellular',\n wifi = 'wifi',\n bluetooth = 'bluetooth',\n ethernet = 'ethernet',\n wimax = 'wimax',\n vpn = 'vpn',\n other = 'other',\n}\n\nexport type NetInfoMethodType = 'HEAD' | 'GET';\n\nexport enum NetInfoCellularGeneration {\n '2g' = '2g',\n '3g' = '3g',\n '4g' = '4g',\n '5g' = '5g',\n}\n\nexport interface NetInfoConnectedDetails {\n isConnectionExpensive: boolean;\n}\n\ninterface NetInfoConnectedState<\n T extends NetInfoStateType,\n D extends Record = Record,\n> {\n type: T;\n isConnected: true;\n isInternetReachable: boolean | null;\n details: D & NetInfoConnectedDetails;\n isWifiEnabled?: boolean;\n}\n\ninterface NetInfoDisconnectedState {\n type: T;\n isConnected: false;\n isInternetReachable: false;\n details: null;\n isWifiEnabled?: boolean;\n}\n\nexport interface NetInfoUnknownState {\n type: NetInfoStateType.unknown;\n isConnected: boolean | null;\n isInternetReachable: null;\n details: null;\n isWifiEnabled?: boolean;\n}\n\nexport type NetInfoNoConnectionState =\n NetInfoDisconnectedState;\nexport type NetInfoDisconnectedStates =\n | NetInfoUnknownState\n | NetInfoNoConnectionState;\n\nexport type NetInfoCellularState = NetInfoConnectedState<\n NetInfoStateType.cellular,\n {\n cellularGeneration: NetInfoCellularGeneration | null;\n carrier: string | null;\n }\n>;\nexport type NetInfoWifiState = NetInfoConnectedState<\n NetInfoStateType.wifi,\n {\n ssid: string | null;\n bssid: string | null;\n strength: number | null;\n ipAddress: string | null;\n subnet: string | null;\n frequency: number | null;\n linkSpeed: number | null;\n rxLinkSpeed: number | null;\n txLinkSpeed: number | null;\n }\n>;\nexport type NetInfoBluetoothState =\n NetInfoConnectedState;\nexport type NetInfoEthernetState = NetInfoConnectedState<\n NetInfoStateType.ethernet,\n {\n ipAddress: string | null;\n subnet: string | null;\n }\n>;\nexport type NetInfoWimaxState = NetInfoConnectedState;\nexport type NetInfoVpnState = NetInfoConnectedState;\nexport type NetInfoOtherState = NetInfoConnectedState;\nexport type NetInfoConnectedStates =\n | NetInfoCellularState\n | NetInfoWifiState\n | NetInfoBluetoothState\n | NetInfoEthernetState\n | NetInfoWimaxState\n | NetInfoVpnState\n | NetInfoOtherState;\n\nexport type NetInfoState = NetInfoDisconnectedStates | NetInfoConnectedStates;\n\nexport type NetInfoChangeHandler = (state: NetInfoState) => void;\nexport type NetInfoSubscription = () => void;\n\nexport interface NetInfoConfiguration {\n reachabilityUrl: string;\n reachabilityMethod?: NetInfoMethodType;\n reachabilityHeaders?: Record;\n reachabilityTest: (response: Response) => Promise;\n reachabilityLongTimeout: number;\n reachabilityShortTimeout: number;\n reachabilityRequestTimeout: number;\n reachabilityShouldRun: () => boolean;\n shouldFetchWiFiSSID: boolean;\n useNativeReachability: boolean;\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA,IASYA,gBAAgB,GAAAC,OAAA,CAAAD,gBAAA,0BAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAA,OAAhBA,gBAAgB;AAAA;AAAA,IAchBE,yBAAyB,GAAAD,OAAA,CAAAC,yBAAA,0BAAzBA,yBAAyB;EAAzBA,yBAAyB;EAAzBA,yBAAyB;EAAzBA,yBAAyB;EAAzBA,yBAAyB;EAAA,OAAzBA,yBAAyB;AAAA"} \ No newline at end of file diff --git a/node_modules/@react-native-community/netinfo/lib/module/index.js b/node_modules/@react-native-community/netinfo/lib/module/index.js -index 147c72e..02aa0db 100644 +index 147c72e..5de4e7c 100644 --- a/node_modules/@react-native-community/netinfo/lib/module/index.js +++ b/node_modules/@react-native-community/netinfo/lib/module/index.js -@@ -6,20 +6,23 @@ +@@ -6,20 +6,26 @@ * * @format */ @@ -1635,11 +1634,14 @@ index 147c72e..02aa0db 100644 const createState = () => { return new State(_configuration); }; ++ ++// Track ongoing requests ++let isRequestInProgress = false; + /** * Configures the library with the given configuration. Note that calling this will stop all * previously added listeners from being called again. It is best to call this right when your -@@ -27,23 +30,20 @@ const createState = () => { +@@ -27,23 +33,20 @@ const createState = () => { * * @param configuration The new configuration to set. */ @@ -1666,7 +1668,7 @@ index 147c72e..02aa0db 100644 /** * Returns a `Promise` that resolves to a `NetInfoState` object. * This function operates on the global singleton instance configured using `configure()` -@@ -52,27 +52,25 @@ export function configure(configuration) { +@@ -52,27 +55,33 @@ export function configure(configuration) { * * @returns A Promise which contains the current connection state. */ @@ -1689,14 +1691,22 @@ index 147c72e..02aa0db 100644 if (!_state) { _state = createState(); } -- - return _state._fetchCurrentState(); + +- return _state._fetchCurrentState(); ++ if (isRequestInProgress) { ++ return _state.latest(); // Return the latest state if a request is already in progress ++ } ++ ++ isRequestInProgress = true; ++ return _state._fetchCurrentState().finally(() => { ++ isRequestInProgress = false; ++ }); } + /** * Subscribe to the global singleton's connection information. The callback is called with a parameter of type * [`NetInfoState`](README.md#netinfostate) whenever the connection state changes. Your listener -@@ -84,18 +82,16 @@ export function refresh() { +@@ -84,18 +93,16 @@ export function refresh() { * * @returns A function which can be called to unsubscribe. */ @@ -1716,7 +1726,7 @@ index 147c72e..02aa0db 100644 /** * A React Hook into this library's singleton which updates when the connection state changes. * -@@ -103,12 +99,10 @@ export function addEventListener(listener) { +@@ -103,12 +110,10 @@ export function addEventListener(listener) { * * @returns The connection state. */ @@ -1729,7 +1739,7 @@ index 147c72e..02aa0db 100644 const [netInfo, setNetInfo] = useState({ type: Types.NetInfoStateType.unknown, isConnected: null, -@@ -120,6 +114,7 @@ export function useNetInfo(configuration) { +@@ -120,6 +125,7 @@ export function useNetInfo(configuration) { }, []); return netInfo; } @@ -1737,7 +1747,7 @@ index 147c72e..02aa0db 100644 /** * A React Hook which manages an isolated instance of the network info manager. * This is not a hook into a singleton shared state. NetInfo.configure, NetInfo.addEventListener, -@@ -129,7 +124,6 @@ export function useNetInfo(configuration) { +@@ -129,7 +135,6 @@ export function useNetInfo(configuration) { * * @returns the netInfo state and a refresh function */ @@ -1745,7 +1755,7 @@ index 147c72e..02aa0db 100644 export function useNetInfoInstance(isPaused = false, configuration) { const [networkInfoManager, setNetworkInfoManager] = useState(); const [netInfo, setNetInfo] = useState({ -@@ -142,8 +136,8 @@ export function useNetInfoInstance(isPaused = false, configuration) { +@@ -142,8 +147,8 @@ export function useNetInfoInstance(isPaused = false, configuration) { if (isPaused) { return; } @@ -2610,22 +2620,13 @@ index 6982220..b515270 100644 }; export default _default; diff --git a/node_modules/@react-native-community/netinfo/package.json b/node_modules/@react-native-community/netinfo/package.json -index 3c80db2..61e6564 100644 +index 3c80db2..15d214d 100644 --- a/node_modules/@react-native-community/netinfo/package.json +++ b/node_modules/@react-native-community/netinfo/package.json -@@ -48,6 +48,7 @@ - "network info" - ], - "peerDependencies": { -+ "react": "*", - "react-native": ">=0.59" +@@ -97,6 +97,14 @@ + "webpack-cli": "^3.3.10", + "webpack-dev-server": "^3.11.3" }, - "dependencies": {}, -@@ -121,5 +122,13 @@ - "yarn eslint --fix", - "git add" - ] -+ }, + "codegenConfig": { + "name": "RNCNetInfoSpec", + "type": "modules", @@ -2633,8 +2634,10 @@ index 3c80db2..61e6564 100644 + "android": { + "javaPackageName": "com.reactnativecommunity.netinfo" + } - } - } ++ }, + "repository": { + "type": "git", + "url": "https://github.com/react-native-netinfo/react-native-netinfo.git" diff --git a/node_modules/@react-native-community/netinfo/react-native-netinfo.podspec b/node_modules/@react-native-community/netinfo/react-native-netinfo.podspec index e34e728..9090eb1 100644 --- a/node_modules/@react-native-community/netinfo/react-native-netinfo.podspec @@ -3062,4 +3065,4 @@ index 878f7ba..0000000 - -#Files generated by the VS build -**/Generated Files/** -- +- \ No newline at end of file diff --git a/patches/eslint-plugin-react-compiler+0.0.0-experimental-53bb89e-20240515.patch b/patches/eslint-plugin-react-compiler+0.0.0-experimental-53bb89e-20240515.patch deleted file mode 100644 index f81f70944dd2..000000000000 --- a/patches/eslint-plugin-react-compiler+0.0.0-experimental-53bb89e-20240515.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/eslint-plugin-react-compiler/dist/index.js b/node_modules/eslint-plugin-react-compiler/dist/index.js -index a0f47a7..f649250 100644 ---- a/node_modules/eslint-plugin-react-compiler/dist/index.js -+++ b/node_modules/eslint-plugin-react-compiler/dist/index.js -@@ -69108,7 +69108,7 @@ const rule = { - return false; - } - let babelAST; -- if (context.filename.endsWith(".tsx") || context.filename.endsWith(".ts")) { -+ if (filename.endsWith(".tsx") || filename.endsWith(".ts")) { - try { - const { parse: babelParse } = require("@babel/parser"); - babelAST = babelParse(sourceCode, { diff --git a/patches/focus-trap+7.5.4.patch b/patches/focus-trap+7.5.4.patch new file mode 100644 index 000000000000..c7b2aef2b51f --- /dev/null +++ b/patches/focus-trap+7.5.4.patch @@ -0,0 +1,106 @@ +diff --git a/node_modules/focus-trap/dist/focus-trap.esm.js b/node_modules/focus-trap/dist/focus-trap.esm.js +index 10d56db..a6d76d8 100644 +--- a/node_modules/focus-trap/dist/focus-trap.esm.js ++++ b/node_modules/focus-trap/dist/focus-trap.esm.js +@@ -100,8 +100,8 @@ var isKeyForward = function isKeyForward(e) { + var isKeyBackward = function isKeyBackward(e) { + return isTabEvent(e) && e.shiftKey; + }; +-var delay = function delay(fn) { +- return setTimeout(fn, 0); ++var delay = function delay(fn, delayTime = 0) { ++ return setTimeout(() => setTimeout(fn, delayTime), 0); + }; + + // Array.find/findIndex() are not supported on IE; this replicates enough +@@ -283,7 +283,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) { + return node; + }; + var getInitialFocusNode = function getInitialFocusNode() { +- var node = getNodeForOption('initialFocus'); ++ var node = getNodeForOption('initialFocus', state.containers); + + // false explicitly indicates we want no initialFocus at all + if (node === false) { +@@ -744,7 +744,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) { + // that caused the focus trap activation. + state.delayInitialFocusTimer = config.delayInitialFocus ? delay(function () { + tryFocus(getInitialFocusNode()); +- }) : tryFocus(getInitialFocusNode()); ++ }, typeof config.delayInitialFocus === 'number' ? config.delayInitialFocus : undefined) : tryFocus(getInitialFocusNode()); + doc.addEventListener('focusin', checkFocusIn, true); + doc.addEventListener('mousedown', checkPointerDown, { + capture: true, +@@ -880,7 +880,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) { + tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)); + } + onPostDeactivate === null || onPostDeactivate === void 0 || onPostDeactivate(); +- }); ++ }, typeof config.delayInitialFocus === 'number' ? config.delayInitialFocus : undefined); + }; + if (returnFocus && checkCanReturnFocus) { + checkCanReturnFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)).then(finishDeactivation, finishDeactivation); +diff --git a/node_modules/focus-trap/index.d.ts b/node_modules/focus-trap/index.d.ts +index 400db1b..69f4b94 100644 +--- a/node_modules/focus-trap/index.d.ts ++++ b/node_modules/focus-trap/index.d.ts +@@ -16,7 +16,7 @@ declare module 'focus-trap' { + * `document.querySelector()` to find the DOM node), `false` to explicitly indicate + * an opt-out, or a function that returns a DOM node or `false`. + */ +- export type FocusTargetOrFalse = FocusTargetValueOrFalse | (() => FocusTargetValueOrFalse); ++ export type FocusTargetOrFalse = FocusTargetValueOrFalse | ((containers?: HTMLElement[]) => FocusTargetValueOrFalse | undefined); + + type MouseEventToBoolean = (event: MouseEvent | TouchEvent) => boolean; + type KeyboardEventToBoolean = (event: KeyboardEvent) => boolean; +@@ -185,7 +185,7 @@ declare module 'focus-trap' { + * This prevents elements within the focusable element from capturing + * the event that triggered the focus trap activation. + */ +- delayInitialFocus?: boolean; ++ delayInitialFocus?: boolean | number; + /** + * Default: `window.document`. Document where the focus trap will be active. + * This allows to use FocusTrap in an iFrame context. +diff --git a/node_modules/focus-trap/index.js b/node_modules/focus-trap/index.js +index de8e46a..bfc8b63 100644 +--- a/node_modules/focus-trap/index.js ++++ b/node_modules/focus-trap/index.js +@@ -63,8 +63,8 @@ const isKeyBackward = function (e) { + return isTabEvent(e) && e.shiftKey; + }; + +-const delay = function (fn) { +- return setTimeout(fn, 0); ++const delay = function (fn, delayTime = 0) { ++ return setTimeout(() => setTimeout(fn, delayTime), 0); + }; + + // Array.find/findIndex() are not supported on IE; this replicates enough +@@ -267,7 +267,7 @@ const createFocusTrap = function (elements, userOptions) { + }; + + const getInitialFocusNode = function () { +- let node = getNodeForOption('initialFocus'); ++ let node = getNodeForOption('initialFocus', state.containers); + + // false explicitly indicates we want no initialFocus at all + if (node === false) { +@@ -817,7 +817,7 @@ const createFocusTrap = function (elements, userOptions) { + state.delayInitialFocusTimer = config.delayInitialFocus + ? delay(function () { + tryFocus(getInitialFocusNode()); +- }) ++ }, typeof config.delayInitialFocus === 'number' ? config.delayInitialFocus : undefined) + : tryFocus(getInitialFocusNode()); + + doc.addEventListener('focusin', checkFocusIn, true); +@@ -989,7 +989,7 @@ const createFocusTrap = function (elements, userOptions) { + tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)); + } + onPostDeactivate?.(); +- }); ++ }, typeof config.delayInitialFocus === 'number' ? config.delayInitialFocus : undefined); + }; + + if (returnFocus && checkCanReturnFocus) { diff --git a/patches/react-compiler-healthcheck+0.0.0-experimental-b130d5f-20240625+001+initial.patch b/patches/react-compiler-healthcheck+0.0.0-experimental-b130d5f-20240625+001+initial.patch new file mode 100644 index 000000000000..d7c02701a636 --- /dev/null +++ b/patches/react-compiler-healthcheck+0.0.0-experimental-b130d5f-20240625+001+initial.patch @@ -0,0 +1,90 @@ +diff --git a/node_modules/react-compiler-healthcheck/dist/index.js b/node_modules/react-compiler-healthcheck/dist/index.js +index b427385..4bf23db 100755 +--- a/node_modules/react-compiler-healthcheck/dist/index.js ++++ b/node_modules/react-compiler-healthcheck/dist/index.js +@@ -69154,7 +69154,7 @@ var reactCompilerCheck = { + compile(source, path); + } + }, +- report() { ++ report(verbose) { + const totalComponents = + SucessfulCompilation.length + + countUniqueLocInEvents(OtherFailures) + +@@ -69164,6 +69164,50 @@ var reactCompilerCheck = { + `Successfully compiled ${SucessfulCompilation.length} out of ${totalComponents} components.` + ) + ); ++ ++ if (verbose) { ++ for (const compilation of [...SucessfulCompilation, ...ActionableFailures, ...OtherFailures]) { ++ const filename = compilation.fnLoc?.filename; ++ ++ if (compilation.kind === "CompileSuccess") { ++ const name = compilation.fnName; ++ const isHook = name?.startsWith('use'); ++ ++ if (name) { ++ console.log( ++ chalk.green( ++ `Successfully compiled ${isHook ? "hook" : "component" } [${name}](${filename})` ++ ) ++ ); ++ } else { ++ console.log(chalk.green(`Successfully compiled ${compilation.fnLoc?.filename}`)); ++ } ++ } ++ ++ if (compilation.kind === "CompileError") { ++ const { reason, severity, loc } = compilation.detail; ++ ++ const lnNo = loc.start?.line; ++ const colNo = loc.start?.column; ++ ++ const isTodo = severity === ErrorSeverity.Todo; ++ ++ console.log( ++ chalk[isTodo ? 'yellow' : 'red']( ++ `Failed to compile ${ ++ filename ++ }${ ++ lnNo !== undefined ? `:${lnNo}${ ++ colNo !== undefined ? `:${colNo}` : "" ++ }.` : "" ++ }` ++ ), ++ chalk[isTodo ? 'yellow' : 'red'](reason? `Reason: ${reason}` : "") ++ ); ++ console.log("\n"); ++ } ++ } ++ } + }, + }; + const JsFileExtensionRE = /(js|ts|jsx|tsx)$/; +@@ -69200,9 +69244,16 @@ function main() { + type: "string", + default: "**/+(*.{js,mjs,jsx,ts,tsx}|package.json)", + }) ++ .option('verbose', { ++ description: 'run with verbose logging', ++ type: 'boolean', ++ default: false, ++ alias: 'v', ++ }) + .parseSync(); + const spinner = ora("Checking").start(); + let src = argv.src; ++ let verbose = argv.verbose; + const globOptions = { + onlyFiles: true, + ignore: [ +@@ -69222,7 +69273,7 @@ function main() { + libraryCompatCheck.run(source, path); + } + spinner.stop(); +- reactCompilerCheck.report(); ++ reactCompilerCheck.report(verbose); + strictModeCheck.report(); + libraryCompatCheck.report(); + }); diff --git a/patches/react-compiler-healthcheck+0.0.0-experimental-b130d5f-20240625+002+enable-ref-identifiers.patch b/patches/react-compiler-healthcheck+0.0.0-experimental-b130d5f-20240625+002+enable-ref-identifiers.patch new file mode 100644 index 000000000000..6caa4ad4c373 --- /dev/null +++ b/patches/react-compiler-healthcheck+0.0.0-experimental-b130d5f-20240625+002+enable-ref-identifiers.patch @@ -0,0 +1,28 @@ +diff --git a/node_modules/react-compiler-healthcheck/dist/index.js b/node_modules/react-compiler-healthcheck/dist/index.js +index 4bf23db..fa2ab22 100755 +--- a/node_modules/react-compiler-healthcheck/dist/index.js ++++ b/node_modules/react-compiler-healthcheck/dist/index.js +@@ -69088,6 +69088,9 @@ const COMPILER_OPTIONS = { + compilationMode: "infer", + panicThreshold: "critical_errors", + logger: logger, ++ environment: { ++ enableTreatRefLikeIdentifiersAsRefs: true, ++ }, + }; + function isActionableDiagnostic(detail) { + switch (detail.severity) { +diff --git a/node_modules/react-compiler-healthcheck/src/checks/reactCompiler.ts b/node_modules/react-compiler-healthcheck/src/checks/reactCompiler.ts +index 09c9b9b..d2418e0 100644 +--- a/node_modules/react-compiler-healthcheck/src/checks/reactCompiler.ts ++++ b/node_modules/react-compiler-healthcheck/src/checks/reactCompiler.ts +@@ -51,6 +51,9 @@ const COMPILER_OPTIONS: Partial = { + compilationMode: "infer", + panicThreshold: "critical_errors", + logger, ++ environment: { ++ enableTreatRefLikeIdentifiersAsRefs: true, ++ }, + }; + + function isActionableDiagnostic(detail: CompilerErrorDetailOptions) { diff --git a/patches/react-compiler-healthcheck+0.0.0-experimental-b130d5f-20240625+003+json.patch b/patches/react-compiler-healthcheck+0.0.0-experimental-b130d5f-20240625+003+json.patch new file mode 100644 index 000000000000..a3de7a365889 --- /dev/null +++ b/patches/react-compiler-healthcheck+0.0.0-experimental-b130d5f-20240625+003+json.patch @@ -0,0 +1,73 @@ +diff --git a/node_modules/react-compiler-healthcheck/dist/index.js b/node_modules/react-compiler-healthcheck/dist/index.js +index fa2ab22..93be1fb 100755 +--- a/node_modules/react-compiler-healthcheck/dist/index.js ++++ b/node_modules/react-compiler-healthcheck/dist/index.js +@@ -69157,16 +69157,28 @@ var reactCompilerCheck = { + compile(source, path); + } + }, +- report(verbose) { ++ report(verbose, json) { + const totalComponents = + SucessfulCompilation.length + + countUniqueLocInEvents(OtherFailures) + + countUniqueLocInEvents(ActionableFailures); +- console.log( +- chalk.green( +- `Successfully compiled ${SucessfulCompilation.length} out of ${totalComponents} components.` +- ) +- ); ++ if (!json) { ++ console.log( ++ chalk.green( ++ `Successfully compiled ${SucessfulCompilation.length} out of ${totalComponents} components.` ++ ) ++ ); ++ } ++ ++ if (json) { ++ const extractFileName = (output) => output.fnLoc.filename; ++ const successfulFiles = SucessfulCompilation.map(extractFileName); ++ const unsuccessfulFiles = [...new Set([...OtherFailures, ...ActionableFailures].map(extractFileName))]; ++ console.log(JSON.stringify({ ++ success: successfulFiles, ++ failure: unsuccessfulFiles, ++ })); ++ } + + if (verbose) { + for (const compilation of [...SucessfulCompilation, ...ActionableFailures, ...OtherFailures]) { +@@ -69253,10 +69265,17 @@ function main() { + default: false, + alias: 'v', + }) ++ .option('json', { ++ description: 'print a list of compiled/not-compiled files as JSON', ++ type: 'boolean', ++ default: false, ++ alias: 'j', ++ }) + .parseSync(); + const spinner = ora("Checking").start(); + let src = argv.src; + let verbose = argv.verbose; ++ let json = argv.json; + const globOptions = { + onlyFiles: true, + ignore: [ +@@ -69276,9 +69295,12 @@ function main() { + libraryCompatCheck.run(source, path); + } + spinner.stop(); +- reactCompilerCheck.report(verbose); +- strictModeCheck.report(); +- libraryCompatCheck.report(); ++ reactCompilerCheck.report(verbose, json); ++ // using json option we only want to get list of files ++ if (!json) { ++ strictModeCheck.report(); ++ libraryCompatCheck.report(); ++ } + }); + } + main(); diff --git a/patches/react-native+0.73.4+014+fixPath.patch b/patches/react-native+0.73.4+015+fixPath.patch similarity index 100% rename from patches/react-native+0.73.4+014+fixPath.patch rename to patches/react-native+0.73.4+015+fixPath.patch diff --git a/patches/react-native+0.73.4+014+iOSCoreAnimationBorderRendering.patch b/patches/react-native+0.73.4+016+iOSCoreAnimationBorderRendering.patch similarity index 100% rename from patches/react-native+0.73.4+014+iOSCoreAnimationBorderRendering.patch rename to patches/react-native+0.73.4+016+iOSCoreAnimationBorderRendering.patch diff --git a/patches/react-native+0.73.4+015+copyStateOnClone.patch b/patches/react-native+0.73.4+017+copyStateOnClone.patch similarity index 100% rename from patches/react-native+0.73.4+015+copyStateOnClone.patch rename to patches/react-native+0.73.4+017+copyStateOnClone.patch diff --git a/patches/react-native+0.73.4+015+fixIOSWebViewCrash.patch b/patches/react-native+0.73.4+018+fixIOSWebViewCrash.patch similarity index 100% rename from patches/react-native+0.73.4+015+fixIOSWebViewCrash.patch rename to patches/react-native+0.73.4+018+fixIOSWebViewCrash.patch diff --git a/patches/react-native+0.73.4+016+fixClippedEmojis.patch b/patches/react-native+0.73.4+019+fixClippedEmojis.patch similarity index 100% rename from patches/react-native+0.73.4+016+fixClippedEmojis.patch rename to patches/react-native+0.73.4+019+fixClippedEmojis.patch diff --git a/patches/react-native+0.73.4+016+iOS-textinput-onscroll-event.patch b/patches/react-native+0.73.4+020+iOS-textinput-onscroll-event.patch similarity index 100% rename from patches/react-native+0.73.4+016+iOS-textinput-onscroll-event.patch rename to patches/react-native+0.73.4+020+iOS-textinput-onscroll-event.patch diff --git a/patches/react-native+0.73.4+017+iOS-fix-whitespace-support-sourcemap.patch b/patches/react-native+0.73.4+021+iOS-fix-whitespace-support-sourcemap.patch similarity index 100% rename from patches/react-native+0.73.4+017+iOS-fix-whitespace-support-sourcemap.patch rename to patches/react-native+0.73.4+021+iOS-fix-whitespace-support-sourcemap.patch diff --git a/patches/react-native+0.73.4+022+textInputClear.patch b/patches/react-native+0.73.4+022+textInputClear.patch new file mode 100644 index 000000000000..1cadce6a0783 --- /dev/null +++ b/patches/react-native+0.73.4+022+textInputClear.patch @@ -0,0 +1,66 @@ +diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +index 7ce04da..123968f 100644 +--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm ++++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +@@ -452,6 +452,12 @@ - (void)blur + [_backedTextInputView resignFirstResponder]; + } + ++- (void)clear ++{ ++ [self setTextAndSelection:_mostRecentEventCount value:@"" start:0 end:0]; ++ _mostRecentEventCount++; ++} ++ + - (void)setTextAndSelection:(NSInteger)eventCount + value:(NSString *__nullable)value + start:(NSInteger)start +diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h +index fe3376a..6a9a45f 100644 +--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h ++++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h +@@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN + @protocol RCTTextInputViewProtocol + - (void)focus; + - (void)blur; ++- (void)clear; + - (void)setTextAndSelection:(NSInteger)eventCount + value:(NSString *__nullable)value + start:(NSInteger)start +@@ -49,6 +50,19 @@ RCTTextInputHandleCommand(id componentView, const NSSt + return; + } + ++ if ([commandName isEqualToString:@"clear"]) { ++#if RCT_DEBUG ++ if ([args count] != 0) { ++ RCTLogError( ++ @"%@ command %@ received %d arguments, expected %d.", @"TextInput", commandName, (int)[args count], 0); ++ return; ++ } ++#endif ++ ++ [componentView clear]; ++ return; ++ } ++ + if ([commandName isEqualToString:@"setTextAndSelection"]) { + #if RCT_DEBUG + if ([args count] != 4) { +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +index 8496a7d..e6bcfc4 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +@@ -331,6 +331,12 @@ public class ReactTextInputManager extends BaseViewManager + #import +-#import + #import + #import + #import +@@ -32,7 +31,7 @@ + #endif // RCT_NEW_ARCH_ENABLED + + #ifdef RCT_NEW_ARCH_ENABLED +-@interface RNGestureHandlerModule () ++@interface RNGestureHandlerModule () + + @end + #else +@@ -66,9 +65,7 @@ - (void)invalidate + + _manager = nil; + +-#ifdef RCT_NEW_ARCH_ENABLED +- [self.bridge.surfacePresenter removeObserver:self]; +-#else ++#ifndef RCT_NEW_ARCH_ENABLED + [self.bridge.uiManager.observerCoordinator removeObserver:self]; + #endif // RCT_NEW_ARCH_ENABLED + } +@@ -113,9 +110,7 @@ - (void)setBridge:(RCTBridge *)bridge + eventDispatcher:bridge.eventDispatcher]; + _operations = [NSMutableArray new]; + +-#ifdef RCT_NEW_ARCH_ENABLED +- [bridge.surfacePresenter addObserver:self]; +-#else ++#ifndef RCT_NEW_ARCH_ENABLED + [bridge.uiManager.observerCoordinator addObserver:self]; + #endif // RCT_NEW_ARCH_ENABLED + } +@@ -241,27 +236,7 @@ - (void)addOperationBlock:(GestureHandlerOperation)operation + [_operations addObject:operation]; + } + +-#pragma mark - RCTSurfacePresenterObserver +- +-#ifdef RCT_NEW_ARCH_ENABLED +- +-- (void)didMountComponentsWithRootTag:(NSInteger)rootTag +-{ +- RCTAssertMainQueue(); +- +- if (_operations.count == 0) { +- return; +- } +- +- NSArray *operations = _operations; +- _operations = [NSMutableArray new]; +- +- for (GestureHandlerOperation operation in operations) { +- operation(self->_manager); +- } +-} +- +-#else ++#ifndef RCT_NEW_ARCH_ENABLED + + #pragma mark - RCTUIManagerObserver + diff --git a/patches/react-native-keyboard-controller+1.12.2.patch.patch b/patches/react-native-keyboard-controller+1.12.2.patch similarity index 100% rename from patches/react-native-keyboard-controller+1.12.2.patch.patch rename to patches/react-native-keyboard-controller+1.12.2.patch diff --git a/patches/react-native-plaid-link-sdk+11.11.0.patch b/patches/react-native-plaid-link-sdk+11.11.0.patch new file mode 100644 index 000000000000..28e492f6999f --- /dev/null +++ b/patches/react-native-plaid-link-sdk+11.11.0.patch @@ -0,0 +1,35 @@ +diff --git a/node_modules/react-native-plaid-link-sdk/ios/PLKFabricHelpers.h b/node_modules/react-native-plaid-link-sdk/ios/PLKFabricHelpers.h +index cf70d5e..4e34cd2 100644 +--- a/node_modules/react-native-plaid-link-sdk/ios/PLKFabricHelpers.h ++++ b/node_modules/react-native-plaid-link-sdk/ios/PLKFabricHelpers.h +@@ -5,8 +5,12 @@ + #if __has_include() + #import + #else ++#ifdef USE_FRAMEWORKS ++#import ++#else + #import + #endif ++#endif + + // copied from RCTFollyConvert + folly::dynamic PLKConvertIdToFollyDynamic(id json) +diff --git a/node_modules/react-native-plaid-link-sdk/react-native-plaid-link-sdk.podspec b/node_modules/react-native-plaid-link-sdk/react-native-plaid-link-sdk.podspec +index 7c60081..4a13a3c 100644 +--- a/node_modules/react-native-plaid-link-sdk/react-native-plaid-link-sdk.podspec ++++ b/node_modules/react-native-plaid-link-sdk/react-native-plaid-link-sdk.podspec +@@ -21,6 +21,13 @@ Pod::Spec.new do |s| + # we don't want this to be seen by Swift + s.private_header_files = 'ios/PLKFabricHelpers.h' + ++ if ENV['USE_FRAMEWORKS'] == '1' ++ s.pod_target_xcconfig = { ++ "OTHER_CFLAGS" => "$(inherited) -DUSE_FRAMEWORKS", ++ "OTHER_CPLUSPLUSFLAGS" => "$(inherited) -DUSE_FRAMEWORKS", ++ } ++ end ++ + if fabric_enabled + install_modules_dependencies(s) + else diff --git a/patches/react-native-plaid-link-sdk+11.5.0+001+initial.patch b/patches/react-native-plaid-link-sdk+11.5.0+001+initial.patch deleted file mode 100644 index 6035477256b7..000000000000 --- a/patches/react-native-plaid-link-sdk+11.5.0+001+initial.patch +++ /dev/null @@ -1,4 +0,0 @@ -diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.m b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.mm -similarity index 100% -rename from node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.m -rename to node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.mm diff --git a/patches/react-native-plaid-link-sdk+11.5.0+002+turbomodule.patch b/patches/react-native-plaid-link-sdk+11.5.0+002+turbomodule.patch deleted file mode 100644 index 7d5aab6c84cf..000000000000 --- a/patches/react-native-plaid-link-sdk+11.5.0+002+turbomodule.patch +++ /dev/null @@ -1,3287 +0,0 @@ -diff --git a/node_modules/react-native-plaid-link-sdk/README.md b/node_modules/react-native-plaid-link-sdk/README.md -index 93ebca6..7bea608 100644 ---- a/node_modules/react-native-plaid-link-sdk/README.md -+++ b/node_modules/react-native-plaid-link-sdk/README.md -@@ -49,7 +49,6 @@ cd ios && bundle install && bundle exec pod install - - AutoLinking should handle all of the Android setup. - -- - ### React Native Setup - - - To initialize `PlaidLink`, you will need to first create a `link_token` at [/link/token/create](https://plaid.com/docs/#create-link-token). Check out our [QuickStart guide](https://plaid.com/docs/quickstart/#introduction) for additional API information. -@@ -58,7 +57,13 @@ AutoLinking should handle all of the Android setup. - - ```javascript - import { Text } from 'react-native'; --import { PlaidLink, LinkSuccess, LinkExit, LinkLogLevel, LinkIOSPresentationStyle } from 'react-native-plaid-link-sdk'; -+import { -+ PlaidLink, -+ LinkSuccess, -+ LinkExit, -+ LinkLogLevel, -+ LinkIOSPresentationStyle, -+} from 'react-native-plaid-link-sdk'; - - const MyPlaidComponent = () => { - return ( -@@ -77,7 +82,7 @@ const MyPlaidComponent = () => { - // UI is always presented in full screen on Android. - iOSPresentationStyle={LinkIOSPresentationStyle.MODAL} - > -- Add Account -+ Add Account - - ); - }; -@@ -92,6 +97,7 @@ const MyPlaidComponent = () => { - ##### Android OAuth Requirements - - ###### Register your app package name -+ - 1. Log into your [Plaid Dashboard](https://dashboard.plaid.com/developers/api) and navigate to the API page under the Developers tab. - 2. Next to Allowed Android package names click "Configure" then "Add New Android Package Name". - 3. Enter your package name, for example `com.plaid.example`. -@@ -100,17 +106,16 @@ const MyPlaidComponent = () => { - ##### iOS OAuth Requirements - - For iOS OAuth to work, specific requirements must be met. -+ - 1. Redirect URIs must be [registered](https://plaid.com/docs/link/ios/#register-your-redirect-uri), and set up as [universal links](https://developer.apple.com/documentation/xcode/supporting-associated-domains). - 2. Your native iOS application, must be configured with your associated domain. See your iOS [set up universal links](https://plaid.com/docs/link/ios/#set-up-universal-links) for more information. - -- - ##### Link Token OAuth Requirements - - - On iOS you must configure your `link_token` with a [redirect_uri](https://plaid.com/docs/api/tokens/#link-token-create-request-redirect-uri) to support OAuth. When creating a `link_token` for initializing Link on Android, `android_package_name` must be specified and `redirect_uri` must be left blank. - - - On Android you must configure your `link_token` with an [android_package_name](https://plaid.com/docs/api/tokens/#link-token-create-request-android-package-name) to support OAuth. When creating a `link_token` for initializing Link on iOS, `android_package_name` must be left blank and `redirect_uri` should be used instead. - -- - #### To receive onEvent callbacks: - - The React Native Plaid module emits `onEvent` events throughout the account linking process — see [details here](https://plaid.com/docs/link/react-native/#onevent). To receive these events in your React Native app, wrap the `PlaidLink` react component with the following in order to listen for those events: -@@ -139,9 +144,9 @@ class PlaidEventContainer extends React.Component { - You can also use the `usePlaidEmitter` hook in react functional components: - - ```javascript -- usePlaidEmitter((event: LinkEvent) => { -- console.log(event) -- }) -+usePlaidEmitter((event: LinkEvent) => { -+ console.log(event); -+}); - ``` - - ## Upgrading -@@ -165,6 +170,8 @@ While these older versions are expected to continue to work without disruption, - | 11.0.2 | * | [4.0.0+] | 21 | 33 | >=5.0.0 | 14.0 | Active, supports Xcode 15.0.1 | - | 11.0.1 | * | [4.0.0+] | 21 | 33 | >=5.0.0 | 14.0 | Active, supports Xcode 15.0.1 | - | 11.0.0 | * | [4.0.0+] | 21 | 33 | >=5.0.0 | 14.0 | Active, supports Xcode 15.0.1 | -+| 10.13.0 | >= 0.66.0 | [3.14.3+] | 21 | 33 | >=4.7.2 | 11.0 | Active, supports Xcode 14 | -+| 10.12.0 | >= 0.66.0 | [3.14.3+] | 21 | 33 | >=4.7.1 | 11.0 | Active, supports Xcode 14 | - | 10.11.0 | >= 0.66.0 | [3.14.1+] | 21 | 33 | >=4.7.1 | 11.0 | Active, supports Xcode 14 | - | ~10.10.0~ | >= 0.66.0 | [3.14.2+] | 21 | 33 | >=4.7.1 | 11.0 | **Deprecated** | - | 10.9.1 | >= 0.66.0 | [3.14.1+] | 21 | 33 | >=4.7.0 | 11.0 | Active, supports Xcode 14 | -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/checksums/checksums.lock b/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/checksums/checksums.lock -deleted file mode 100644 -index b5da584..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/checksums/checksums.lock and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/checksums/md5-checksums.bin b/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/checksums/md5-checksums.bin -deleted file mode 100644 -index ef608b4..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/checksums/md5-checksums.bin and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/checksums/sha1-checksums.bin b/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/checksums/sha1-checksums.bin -deleted file mode 100644 -index 0856ae4..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/checksums/sha1-checksums.bin and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/dependencies-accessors/dependencies-accessors.lock b/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/dependencies-accessors/dependencies-accessors.lock -deleted file mode 100644 -index 12aea68..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/dependencies-accessors/dependencies-accessors.lock and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/dependencies-accessors/gc.properties b/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/dependencies-accessors/gc.properties -deleted file mode 100644 -index e69de29..0000000 -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/fileChanges/last-build.bin b/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/fileChanges/last-build.bin -deleted file mode 100644 -index f76dd23..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/fileChanges/last-build.bin and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/fileHashes/fileHashes.lock b/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/fileHashes/fileHashes.lock -deleted file mode 100644 -index 752a252..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/fileHashes/fileHashes.lock and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/gc.properties b/node_modules/react-native-plaid-link-sdk/android/.gradle/7.4.2/gc.properties -deleted file mode 100644 -index e69de29..0000000 -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/node_modules/react-native-plaid-link-sdk/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock -deleted file mode 100644 -index 470ca89..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/buildOutputCleanup/cache.properties b/node_modules/react-native-plaid-link-sdk/android/.gradle/buildOutputCleanup/cache.properties -deleted file mode 100644 -index 1439672..0000000 ---- a/node_modules/react-native-plaid-link-sdk/android/.gradle/buildOutputCleanup/cache.properties -+++ /dev/null -@@ -1,2 +0,0 @@ --#Thu Nov 09 09:41:17 PST 2023 --gradle.version=7.4.2 -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/checksums/checksums.lock b/node_modules/react-native-plaid-link-sdk/android/.gradle/checksums/checksums.lock -deleted file mode 100644 -index 34602e1..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/android/.gradle/checksums/checksums.lock and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/checksums/md5-checksums.bin b/node_modules/react-native-plaid-link-sdk/android/.gradle/checksums/md5-checksums.bin -deleted file mode 100644 -index 2420123..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/android/.gradle/checksums/md5-checksums.bin and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/checksums/sha1-checksums.bin b/node_modules/react-native-plaid-link-sdk/android/.gradle/checksums/sha1-checksums.bin -deleted file mode 100644 -index 1081852..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/android/.gradle/checksums/sha1-checksums.bin and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/android/.gradle/vcs-1/gc.properties b/node_modules/react-native-plaid-link-sdk/android/.gradle/vcs-1/gc.properties -deleted file mode 100644 -index e69de29..0000000 -diff --git a/node_modules/react-native-plaid-link-sdk/android/.idea/gradle.xml b/node_modules/react-native-plaid-link-sdk/android/.idea/gradle.xml -deleted file mode 100644 -index 0364d75..0000000 ---- a/node_modules/react-native-plaid-link-sdk/android/.idea/gradle.xml -+++ /dev/null -@@ -1,14 +0,0 @@ -- -- -- -- -- -- -- -\ No newline at end of file -diff --git a/node_modules/react-native-plaid-link-sdk/android/.idea/misc.xml b/node_modules/react-native-plaid-link-sdk/android/.idea/misc.xml -deleted file mode 100644 -index a318cae..0000000 ---- a/node_modules/react-native-plaid-link-sdk/android/.idea/misc.xml -+++ /dev/null -@@ -1,9 +0,0 @@ -- -- -- -- -- -- -- -- -\ No newline at end of file -diff --git a/node_modules/react-native-plaid-link-sdk/android/.idea/vcs.xml b/node_modules/react-native-plaid-link-sdk/android/.idea/vcs.xml -deleted file mode 100644 -index 6c0b863..0000000 ---- a/node_modules/react-native-plaid-link-sdk/android/.idea/vcs.xml -+++ /dev/null -@@ -1,6 +0,0 @@ -- -- -- -- -- -- -\ No newline at end of file -diff --git a/node_modules/react-native-plaid-link-sdk/android/build.gradle b/node_modules/react-native-plaid-link-sdk/android/build.gradle -index 2d9e2ce..e88208b 100644 ---- a/node_modules/react-native-plaid-link-sdk/android/build.gradle -+++ b/node_modules/react-native-plaid-link-sdk/android/build.gradle -@@ -12,7 +12,12 @@ allprojects { - - - buildscript { -- ext.kotlin_version = '1.8.22' -+ ext { -+ kotlin_version = '1.8.22' -+ } -+ ext.safeExtGet = {prop, fallback -> -+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback -+ } - repositories { - google() - mavenCentral() -@@ -25,10 +30,32 @@ buildscript { - } - } - -+def isNewArchitectureEnabled() { -+ // To opt-in for the New Architecture, you can either: -+ // - Set `newArchEnabled` to true inside the `gradle.properties` file -+ // - Invoke gradle with `-newArchEnabled=true` -+ // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` -+ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" -+} -+ -+if (isNewArchitectureEnabled()) { -+ apply plugin: "com.facebook.react" -+} -+ - apply plugin: 'com.android.library' - apply plugin: "kotlin-android" - - android { -+ -+ // Used to override the NDK path/version on internal CI or by allowing -+ // users to customize the NDK path/version from their root project (e.g. for M1 support) -+ if (rootProject.hasProperty("ndkPath")) { -+ ndkPath rootProject.ext.ndkPath -+ } -+ if (rootProject.hasProperty("ndkVersion")) { -+ ndkVersion rootProject.ext.ndkVersion -+ } -+ - def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger() - if (agpVersion >= 7) { - namespace 'com.plaid' -@@ -52,6 +79,14 @@ android { - } - } - -+ sourceSets.main { -+ java { -+ if (!isNewArchitectureEnabled()) { -+ srcDirs += 'src/paper/java' -+ } -+ } -+ } -+ - buildTypes { - release { - debuggable = false -diff --git a/node_modules/react-native-plaid-link-sdk/android/local.properties b/node_modules/react-native-plaid-link-sdk/android/local.properties -deleted file mode 100644 -index 0b4e321..0000000 ---- a/node_modules/react-native-plaid-link-sdk/android/local.properties -+++ /dev/null -@@ -1,8 +0,0 @@ --## This file must *NOT* be checked into Version Control Systems, --# as it contains information specific to your local configuration. --# --# Location of the SDK. This is only used by Gradle. --# For customization when using a Version Control System, please read the --# header note. --#Fri Aug 11 13:58:32 PDT 2023 --sdk.dir=/Users/dtroupe/Library/Android/sdk -diff --git a/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PLKEmbeddedViewManager.kt b/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PLKEmbeddedViewManager.kt -index c73011f..66fd266 100644 ---- a/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PLKEmbeddedViewManager.kt -+++ b/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PLKEmbeddedViewManager.kt -@@ -19,9 +19,9 @@ class PLKEmbeddedViewManager : SimpleViewManager() { - } - - override fun getExportedCustomBubblingEventTypeConstants(): Map { -- return mapOf( -- EVENT_NAME to mapOf( -- "phasedRegistrationNames" to mapOf( -+ return mutableMapOf( -+ EVENT_NAME to mutableMapOf( -+ "phasedRegistrationNames" to mutableMapOf( - "bubbled" to EVENT_NAME - ) - )) -diff --git a/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PlaidModule.kt b/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PlaidModule.kt -index 293374a..b79352e 100644 ---- a/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PlaidModule.kt -+++ b/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PlaidModule.kt -@@ -24,9 +24,9 @@ import org.json.JSONException - import org.json.JSONObject - import java.util.ArrayList - --@ReactModule(name = PlaidModule.TAG) -+@ReactModule(name = PlaidModule.NAME) - class PlaidModule internal constructor(reactContext: ReactApplicationContext) : -- ReactContextBaseJavaModule(reactContext), ActivityEventListener { -+ NativePlaidLinkModuleAndroidSpec(reactContext), ActivityEventListener { - - val mActivityResultManager by lazy { ActivityResultManager() } - -@@ -38,11 +38,11 @@ class PlaidModule internal constructor(reactContext: ReactApplicationContext) : - companion object { - private const val LINK_TOKEN_PREFIX = "link" - -- const val TAG = "PlaidAndroid" -+ const val NAME = "PlaidAndroid" - } - - override fun getName(): String { -- return PlaidModule.TAG -+ return NAME - } - - override fun initialize() { -@@ -78,7 +78,7 @@ class PlaidModule internal constructor(reactContext: ReactApplicationContext) : - - @ReactMethod - @Suppress("unused") -- fun startLinkActivityForResult( -+ override fun startLinkActivityForResult( - token: String, - noLoadingState: Boolean, - logLevel: String, -@@ -113,6 +113,10 @@ class PlaidModule internal constructor(reactContext: ReactApplicationContext) : - } - } - -+ override fun addListener(eventName: String?) = Unit -+ -+ override fun removeListeners(count: Double) = Unit -+ - private fun maybeGetStringField(obj: JSONObject, fieldName: String): String? { - if (obj.has(fieldName) && !TextUtils.isEmpty(obj.getString(fieldName))) { - return obj.getString(fieldName) -diff --git a/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PlaidPackage.java b/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PlaidPackage.java -index c59299e..d6b310e 100644 ---- a/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PlaidPackage.java -+++ b/node_modules/react-native-plaid-link-sdk/android/src/main/java/com/plaid/PlaidPackage.java -@@ -6,19 +6,54 @@ import java.util.List; - import java.util.Map; - - import com.facebook.react.TurboReactPackage; -+import com.facebook.react.ViewManagerOnDemandReactPackage; -+import com.facebook.react.bridge.ModuleSpec; - import com.facebook.react.bridge.NativeModule; - import com.facebook.react.bridge.ReactApplicationContext; -+import com.facebook.react.module.annotations.ReactModule; -+import com.facebook.react.module.annotations.ReactModuleList; - import com.facebook.react.module.model.ReactModuleInfo; - import com.facebook.react.module.model.ReactModuleInfoProvider; -+import com.facebook.react.turbomodule.core.interfaces.TurboModule; - import com.facebook.react.uimanager.ViewManager; - --@SuppressWarnings("unused") --public class PlaidPackage extends TurboReactPackage { -+import javax.annotation.Nonnull; -+import javax.annotation.Nullable; - -+@ReactModuleList(nativeModules = {PlaidModule.class}) -+public class PlaidPackage extends TurboReactPackage implements ViewManagerOnDemandReactPackage { -+ -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public List getViewManagerNames(ReactApplicationContext reactContext) { -+ return null; -+ } -+ -+ @Override -+ protected List getViewManagers(ReactApplicationContext reactContext) { -+ return null; -+ } -+ -+ /** -+ * {@inheritDoc} -+ */ - @Override -- public NativeModule getModule( -- String name, ReactApplicationContext reactContext) { -- return new PlaidModule(reactContext); -+ public @Nullable -+ ViewManager createViewManager( -+ ReactApplicationContext reactContext, String viewManagerName) { -+ return null; -+ } -+ -+ @Override -+ public NativeModule getModule(String name, @Nonnull ReactApplicationContext reactContext) { -+ switch (name) { -+ case PlaidModule.NAME: -+ return new PlaidModule(reactContext); -+ default: -+ return null; -+ } - } - - @Override -@@ -28,19 +63,44 @@ public class PlaidPackage extends TurboReactPackage { - - @Override - public ReactModuleInfoProvider getReactModuleInfoProvider() { -- return () -> { -- Map map = new HashMap<>(); -- map.put( -- "PlaidAndroid", -- new ReactModuleInfo( -- "PlaidAndroid", -- "com.reactlibrary.PlaidModule", -- false, -- false, -- true, -- false, -- false)); -- return map; -- }; -+ try { -+ Class reactModuleInfoProviderClass = -+ Class.forName("com.plaid.PlaidPackage$$ReactModuleInfoProvider"); -+ return (ReactModuleInfoProvider) reactModuleInfoProviderClass.newInstance(); -+ } catch (ClassNotFoundException e) { -+ // ReactModuleSpecProcessor does not run at build-time. Create this ReactModuleInfoProvider by -+ // hand. -+ return new ReactModuleInfoProvider() { -+ @Override -+ public Map getReactModuleInfos() { -+ final Map reactModuleInfoMap = new HashMap<>(); -+ -+ Class[] moduleList = -+ new Class[]{ -+ PlaidModule.class, -+ }; -+ -+ for (Class moduleClass : moduleList) { -+ ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); -+ -+ reactModuleInfoMap.put( -+ reactModule.name(), -+ new ReactModuleInfo( -+ reactModule.name(), -+ moduleClass.getName(), -+ reactModule.canOverrideExistingModule(), -+ reactModule.needsEagerInit(), -+ reactModule.hasConstants(), -+ reactModule.isCxxModule(), -+ TurboModule.class.isAssignableFrom(moduleClass))); -+ } -+ -+ return reactModuleInfoMap; -+ } -+ }; -+ } catch (InstantiationException | IllegalAccessException e) { -+ throw new RuntimeException( -+ "No ReactModuleInfoProvider for com.plaid.PlaidPackage$$ReactModuleInfoProvider", e); -+ } - } - } -diff --git a/node_modules/react-native-plaid-link-sdk/android/src/paper/java/com/plaid/NativePlaidLinkModuleAndroidSpec.java b/node_modules/react-native-plaid-link-sdk/android/src/paper/java/com/plaid/NativePlaidLinkModuleAndroidSpec.java -new file mode 100644 -index 0000000..fee5a11 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/android/src/paper/java/com/plaid/NativePlaidLinkModuleAndroidSpec.java -@@ -0,0 +1,46 @@ -+ -+/** -+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). -+ * -+ * Do not edit this file as changes may cause incorrect behavior and will be lost -+ * once the code is regenerated. -+ * -+ * @generated by codegen project: GenerateModuleJavaSpec.js -+ * -+ * @nolint -+ */ -+ -+package com.plaid; -+ -+import com.facebook.proguard.annotations.DoNotStrip; -+import com.facebook.react.bridge.Callback; -+import com.facebook.react.bridge.ReactApplicationContext; -+import com.facebook.react.bridge.ReactContextBaseJavaModule; -+import com.facebook.react.bridge.ReactMethod; -+import com.facebook.react.turbomodule.core.interfaces.TurboModule; -+import javax.annotation.Nonnull; -+ -+public abstract class NativePlaidLinkModuleAndroidSpec extends ReactContextBaseJavaModule implements TurboModule { -+ public static final String NAME = "PlaidAndroid"; -+ -+ public NativePlaidLinkModuleAndroidSpec(ReactApplicationContext reactContext) { -+ super(reactContext); -+ } -+ -+ @Override -+ public @Nonnull String getName() { -+ return NAME; -+ } -+ -+ @ReactMethod -+ @DoNotStrip -+ public abstract void startLinkActivityForResult(String token, boolean noLoadingState, String logLevel, Callback onSuccessCallback, Callback onExitCallback); -+ -+ @ReactMethod -+ @DoNotStrip -+ public abstract void addListener(String eventName); -+ -+ @ReactMethod -+ @DoNotStrip -+ public abstract void removeListeners(double count); -+} -diff --git a/node_modules/react-native-plaid-link-sdk/dist/EmbeddedLink/EmbeddedLinkView.js b/node_modules/react-native-plaid-link-sdk/dist/EmbeddedLink/EmbeddedLinkView.js -index c7b1e96..c429da7 100644 ---- a/node_modules/react-native-plaid-link-sdk/dist/EmbeddedLink/EmbeddedLinkView.js -+++ b/node_modules/react-native-plaid-link-sdk/dist/EmbeddedLink/EmbeddedLinkView.js -@@ -1,55 +1,69 @@ - import React from 'react'; - import NativeEmbeddedLinkView from './NativeEmbeddedLinkView'; - class EmbeddedEvent { -- constructor(event) { -- this.eventName = event.eventName; -- this.metadata = event.metadata; -- } -+ constructor(event) { -+ this.eventName = event.eventName; -+ this.metadata = event.metadata; -+ } - } - class EmbeddedExit { -- constructor(event) { -- this.error = event.error; -- this.metadata = event.metadata; -- } -+ constructor(event) { -+ this.error = event.error; -+ this.metadata = event.metadata; -+ } - } - class EmbeddedSuccess { -- constructor(event) { -- this.publicToken = event.publicToken; -- this.metadata = event.metadata; -- } -+ constructor(event) { -+ this.publicToken = event.publicToken; -+ this.metadata = event.metadata; -+ } - } --export const EmbeddedLinkView = (props) => { -- const { token, iOSPresentationStyle, onEvent, onSuccess, onExit, style } = props; -- const onEmbeddedEvent = (event) => { -- switch (event.nativeEvent.embeddedEventName) { -- case 'onSuccess': { -- if (!onSuccess) { -- return; -- } -- const embeddedSuccess = new EmbeddedSuccess(event.nativeEvent); -- onSuccess(embeddedSuccess); -- break; -- } -- case 'onExit': { -- if (!onExit) { -- return; -- } -- const embeddedExit = new EmbeddedExit(event.nativeEvent); -- onExit(embeddedExit); -- break; -- } -- case 'onEvent': { -- if (!onEvent) { -- return; -- } -- const embeddedEvent = new EmbeddedEvent(event.nativeEvent); -- onEvent(embeddedEvent); -- break; -- } -- default: { -- return; -- } -+export const EmbeddedLinkView = props => { -+ const { -+ token, -+ iOSPresentationStyle, -+ onEvent, -+ onSuccess, -+ onExit, -+ style, -+ } = props; -+ const onEmbeddedEvent = event => { -+ switch (event.nativeEvent.embeddedEventName) { -+ case 'onSuccess': { -+ if (!onSuccess) { -+ return; - } -- }; -- return ; -+ const embeddedSuccess = new EmbeddedSuccess(event.nativeEvent); -+ onSuccess(embeddedSuccess); -+ break; -+ } -+ case 'onExit': { -+ if (!onExit) { -+ return; -+ } -+ const embeddedExit = new EmbeddedExit(event.nativeEvent); -+ onExit(embeddedExit); -+ break; -+ } -+ case 'onEvent': { -+ if (!onEvent) { -+ return; -+ } -+ const embeddedEvent = new EmbeddedEvent(event.nativeEvent); -+ onEvent(embeddedEvent); -+ break; -+ } -+ default: { -+ return; -+ } -+ } -+ }; -+ return ( -+ -+ ); - }; -diff --git a/node_modules/react-native-plaid-link-sdk/dist/PlaidLink.d.ts b/node_modules/react-native-plaid-link-sdk/dist/PlaidLink.d.ts -index a48b319..43205dd 100644 ---- a/node_modules/react-native-plaid-link-sdk/dist/PlaidLink.d.ts -+++ b/node_modules/react-native-plaid-link-sdk/dist/PlaidLink.d.ts -@@ -4,9 +4,9 @@ import { LinkEventListener, PlaidLinkComponentProps, PlaidLinkProps } from './Ty - * A hook that registers a listener on the Plaid emitter for the 'onEvent' type. - * The listener is cleaned up when this view is unmounted - * -- * @param LinkEventListener the listener to call -+ * @param linkEventListener the listener to call - */ --export declare const usePlaidEmitter: (LinkEventListener: LinkEventListener) => void; -+export declare const usePlaidEmitter: (linkEventListener: LinkEventListener) => void; - export declare const openLink: (props: PlaidLinkProps) => Promise; - export declare const dismissLink: () => void; - export declare const PlaidLink: (props: PlaidLinkComponentProps) => React.JSX.Element; -diff --git a/node_modules/react-native-plaid-link-sdk/dist/PlaidLink.js b/node_modules/react-native-plaid-link-sdk/dist/PlaidLink.js -index 21da2bc..6c43633 100644 ---- a/node_modules/react-native-plaid-link-sdk/dist/PlaidLink.js -+++ b/node_modules/react-native-plaid-link-sdk/dist/PlaidLink.js -@@ -1,83 +1,146 @@ --var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { -- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } -- return new (P || (P = Promise))(function (resolve, reject) { -- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } -- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } -- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } -- step((generator = generator.apply(thisArg, _arguments || [])).next()); -+var __awaiter = -+ (this && this.__awaiter) || -+ function(thisArg, _arguments, P, generator) { -+ function adopt(value) { -+ return value instanceof P -+ ? value -+ : new P(function(resolve) { -+ resolve(value); -+ }); -+ } -+ return new (P || (P = Promise))(function(resolve, reject) { -+ function fulfilled(value) { -+ try { -+ step(generator.next(value)); -+ } catch (e) { -+ reject(e); -+ } -+ } -+ function rejected(value) { -+ try { -+ step(generator['throw'](value)); -+ } catch (e) { -+ reject(e); -+ } -+ } -+ function step(result) { -+ result.done -+ ? resolve(result.value) -+ : adopt(result.value).then(fulfilled, rejected); -+ } -+ step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); --}; -+ }; -+var _a; - import React, { useEffect } from 'react'; --import { NativeEventEmitter, NativeModules, Platform, TouchableOpacity, } from 'react-native'; --import { LinkIOSPresentationStyle, LinkLogLevel, } from './Types'; -+import { NativeEventEmitter, Platform, TouchableOpacity } from 'react-native'; -+import { LinkIOSPresentationStyle, LinkLogLevel } from './Types'; -+import RNLinksdkAndroid from './fabric/NativePlaidLinkModuleAndroid'; -+import RNLinksdkiOS from './fabric/NativePlaidLinkModuleiOS'; -+const RNLinksdk = -+ (_a = Platform.OS === 'android' ? RNLinksdkAndroid : RNLinksdkiOS) !== null && -+ _a !== void 0 -+ ? _a -+ : undefined; - /** - * A hook that registers a listener on the Plaid emitter for the 'onEvent' type. - * The listener is cleaned up when this view is unmounted - * -- * @param LinkEventListener the listener to call -+ * @param linkEventListener the listener to call - */ --export const usePlaidEmitter = (LinkEventListener) => { -- useEffect(() => { -- const emitter = new NativeEventEmitter(Platform.OS === 'ios' -- ? NativeModules.RNLinksdk -- : NativeModules.PlaidAndroid); -- const listener = emitter.addListener('onEvent', LinkEventListener); -- // Clean up after this effect: -- return function cleanup() { -- listener.remove(); -- }; -- }, []); -+export const usePlaidEmitter = linkEventListener => { -+ useEffect(() => { -+ const emitter = new NativeEventEmitter(RNLinksdk); -+ const listener = emitter.addListener('onEvent', linkEventListener); -+ // Clean up after this effect: -+ return function cleanup() { -+ listener.remove(); -+ }; -+ }, []); - }; --export const openLink = (props) => __awaiter(void 0, void 0, void 0, function* () { -- var _a, _b; -+export const openLink = props => -+ __awaiter(void 0, void 0, void 0, function*() { -+ var _b, _c; - let config = props.tokenConfig; -- let noLoadingState = (_a = config.noLoadingState) !== null && _a !== void 0 ? _a : false; -+ let noLoadingState = -+ (_b = config.noLoadingState) !== null && _b !== void 0 ? _b : false; - if (Platform.OS === 'android') { -- NativeModules.PlaidAndroid.startLinkActivityForResult(config.token, noLoadingState, (_b = config.logLevel) !== null && _b !== void 0 ? _b : LinkLogLevel.ERROR, (result) => { -- if (props.onSuccess != null) { -- props.onSuccess(result); -- } -- }, (result) => { -- if (props.onExit != null) { -- if (result.error != null && result.error.displayMessage != null) { -- //TODO(RNSDK-118): Remove errorDisplayMessage field in next major update. -- result.error.errorDisplayMessage = result.error.displayMessage; -- } -- props.onExit(result); -+ if (RNLinksdkAndroid === null) { -+ throw new Error( -+ '[react-native-plaid-link-sdk] RNLinksdkAndroid is not defined', -+ ); -+ } -+ RNLinksdkAndroid.startLinkActivityForResult( -+ config.token, -+ noLoadingState, -+ (_c = config.logLevel) !== null && _c !== void 0 -+ ? _c -+ : LinkLogLevel.ERROR, -+ // @ts-ignore we use Object type in the spec file as it maps to NSDictionary and ReadableMap -+ result => { -+ if (props.onSuccess != null) { -+ props.onSuccess(result); -+ } -+ }, -+ result => { -+ if (props.onExit != null) { -+ if (result.error != null && result.error.displayMessage != null) { -+ //TODO(RNSDK-118): Remove errorDisplayMessage field in next major update. -+ result.error.errorDisplayMessage = result.error.displayMessage; - } -- }); -- } -- else { -- NativeModules.RNLinksdk.create(config.token, noLoadingState); -- let presentFullScreen = props.iOSPresentationStyle == LinkIOSPresentationStyle.FULL_SCREEN; -- NativeModules.RNLinksdk.open(presentFullScreen, (result) => { -- if (props.onSuccess != null) { -- props.onSuccess(result); -- } -- }, (error, result) => { -- if (props.onExit != null) { -- if (error) { -- var data = result || {}; -- data.error = error; -- props.onExit(data); -- } -- else { -- props.onExit(result); -- } -+ props.onExit(result); -+ } -+ }, -+ ); -+ } else { -+ if (RNLinksdkiOS === null) { -+ throw new Error( -+ '[react-native-plaid-link-sdk] RNLinksdkiOS is not defined', -+ ); -+ } -+ RNLinksdkiOS.create(config.token, noLoadingState); -+ let presentFullScreen = -+ props.iOSPresentationStyle == LinkIOSPresentationStyle.FULL_SCREEN; -+ RNLinksdkiOS.open( -+ presentFullScreen, -+ // @ts-ignore we use Object type in the spec file as it maps to NSDictionary and ReadableMap -+ result => { -+ if (props.onSuccess != null) { -+ props.onSuccess(result); -+ } -+ }, -+ (error, result) => { -+ if (props.onExit != null) { -+ if (error) { -+ var data = result || {}; -+ data.error = error; -+ props.onExit(data); -+ } else { -+ props.onExit(result); - } -- }); -+ } -+ }, -+ ); - } --}); -+ }); - export const dismissLink = () => { -- if (Platform.OS === 'ios') { -- NativeModules.RNLinksdk.dismiss(); -+ if (Platform.OS === 'ios') { -+ if (RNLinksdkiOS === null) { -+ throw new Error( -+ '[react-native-plaid-link-sdk] RNLinksdkiOS is not defined', -+ ); - } -+ RNLinksdkiOS.dismiss(); -+ } - }; --export const PlaidLink = (props) => { -- function onPress() { -- var _a; -- (_a = props.onPress) === null || _a === void 0 ? void 0 : _a.call(props); -- openLink(props); -- } -- return {props.children}; -+export const PlaidLink = props => { -+ function onPress() { -+ var _a; -+ (_a = props.onPress) === null || _a === void 0 ? void 0 : _a.call(props); -+ openLink(props); -+ } -+ return ( -+ // @ts-ignore some types directories misconfiguration -+ {props.children} -+ ); - }; -diff --git a/node_modules/react-native-plaid-link-sdk/dist/Types.js b/node_modules/react-native-plaid-link-sdk/dist/Types.js -index 184adad..11b34e3 100644 ---- a/node_modules/react-native-plaid-link-sdk/dist/Types.js -+++ b/node_modules/react-native-plaid-link-sdk/dist/Types.js -@@ -1,430 +1,681 @@ - export var LinkLogLevel; --(function (LinkLogLevel) { -- LinkLogLevel["DEBUG"] = "debug"; -- LinkLogLevel["INFO"] = "info"; -- LinkLogLevel["WARN"] = "warn"; -- LinkLogLevel["ERROR"] = "error"; -+(function(LinkLogLevel) { -+ LinkLogLevel['DEBUG'] = 'debug'; -+ LinkLogLevel['INFO'] = 'info'; -+ LinkLogLevel['WARN'] = 'warn'; -+ LinkLogLevel['ERROR'] = 'error'; - })(LinkLogLevel || (LinkLogLevel = {})); - export var PlaidEnvironment; --(function (PlaidEnvironment) { -- PlaidEnvironment["PRODUCTION"] = "production"; -- PlaidEnvironment["DEVELOPMENT"] = "development"; -- PlaidEnvironment["SANDBOX"] = "sandbox"; -+(function(PlaidEnvironment) { -+ PlaidEnvironment['PRODUCTION'] = 'production'; -+ PlaidEnvironment['DEVELOPMENT'] = 'development'; -+ PlaidEnvironment['SANDBOX'] = 'sandbox'; - })(PlaidEnvironment || (PlaidEnvironment = {})); - export var PlaidProduct; --(function (PlaidProduct) { -- PlaidProduct["ASSETS"] = "assets"; -- PlaidProduct["AUTH"] = "auth"; -- PlaidProduct["DEPOSIT_SWITCH"] = "deposit_switch"; -- PlaidProduct["IDENTITY"] = "identity"; -- PlaidProduct["INCOME"] = "income"; -- PlaidProduct["INVESTMENTS"] = "investments"; -- PlaidProduct["LIABILITIES"] = "liabilities"; -- PlaidProduct["LIABILITIES_REPORT"] = "liabilities_report"; -- PlaidProduct["PAYMENT_INITIATION"] = "payment_initiation"; -- PlaidProduct["TRANSACTIONS"] = "transactions"; -+(function(PlaidProduct) { -+ PlaidProduct['ASSETS'] = 'assets'; -+ PlaidProduct['AUTH'] = 'auth'; -+ PlaidProduct['DEPOSIT_SWITCH'] = 'deposit_switch'; -+ PlaidProduct['IDENTITY'] = 'identity'; -+ PlaidProduct['INCOME'] = 'income'; -+ PlaidProduct['INVESTMENTS'] = 'investments'; -+ PlaidProduct['LIABILITIES'] = 'liabilities'; -+ PlaidProduct['LIABILITIES_REPORT'] = 'liabilities_report'; -+ PlaidProduct['PAYMENT_INITIATION'] = 'payment_initiation'; -+ PlaidProduct['TRANSACTIONS'] = 'transactions'; - })(PlaidProduct || (PlaidProduct = {})); - export var LinkAccountType; --(function (LinkAccountType) { -- LinkAccountType["CREDIT"] = "credit"; -- LinkAccountType["DEPOSITORY"] = "depository"; -- LinkAccountType["INVESTMENT"] = "investment"; -- LinkAccountType["LOAN"] = "loan"; -- LinkAccountType["OTHER"] = "other"; -+(function(LinkAccountType) { -+ LinkAccountType['CREDIT'] = 'credit'; -+ LinkAccountType['DEPOSITORY'] = 'depository'; -+ LinkAccountType['INVESTMENT'] = 'investment'; -+ LinkAccountType['LOAN'] = 'loan'; -+ LinkAccountType['OTHER'] = 'other'; - })(LinkAccountType || (LinkAccountType = {})); - export var LinkAccountSubtypes; --(function (LinkAccountSubtypes) { -- LinkAccountSubtypes["ALL"] = "all"; -- LinkAccountSubtypes["CREDIT_CARD"] = "credit card"; -- LinkAccountSubtypes["PAYPAL"] = "paypal"; -- LinkAccountSubtypes["AUTO"] = "auto"; -- LinkAccountSubtypes["BUSINESS"] = "business"; -- LinkAccountSubtypes["COMMERCIAL"] = "commercial"; -- LinkAccountSubtypes["CONSTRUCTION"] = "construction"; -- LinkAccountSubtypes["CONSUMER"] = "consumer"; -- LinkAccountSubtypes["HOME_EQUITY"] = "home equity"; -- LinkAccountSubtypes["LINE_OF_CREDIT"] = "line of credit"; -- LinkAccountSubtypes["LOAN"] = "loan"; -- LinkAccountSubtypes["MORTGAGE"] = "mortgage"; -- LinkAccountSubtypes["OVERDRAFT"] = "overdraft"; -- LinkAccountSubtypes["STUDENT"] = "student"; -- LinkAccountSubtypes["CASH_MANAGEMENT"] = "cash management"; -- LinkAccountSubtypes["CD"] = "cd"; -- LinkAccountSubtypes["CHECKING"] = "checking"; -- LinkAccountSubtypes["EBT"] = "ebt"; -- LinkAccountSubtypes["HSA"] = "hsa"; -- LinkAccountSubtypes["MONEY_MARKET"] = "money market"; -- LinkAccountSubtypes["PREPAID"] = "prepaid"; -- LinkAccountSubtypes["SAVINGS"] = "savings"; -- LinkAccountSubtypes["FOUR_0_1_A"] = "401a"; -- LinkAccountSubtypes["FOUR_0_1_K"] = "401k"; -- LinkAccountSubtypes["FOUR_0_3_B"] = "403B"; -- LinkAccountSubtypes["FOUR_5_7_B"] = "457b"; -- LinkAccountSubtypes["FIVE_2_9"] = "529"; -- LinkAccountSubtypes["BROKERAGE"] = "brokerage"; -- LinkAccountSubtypes["CASH_ISA"] = "cash isa"; -- LinkAccountSubtypes["EDUCATION_SAVINGS_ACCOUNT"] = "education savings account"; -- LinkAccountSubtypes["FIXED_ANNUNITY"] = "fixed annuity"; -- LinkAccountSubtypes["GIC"] = "gic"; -- LinkAccountSubtypes["HEALTH_REIMBURSEMENT_ARRANGEMENT"] = "health reimbursement arrangement"; -- LinkAccountSubtypes["IRA"] = "ira"; -- LinkAccountSubtypes["ISA"] = "isa"; -- LinkAccountSubtypes["KEOGH"] = "keogh"; -- LinkAccountSubtypes["LIF"] = "lif"; -- LinkAccountSubtypes["LIRA"] = "lira"; -- LinkAccountSubtypes["LRIF"] = "lrif"; -- LinkAccountSubtypes["LRSP"] = "lrsp"; -- LinkAccountSubtypes["MUTUAL_FUND"] = "mutual fund"; -- LinkAccountSubtypes["NON_TAXABLE_BROKERAGE_ACCOUNT"] = "non-taxable brokerage account"; -- LinkAccountSubtypes["PENSION"] = "pension"; -- LinkAccountSubtypes["PLAN"] = "plan"; -- LinkAccountSubtypes["PRIF"] = "prif"; -- LinkAccountSubtypes["PROFIT_SHARING_PLAN"] = "profit sharing plan"; -- LinkAccountSubtypes["RDSP"] = "rdsp"; -- LinkAccountSubtypes["RESP"] = "resp"; -- LinkAccountSubtypes["RETIREMENT"] = "retirement"; -- LinkAccountSubtypes["RLIF"] = "rlif"; -- LinkAccountSubtypes["ROTH_401K"] = "roth 401k"; -- LinkAccountSubtypes["ROTH"] = "roth"; -- LinkAccountSubtypes["RRIF"] = "rrif"; -- LinkAccountSubtypes["RRSP"] = "rrsp"; -- LinkAccountSubtypes["SARSEP"] = "sarsep"; -- LinkAccountSubtypes["SEP_IRA"] = "sep ira"; -- LinkAccountSubtypes["SIMPLE_IRA"] = "simple ira"; -- LinkAccountSubtypes["SIPP"] = "sipp"; -- LinkAccountSubtypes["STOCK_PLAN"] = "stock plan"; -- LinkAccountSubtypes["TFSA"] = "tfsa"; -- LinkAccountSubtypes["THRIFT_SAVINGS_PLAN"] = "thrift savings plan"; -- LinkAccountSubtypes["TRUST"] = "trust"; -- LinkAccountSubtypes["UGMA"] = "ugma"; -- LinkAccountSubtypes["UTMA"] = "utma"; -- LinkAccountSubtypes["VARIABLE_ANNUITY"] = "variable annuity"; -+(function(LinkAccountSubtypes) { -+ LinkAccountSubtypes['ALL'] = 'all'; -+ LinkAccountSubtypes['CREDIT_CARD'] = 'credit card'; -+ LinkAccountSubtypes['PAYPAL'] = 'paypal'; -+ LinkAccountSubtypes['AUTO'] = 'auto'; -+ LinkAccountSubtypes['BUSINESS'] = 'business'; -+ LinkAccountSubtypes['COMMERCIAL'] = 'commercial'; -+ LinkAccountSubtypes['CONSTRUCTION'] = 'construction'; -+ LinkAccountSubtypes['CONSUMER'] = 'consumer'; -+ LinkAccountSubtypes['HOME_EQUITY'] = 'home equity'; -+ LinkAccountSubtypes['LINE_OF_CREDIT'] = 'line of credit'; -+ LinkAccountSubtypes['LOAN'] = 'loan'; -+ LinkAccountSubtypes['MORTGAGE'] = 'mortgage'; -+ LinkAccountSubtypes['OVERDRAFT'] = 'overdraft'; -+ LinkAccountSubtypes['STUDENT'] = 'student'; -+ LinkAccountSubtypes['CASH_MANAGEMENT'] = 'cash management'; -+ LinkAccountSubtypes['CD'] = 'cd'; -+ LinkAccountSubtypes['CHECKING'] = 'checking'; -+ LinkAccountSubtypes['EBT'] = 'ebt'; -+ LinkAccountSubtypes['HSA'] = 'hsa'; -+ LinkAccountSubtypes['MONEY_MARKET'] = 'money market'; -+ LinkAccountSubtypes['PREPAID'] = 'prepaid'; -+ LinkAccountSubtypes['SAVINGS'] = 'savings'; -+ LinkAccountSubtypes['FOUR_0_1_A'] = '401a'; -+ LinkAccountSubtypes['FOUR_0_1_K'] = '401k'; -+ LinkAccountSubtypes['FOUR_0_3_B'] = '403B'; -+ LinkAccountSubtypes['FOUR_5_7_B'] = '457b'; -+ LinkAccountSubtypes['FIVE_2_9'] = '529'; -+ LinkAccountSubtypes['BROKERAGE'] = 'brokerage'; -+ LinkAccountSubtypes['CASH_ISA'] = 'cash isa'; -+ LinkAccountSubtypes['EDUCATION_SAVINGS_ACCOUNT'] = -+ 'education savings account'; -+ LinkAccountSubtypes['FIXED_ANNUNITY'] = 'fixed annuity'; -+ LinkAccountSubtypes['GIC'] = 'gic'; -+ LinkAccountSubtypes['HEALTH_REIMBURSEMENT_ARRANGEMENT'] = -+ 'health reimbursement arrangement'; -+ LinkAccountSubtypes['IRA'] = 'ira'; -+ LinkAccountSubtypes['ISA'] = 'isa'; -+ LinkAccountSubtypes['KEOGH'] = 'keogh'; -+ LinkAccountSubtypes['LIF'] = 'lif'; -+ LinkAccountSubtypes['LIRA'] = 'lira'; -+ LinkAccountSubtypes['LRIF'] = 'lrif'; -+ LinkAccountSubtypes['LRSP'] = 'lrsp'; -+ LinkAccountSubtypes['MUTUAL_FUND'] = 'mutual fund'; -+ LinkAccountSubtypes['NON_TAXABLE_BROKERAGE_ACCOUNT'] = -+ 'non-taxable brokerage account'; -+ LinkAccountSubtypes['PENSION'] = 'pension'; -+ LinkAccountSubtypes['PLAN'] = 'plan'; -+ LinkAccountSubtypes['PRIF'] = 'prif'; -+ LinkAccountSubtypes['PROFIT_SHARING_PLAN'] = 'profit sharing plan'; -+ LinkAccountSubtypes['RDSP'] = 'rdsp'; -+ LinkAccountSubtypes['RESP'] = 'resp'; -+ LinkAccountSubtypes['RETIREMENT'] = 'retirement'; -+ LinkAccountSubtypes['RLIF'] = 'rlif'; -+ LinkAccountSubtypes['ROTH_401K'] = 'roth 401k'; -+ LinkAccountSubtypes['ROTH'] = 'roth'; -+ LinkAccountSubtypes['RRIF'] = 'rrif'; -+ LinkAccountSubtypes['RRSP'] = 'rrsp'; -+ LinkAccountSubtypes['SARSEP'] = 'sarsep'; -+ LinkAccountSubtypes['SEP_IRA'] = 'sep ira'; -+ LinkAccountSubtypes['SIMPLE_IRA'] = 'simple ira'; -+ LinkAccountSubtypes['SIPP'] = 'sipp'; -+ LinkAccountSubtypes['STOCK_PLAN'] = 'stock plan'; -+ LinkAccountSubtypes['TFSA'] = 'tfsa'; -+ LinkAccountSubtypes['THRIFT_SAVINGS_PLAN'] = 'thrift savings plan'; -+ LinkAccountSubtypes['TRUST'] = 'trust'; -+ LinkAccountSubtypes['UGMA'] = 'ugma'; -+ LinkAccountSubtypes['UTMA'] = 'utma'; -+ LinkAccountSubtypes['VARIABLE_ANNUITY'] = 'variable annuity'; - })(LinkAccountSubtypes || (LinkAccountSubtypes = {})); - export class LinkAccountSubtypeCredit { -- constructor(type, subtype) { -- this.type = type; -- this.subtype = subtype; -- } -+ constructor(type, subtype) { -+ this.type = type; -+ this.subtype = subtype; -+ } - } --LinkAccountSubtypeCredit.ALL = new LinkAccountSubtypeCredit(LinkAccountType.CREDIT, LinkAccountSubtypes.ALL); --LinkAccountSubtypeCredit.CREDIT_CARD = new LinkAccountSubtypeCredit(LinkAccountType.CREDIT, LinkAccountSubtypes.CREDIT_CARD); --LinkAccountSubtypeCredit.PAYPAL = new LinkAccountSubtypeCredit(LinkAccountType.CREDIT, LinkAccountSubtypes.PAYPAL); -+LinkAccountSubtypeCredit.ALL = new LinkAccountSubtypeCredit( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.ALL, -+); -+LinkAccountSubtypeCredit.CREDIT_CARD = new LinkAccountSubtypeCredit( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.CREDIT_CARD, -+); -+LinkAccountSubtypeCredit.PAYPAL = new LinkAccountSubtypeCredit( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.PAYPAL, -+); - export class LinkAccountSubtypeDepository { -- constructor(type, subtype) { -- this.type = type; -- this.subtype = subtype; -- } -+ constructor(type, subtype) { -+ this.type = type; -+ this.subtype = subtype; -+ } - } --LinkAccountSubtypeDepository.ALL = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.ALL); --LinkAccountSubtypeDepository.CASH_MANAGEMENT = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.CASH_MANAGEMENT); --LinkAccountSubtypeDepository.CD = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.CD); --LinkAccountSubtypeDepository.CHECKING = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.CHECKING); --LinkAccountSubtypeDepository.EBT = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.EBT); --LinkAccountSubtypeDepository.HSA = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.HSA); --LinkAccountSubtypeDepository.MONEY_MARKET = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.MONEY_MARKET); --LinkAccountSubtypeDepository.PAYPAL = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.PAYPAL); --LinkAccountSubtypeDepository.PREPAID = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.PREPAID); --LinkAccountSubtypeDepository.SAVINGS = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.SAVINGS); -+LinkAccountSubtypeDepository.ALL = new LinkAccountSubtypeDepository( -+ LinkAccountType.DEPOSITORY, -+ LinkAccountSubtypes.ALL, -+); -+LinkAccountSubtypeDepository.CASH_MANAGEMENT = new LinkAccountSubtypeDepository( -+ LinkAccountType.DEPOSITORY, -+ LinkAccountSubtypes.CASH_MANAGEMENT, -+); -+LinkAccountSubtypeDepository.CD = new LinkAccountSubtypeDepository( -+ LinkAccountType.DEPOSITORY, -+ LinkAccountSubtypes.CD, -+); -+LinkAccountSubtypeDepository.CHECKING = new LinkAccountSubtypeDepository( -+ LinkAccountType.DEPOSITORY, -+ LinkAccountSubtypes.CHECKING, -+); -+LinkAccountSubtypeDepository.EBT = new LinkAccountSubtypeDepository( -+ LinkAccountType.DEPOSITORY, -+ LinkAccountSubtypes.EBT, -+); -+LinkAccountSubtypeDepository.HSA = new LinkAccountSubtypeDepository( -+ LinkAccountType.DEPOSITORY, -+ LinkAccountSubtypes.HSA, -+); -+LinkAccountSubtypeDepository.MONEY_MARKET = new LinkAccountSubtypeDepository( -+ LinkAccountType.DEPOSITORY, -+ LinkAccountSubtypes.MONEY_MARKET, -+); -+LinkAccountSubtypeDepository.PAYPAL = new LinkAccountSubtypeDepository( -+ LinkAccountType.DEPOSITORY, -+ LinkAccountSubtypes.PAYPAL, -+); -+LinkAccountSubtypeDepository.PREPAID = new LinkAccountSubtypeDepository( -+ LinkAccountType.DEPOSITORY, -+ LinkAccountSubtypes.PREPAID, -+); -+LinkAccountSubtypeDepository.SAVINGS = new LinkAccountSubtypeDepository( -+ LinkAccountType.DEPOSITORY, -+ LinkAccountSubtypes.SAVINGS, -+); - export class LinkAccountSubtypeInvestment { -- constructor(type, subtype) { -- this.type = type; -- this.subtype = subtype; -- } -+ constructor(type, subtype) { -+ this.type = type; -+ this.subtype = subtype; -+ } - } --LinkAccountSubtypeInvestment.ALL = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.ALL); --LinkAccountSubtypeInvestment.BROKERAGE = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.BROKERAGE); --LinkAccountSubtypeInvestment.CASH_ISA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.CASH_ISA); --LinkAccountSubtypeInvestment.EDUCATION_SAVINGS_ACCOUNT = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.EDUCATION_SAVINGS_ACCOUNT); --LinkAccountSubtypeInvestment.FIXED_ANNUNITY = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FIXED_ANNUNITY); --LinkAccountSubtypeInvestment.GIC = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.GIC); --LinkAccountSubtypeInvestment.HEALTH_REIMBURSEMENT_ARRANGEMENT = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.HEALTH_REIMBURSEMENT_ARRANGEMENT); --LinkAccountSubtypeInvestment.HSA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.HSA); --LinkAccountSubtypeInvestment.INVESTMENT_401A = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FOUR_0_1_A); --LinkAccountSubtypeInvestment.INVESTMENT_401K = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FOUR_0_1_K); --LinkAccountSubtypeInvestment.INVESTMENT_403B = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FOUR_0_3_B); --LinkAccountSubtypeInvestment.INVESTMENT_457B = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FOUR_5_7_B); --LinkAccountSubtypeInvestment.INVESTMENT_529 = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FIVE_2_9); --LinkAccountSubtypeInvestment.IRA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.IRA); --LinkAccountSubtypeInvestment.ISA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.ISA); --LinkAccountSubtypeInvestment.KEOGH = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.KEOGH); --LinkAccountSubtypeInvestment.LIF = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.LIF); --LinkAccountSubtypeInvestment.LIRA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.LIRA); --LinkAccountSubtypeInvestment.LRIF = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.LRIF); --LinkAccountSubtypeInvestment.LRSP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.LRSP); --LinkAccountSubtypeInvestment.MUTUAL_FUND = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.MUTUAL_FUND); --LinkAccountSubtypeInvestment.NON_TAXABLE_BROKERAGE_ACCOUNT = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.NON_TAXABLE_BROKERAGE_ACCOUNT); --LinkAccountSubtypeInvestment.PENSION = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.PENSION); --LinkAccountSubtypeInvestment.PLAN = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.PLAN); --LinkAccountSubtypeInvestment.PRIF = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.PRIF); --LinkAccountSubtypeInvestment.PROFIT_SHARING_PLAN = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.PROFIT_SHARING_PLAN); --LinkAccountSubtypeInvestment.RDSP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RDSP); --LinkAccountSubtypeInvestment.RESP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RESP); --LinkAccountSubtypeInvestment.RETIREMENT = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RETIREMENT); --LinkAccountSubtypeInvestment.RLIF = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RLIF); --LinkAccountSubtypeInvestment.ROTH = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.ROTH); --LinkAccountSubtypeInvestment.ROTH_401K = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.ROTH_401K); --LinkAccountSubtypeInvestment.RRIF = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RRIF); --LinkAccountSubtypeInvestment.RRSP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RRSP); --LinkAccountSubtypeInvestment.SARSEP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.SARSEP); --LinkAccountSubtypeInvestment.SEP_IRA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.SEP_IRA); --LinkAccountSubtypeInvestment.SIMPLE_IRA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.SIMPLE_IRA); --LinkAccountSubtypeInvestment.SIIP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.SIPP); --LinkAccountSubtypeInvestment.STOCK_PLAN = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.STOCK_PLAN); --LinkAccountSubtypeInvestment.TFSA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.TFSA); --LinkAccountSubtypeInvestment.THRIFT_SAVINGS_PLAN = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.THRIFT_SAVINGS_PLAN); --LinkAccountSubtypeInvestment.TRUST = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.TRUST); --LinkAccountSubtypeInvestment.UGMA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.UGMA); --LinkAccountSubtypeInvestment.UTMA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.UTMA); --LinkAccountSubtypeInvestment.VARIABLE_ANNUITY = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.VARIABLE_ANNUITY); -+LinkAccountSubtypeInvestment.ALL = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.ALL, -+); -+LinkAccountSubtypeInvestment.BROKERAGE = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.BROKERAGE, -+); -+LinkAccountSubtypeInvestment.CASH_ISA = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.CASH_ISA, -+); -+LinkAccountSubtypeInvestment.EDUCATION_SAVINGS_ACCOUNT = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.EDUCATION_SAVINGS_ACCOUNT, -+); -+LinkAccountSubtypeInvestment.FIXED_ANNUNITY = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.FIXED_ANNUNITY, -+); -+LinkAccountSubtypeInvestment.GIC = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.GIC, -+); -+LinkAccountSubtypeInvestment.HEALTH_REIMBURSEMENT_ARRANGEMENT = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.HEALTH_REIMBURSEMENT_ARRANGEMENT, -+); -+LinkAccountSubtypeInvestment.HSA = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.HSA, -+); -+LinkAccountSubtypeInvestment.INVESTMENT_401A = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.FOUR_0_1_A, -+); -+LinkAccountSubtypeInvestment.INVESTMENT_401K = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.FOUR_0_1_K, -+); -+LinkAccountSubtypeInvestment.INVESTMENT_403B = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.FOUR_0_3_B, -+); -+LinkAccountSubtypeInvestment.INVESTMENT_457B = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.FOUR_5_7_B, -+); -+LinkAccountSubtypeInvestment.INVESTMENT_529 = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.FIVE_2_9, -+); -+LinkAccountSubtypeInvestment.IRA = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.IRA, -+); -+LinkAccountSubtypeInvestment.ISA = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.ISA, -+); -+LinkAccountSubtypeInvestment.KEOGH = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.KEOGH, -+); -+LinkAccountSubtypeInvestment.LIF = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.LIF, -+); -+LinkAccountSubtypeInvestment.LIRA = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.LIRA, -+); -+LinkAccountSubtypeInvestment.LRIF = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.LRIF, -+); -+LinkAccountSubtypeInvestment.LRSP = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.LRSP, -+); -+LinkAccountSubtypeInvestment.MUTUAL_FUND = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.MUTUAL_FUND, -+); -+LinkAccountSubtypeInvestment.NON_TAXABLE_BROKERAGE_ACCOUNT = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.NON_TAXABLE_BROKERAGE_ACCOUNT, -+); -+LinkAccountSubtypeInvestment.PENSION = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.PENSION, -+); -+LinkAccountSubtypeInvestment.PLAN = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.PLAN, -+); -+LinkAccountSubtypeInvestment.PRIF = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.PRIF, -+); -+LinkAccountSubtypeInvestment.PROFIT_SHARING_PLAN = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.PROFIT_SHARING_PLAN, -+); -+LinkAccountSubtypeInvestment.RDSP = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.RDSP, -+); -+LinkAccountSubtypeInvestment.RESP = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.RESP, -+); -+LinkAccountSubtypeInvestment.RETIREMENT = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.RETIREMENT, -+); -+LinkAccountSubtypeInvestment.RLIF = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.RLIF, -+); -+LinkAccountSubtypeInvestment.ROTH = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.ROTH, -+); -+LinkAccountSubtypeInvestment.ROTH_401K = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.ROTH_401K, -+); -+LinkAccountSubtypeInvestment.RRIF = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.RRIF, -+); -+LinkAccountSubtypeInvestment.RRSP = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.RRSP, -+); -+LinkAccountSubtypeInvestment.SARSEP = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.SARSEP, -+); -+LinkAccountSubtypeInvestment.SEP_IRA = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.SEP_IRA, -+); -+LinkAccountSubtypeInvestment.SIMPLE_IRA = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.SIMPLE_IRA, -+); -+LinkAccountSubtypeInvestment.SIIP = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.SIPP, -+); -+LinkAccountSubtypeInvestment.STOCK_PLAN = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.STOCK_PLAN, -+); -+LinkAccountSubtypeInvestment.TFSA = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.TFSA, -+); -+LinkAccountSubtypeInvestment.THRIFT_SAVINGS_PLAN = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.THRIFT_SAVINGS_PLAN, -+); -+LinkAccountSubtypeInvestment.TRUST = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.TRUST, -+); -+LinkAccountSubtypeInvestment.UGMA = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.UGMA, -+); -+LinkAccountSubtypeInvestment.UTMA = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.UTMA, -+); -+LinkAccountSubtypeInvestment.VARIABLE_ANNUITY = new LinkAccountSubtypeInvestment( -+ LinkAccountType.INVESTMENT, -+ LinkAccountSubtypes.VARIABLE_ANNUITY, -+); - export class LinkAccountSubtypeLoan { -- constructor(type, subtype) { -- this.type = type; -- this.subtype = subtype; -- } -+ constructor(type, subtype) { -+ this.type = type; -+ this.subtype = subtype; -+ } - } --LinkAccountSubtypeLoan.ALL = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.ALL); --LinkAccountSubtypeLoan.AUTO = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.AUTO); --LinkAccountSubtypeLoan.BUSINESS = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.BUSINESS); --LinkAccountSubtypeLoan.COMMERCIAL = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.COMMERCIAL); --LinkAccountSubtypeLoan.CONSTRUCTION = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.CONSTRUCTION); --LinkAccountSubtypeLoan.CONSUMER = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.CONSUMER); --LinkAccountSubtypeLoan.HOME_EQUITY = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.HOME_EQUITY); --LinkAccountSubtypeLoan.LINE_OF_CREDIT = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.LINE_OF_CREDIT); --LinkAccountSubtypeLoan.LOAN = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.LOAN); --LinkAccountSubtypeLoan.MORTGAGE = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.MORTGAGE); --LinkAccountSubtypeLoan.OVERDRAFT = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.OVERDRAFT); --LinkAccountSubtypeLoan.STUDENT = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.STUDENT); -+LinkAccountSubtypeLoan.ALL = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.ALL, -+); -+LinkAccountSubtypeLoan.AUTO = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.AUTO, -+); -+LinkAccountSubtypeLoan.BUSINESS = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.BUSINESS, -+); -+LinkAccountSubtypeLoan.COMMERCIAL = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.COMMERCIAL, -+); -+LinkAccountSubtypeLoan.CONSTRUCTION = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.CONSTRUCTION, -+); -+LinkAccountSubtypeLoan.CONSUMER = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.CONSUMER, -+); -+LinkAccountSubtypeLoan.HOME_EQUITY = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.HOME_EQUITY, -+); -+LinkAccountSubtypeLoan.LINE_OF_CREDIT = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.LINE_OF_CREDIT, -+); -+LinkAccountSubtypeLoan.LOAN = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.LOAN, -+); -+LinkAccountSubtypeLoan.MORTGAGE = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.MORTGAGE, -+); -+LinkAccountSubtypeLoan.OVERDRAFT = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.OVERDRAFT, -+); -+LinkAccountSubtypeLoan.STUDENT = new LinkAccountSubtypeLoan( -+ LinkAccountType.CREDIT, -+ LinkAccountSubtypes.STUDENT, -+); - export class LinkAccountSubtypeUnknown { -- constructor(type, subtype) { -- this.type = type; -- this.subtype = subtype; -- } -+ constructor(type, subtype) { -+ this.type = type; -+ this.subtype = subtype; -+ } - } - export var LinkAccountVerificationStatus; --(function (LinkAccountVerificationStatus) { -- LinkAccountVerificationStatus["PENDING_AUTOMATIC_VERIFICATION"] = "pending_automatic_verification"; -- LinkAccountVerificationStatus["PENDING_MANUAL_VERIFICATION"] = "pending_manual_verification"; -- LinkAccountVerificationStatus["MANUALLY_VERIFIED"] = "manually_verified"; -+(function(LinkAccountVerificationStatus) { -+ LinkAccountVerificationStatus['PENDING_AUTOMATIC_VERIFICATION'] = -+ 'pending_automatic_verification'; -+ LinkAccountVerificationStatus['PENDING_MANUAL_VERIFICATION'] = -+ 'pending_manual_verification'; -+ LinkAccountVerificationStatus['MANUALLY_VERIFIED'] = 'manually_verified'; - })(LinkAccountVerificationStatus || (LinkAccountVerificationStatus = {})); - export var LinkExitMetadataStatus; --(function (LinkExitMetadataStatus) { -- LinkExitMetadataStatus["CONNECTED"] = "connected"; -- LinkExitMetadataStatus["CHOOSE_DEVICE"] = "choose_device"; -- LinkExitMetadataStatus["REQUIRES_ACCOUNT_SELECTION"] = "requires_account_selection"; -- LinkExitMetadataStatus["REQUIRES_CODE"] = "requires_code"; -- LinkExitMetadataStatus["REQUIRES_CREDENTIALS"] = "requires_credentials"; -- LinkExitMetadataStatus["REQUIRES_EXTERNAL_ACTION"] = "requires_external_action"; -- LinkExitMetadataStatus["REQUIRES_OAUTH"] = "requires_oauth"; -- LinkExitMetadataStatus["REQUIRES_QUESTIONS"] = "requires_questions"; -- LinkExitMetadataStatus["REQUIRES_RECAPTCHA"] = "requires_recaptcha"; -- LinkExitMetadataStatus["REQUIRES_SELECTIONS"] = "requires_selections"; -- LinkExitMetadataStatus["REQUIRES_DEPOSIT_SWITCH_ALLOCATION_CONFIGURATION"] = "requires_deposit_switch_allocation_configuration"; -- LinkExitMetadataStatus["REQUIRES_DEPOSIT_SWITCH_ALLOCATION_SELECTION"] = "requires_deposit_switch_allocation_selection"; -+(function(LinkExitMetadataStatus) { -+ LinkExitMetadataStatus['CONNECTED'] = 'connected'; -+ LinkExitMetadataStatus['CHOOSE_DEVICE'] = 'choose_device'; -+ LinkExitMetadataStatus['REQUIRES_ACCOUNT_SELECTION'] = -+ 'requires_account_selection'; -+ LinkExitMetadataStatus['REQUIRES_CODE'] = 'requires_code'; -+ LinkExitMetadataStatus['REQUIRES_CREDENTIALS'] = 'requires_credentials'; -+ LinkExitMetadataStatus['REQUIRES_EXTERNAL_ACTION'] = -+ 'requires_external_action'; -+ LinkExitMetadataStatus['REQUIRES_OAUTH'] = 'requires_oauth'; -+ LinkExitMetadataStatus['REQUIRES_QUESTIONS'] = 'requires_questions'; -+ LinkExitMetadataStatus['REQUIRES_RECAPTCHA'] = 'requires_recaptcha'; -+ LinkExitMetadataStatus['REQUIRES_SELECTIONS'] = 'requires_selections'; -+ LinkExitMetadataStatus['REQUIRES_DEPOSIT_SWITCH_ALLOCATION_CONFIGURATION'] = -+ 'requires_deposit_switch_allocation_configuration'; -+ LinkExitMetadataStatus['REQUIRES_DEPOSIT_SWITCH_ALLOCATION_SELECTION'] = -+ 'requires_deposit_switch_allocation_selection'; - })(LinkExitMetadataStatus || (LinkExitMetadataStatus = {})); - export var LinkErrorCode; --(function (LinkErrorCode) { -- // ITEM_ERROR -- LinkErrorCode["INVALID_CREDENTIALS"] = "INVALID_CREDENTIALS"; -- LinkErrorCode["INVALID_MFA"] = "INVALID_MFA"; -- LinkErrorCode["ITEM_LOGIN_REQUIRED"] = "ITEM_LOGIN_REQUIRED"; -- LinkErrorCode["INSUFFICIENT_CREDENTIALS"] = "INSUFFICIENT_CREDENTIALS"; -- LinkErrorCode["ITEM_LOCKED"] = "ITEM_LOCKED"; -- LinkErrorCode["USER_SETUP_REQUIRED"] = "USER_SETUP_REQUIRED"; -- LinkErrorCode["MFA_NOT_SUPPORTED"] = "MFA_NOT_SUPPORTED"; -- LinkErrorCode["INVALID_SEND_METHOD"] = "INVALID_SEND_METHOD"; -- LinkErrorCode["NO_ACCOUNTS"] = "NO_ACCOUNTS"; -- LinkErrorCode["ITEM_NOT_SUPPORTED"] = "ITEM_NOT_SUPPORTED"; -- LinkErrorCode["TOO_MANY_VERIFICATION_ATTEMPTS"] = "TOO_MANY_VERIFICATION_ATTEMPTS"; -- LinkErrorCode["INVALD_UPDATED_USERNAME"] = "INVALD_UPDATED_USERNAME"; -- LinkErrorCode["INVALID_UPDATED_USERNAME"] = "INVALID_UPDATED_USERNAME"; -- LinkErrorCode["ITEM_NO_ERROR"] = "ITEM_NO_ERROR"; -- LinkErrorCode["item_no_error"] = "item-no-error"; -- LinkErrorCode["NO_AUTH_ACCOUNTS"] = "NO_AUTH_ACCOUNTS"; -- LinkErrorCode["NO_INVESTMENT_ACCOUNTS"] = "NO_INVESTMENT_ACCOUNTS"; -- LinkErrorCode["NO_INVESTMENT_AUTH_ACCOUNTS"] = "NO_INVESTMENT_AUTH_ACCOUNTS"; -- LinkErrorCode["NO_LIABILITY_ACCOUNTS"] = "NO_LIABILITY_ACCOUNTS"; -- LinkErrorCode["PRODUCTS_NOT_SUPPORTED"] = "PRODUCTS_NOT_SUPPORTED"; -- LinkErrorCode["ITEM_NOT_FOUND"] = "ITEM_NOT_FOUND"; -- LinkErrorCode["ITEM_PRODUCT_NOT_READY"] = "ITEM_PRODUCT_NOT_READY"; -- // INSTITUTION_ERROR -- LinkErrorCode["INSTITUTION_DOWN"] = "INSTITUTION_DOWN"; -- LinkErrorCode["INSTITUTION_NOT_RESPONDING"] = "INSTITUTION_NOT_RESPONDING"; -- LinkErrorCode["INSTITUTION_NOT_AVAILABLE"] = "INSTITUTION_NOT_AVAILABLE"; -- LinkErrorCode["INSTITUTION_NO_LONGER_SUPPORTED"] = "INSTITUTION_NO_LONGER_SUPPORTED"; -- // API_ERROR -- LinkErrorCode["INTERNAL_SERVER_ERROR"] = "INTERNAL_SERVER_ERROR"; -- LinkErrorCode["PLANNED_MAINTENANCE"] = "PLANNED_MAINTENANCE"; -- // ASSET_REPORT_ERROR -- LinkErrorCode["PRODUCT_NOT_ENABLED"] = "PRODUCT_NOT_ENABLED"; -- LinkErrorCode["DATA_UNAVAILABLE"] = "DATA_UNAVAILABLE"; -- LinkErrorCode["ASSET_PRODUCT_NOT_READY"] = "ASSET_PRODUCT_NOT_READY"; -- LinkErrorCode["ASSET_REPORT_GENERATION_FAILED"] = "ASSET_REPORT_GENERATION_FAILED"; -- LinkErrorCode["INVALID_PARENT"] = "INVALID_PARENT"; -- LinkErrorCode["INSIGHTS_NOT_ENABLED"] = "INSIGHTS_NOT_ENABLED"; -- LinkErrorCode["INSIGHTS_PREVIOUSLY_NOT_ENABLED"] = "INSIGHTS_PREVIOUSLY_NOT_ENABLED"; -- // BANK_TRANSFER_ERROR -- LinkErrorCode["BANK_TRANSFER_LIMIT_EXCEEDED"] = "BANK_TRANSFER_LIMIT_EXCEEDED"; -- LinkErrorCode["BANK_TRANSFER_MISSING_ORIGINATION_ACCOUNT"] = "BANK_TRANSFER_MISSING_ORIGINATION_ACCOUNT"; -- LinkErrorCode["BANK_TRANSFER_INVALID_ORIGINATION_ACCOUNT"] = "BANK_TRANSFER_INVALID_ORIGINATION_ACCOUNT"; -- LinkErrorCode["BANK_TRANSFER_ACCOUNT_BLOCKED"] = "BANK_TRANSFER_ACCOUNT_BLOCKED"; -- LinkErrorCode["BANK_TRANSFER_INSUFFICIENT_FUNDS"] = "BANK_TRANSFER_INSUFFICIENT_FUNDS"; -- LinkErrorCode["BANK_TRANSFER_NOT_CANCELLABLE"] = "BANK_TRANSFER_NOT_CANCELLABLE"; -- LinkErrorCode["BANK_TRANSFER_UNSUPPORTED_ACCOUNT_TYPE"] = "BANK_TRANSFER_UNSUPPORTED_ACCOUNT_TYPE"; -- LinkErrorCode["BANK_TRANSFER_UNSUPPORTED_ENVIRONMENT"] = "BANK_TRANSFER_UNSUPPORTED_ENVIRONMENT"; -- // SANDBOX_ERROR -- LinkErrorCode["SANDBOX_PRODUCT_NOT_ENABLED"] = "SANDBOX_PRODUCT_NOT_ENABLED"; -- LinkErrorCode["SANDBOX_WEBHOOK_INVALID"] = "SANDBOX_WEBHOOK_INVALID"; -- LinkErrorCode["SANDBOX_BANK_TRANSFER_EVENT_TRANSITION_INVALID"] = "SANDBOX_BANK_TRANSFER_EVENT_TRANSITION_INVALID"; -- // INVALID_REQUEST -- LinkErrorCode["MISSING_FIELDS"] = "MISSING_FIELDS"; -- LinkErrorCode["UNKNOWN_FIELDS"] = "UNKNOWN_FIELDS"; -- LinkErrorCode["INVALID_FIELD"] = "INVALID_FIELD"; -- LinkErrorCode["INCOMPATIBLE_API_VERSION"] = "INCOMPATIBLE_API_VERSION"; -- LinkErrorCode["INVALID_BODY"] = "INVALID_BODY"; -- LinkErrorCode["INVALID_HEADERS"] = "INVALID_HEADERS"; -- LinkErrorCode["NOT_FOUND"] = "NOT_FOUND"; -- LinkErrorCode["NO_LONGER_AVAILABLE"] = "NO_LONGER_AVAILABLE"; -- LinkErrorCode["SANDBOX_ONLY"] = "SANDBOX_ONLY"; -- LinkErrorCode["INVALID_ACCOUNT_NUMBER"] = "INVALID_ACCOUNT_NUMBER"; -- // INVALID_INPUT -- // From above ITEM_LOGIN_REQUIRED = "INVALID_CREDENTIALS", -- LinkErrorCode["INCORRECT_DEPOSIT_AMOUNTS"] = "INCORRECT_DEPOSIT_AMOUNTS"; -- LinkErrorCode["UNAUTHORIZED_ENVIRONMENT"] = "UNAUTHORIZED_ENVIRONMENT"; -- LinkErrorCode["INVALID_PRODUCT"] = "INVALID_PRODUCT"; -- LinkErrorCode["UNAUTHORIZED_ROUTE_ACCESS"] = "UNAUTHORIZED_ROUTE_ACCESS"; -- LinkErrorCode["DIRECT_INTEGRATION_NOT_ENABLED"] = "DIRECT_INTEGRATION_NOT_ENABLED"; -- LinkErrorCode["INVALID_API_KEYS"] = "INVALID_API_KEYS"; -- LinkErrorCode["INVALID_ACCESS_TOKEN"] = "INVALID_ACCESS_TOKEN"; -- LinkErrorCode["INVALID_PUBLIC_TOKEN"] = "INVALID_PUBLIC_TOKEN"; -- LinkErrorCode["INVALID_LINK_TOKEN"] = "INVALID_LINK_TOKEN"; -- LinkErrorCode["INVALID_PROCESSOR_TOKEN"] = "INVALID_PROCESSOR_TOKEN"; -- LinkErrorCode["INVALID_AUDIT_COPY_TOKEN"] = "INVALID_AUDIT_COPY_TOKEN"; -- LinkErrorCode["INVALID_ACCOUNT_ID"] = "INVALID_ACCOUNT_ID"; -- LinkErrorCode["MICRODEPOSITS_ALREADY_VERIFIED"] = "MICRODEPOSITS_ALREADY_VERIFIED"; -- // INVALID_RESULT -- LinkErrorCode["PLAID_DIRECT_ITEM_IMPORT_RETURNED_INVALID_MFA"] = "PLAID_DIRECT_ITEM_IMPORT_RETURNED_INVALID_MFA"; -- // RATE_LIMIT_EXCEEDED -- LinkErrorCode["ACCOUNTS_LIMIT"] = "ACCOUNTS_LIMIT"; -- LinkErrorCode["ADDITION_LIMIT"] = "ADDITION_LIMIT"; -- LinkErrorCode["AUTH_LIMIT"] = "AUTH_LIMIT"; -- LinkErrorCode["BALANCE_LIMIT"] = "BALANCE_LIMIT"; -- LinkErrorCode["IDENTITY_LIMIT"] = "IDENTITY_LIMIT"; -- LinkErrorCode["ITEM_GET_LIMIT"] = "ITEM_GET_LIMIT"; -- LinkErrorCode["RATE_LIMIT"] = "RATE_LIMIT"; -- LinkErrorCode["TRANSACTIONS_LIMIT"] = "TRANSACTIONS_LIMIT"; -- // RECAPTCHA_ERROR -- LinkErrorCode["RECAPTCHA_REQUIRED"] = "RECAPTCHA_REQUIRED"; -- LinkErrorCode["RECAPTCHA_BAD"] = "RECAPTCHA_BAD"; -- // OAUTH_ERROR -- LinkErrorCode["INCORRECT_OAUTH_NONCE"] = "INCORRECT_OAUTH_NONCE"; -- LinkErrorCode["OAUTH_STATE_ID_ALREADY_PROCESSED"] = "OAUTH_STATE_ID_ALREADY_PROCESSED"; -+(function(LinkErrorCode) { -+ // ITEM_ERROR -+ LinkErrorCode['INVALID_CREDENTIALS'] = 'INVALID_CREDENTIALS'; -+ LinkErrorCode['INVALID_MFA'] = 'INVALID_MFA'; -+ LinkErrorCode['ITEM_LOGIN_REQUIRED'] = 'ITEM_LOGIN_REQUIRED'; -+ LinkErrorCode['INSUFFICIENT_CREDENTIALS'] = 'INSUFFICIENT_CREDENTIALS'; -+ LinkErrorCode['ITEM_LOCKED'] = 'ITEM_LOCKED'; -+ LinkErrorCode['USER_SETUP_REQUIRED'] = 'USER_SETUP_REQUIRED'; -+ LinkErrorCode['MFA_NOT_SUPPORTED'] = 'MFA_NOT_SUPPORTED'; -+ LinkErrorCode['INVALID_SEND_METHOD'] = 'INVALID_SEND_METHOD'; -+ LinkErrorCode['NO_ACCOUNTS'] = 'NO_ACCOUNTS'; -+ LinkErrorCode['ITEM_NOT_SUPPORTED'] = 'ITEM_NOT_SUPPORTED'; -+ LinkErrorCode['TOO_MANY_VERIFICATION_ATTEMPTS'] = -+ 'TOO_MANY_VERIFICATION_ATTEMPTS'; -+ LinkErrorCode['INVALD_UPDATED_USERNAME'] = 'INVALD_UPDATED_USERNAME'; -+ LinkErrorCode['INVALID_UPDATED_USERNAME'] = 'INVALID_UPDATED_USERNAME'; -+ LinkErrorCode['ITEM_NO_ERROR'] = 'ITEM_NO_ERROR'; -+ LinkErrorCode['item_no_error'] = 'item-no-error'; -+ LinkErrorCode['NO_AUTH_ACCOUNTS'] = 'NO_AUTH_ACCOUNTS'; -+ LinkErrorCode['NO_INVESTMENT_ACCOUNTS'] = 'NO_INVESTMENT_ACCOUNTS'; -+ LinkErrorCode['NO_INVESTMENT_AUTH_ACCOUNTS'] = 'NO_INVESTMENT_AUTH_ACCOUNTS'; -+ LinkErrorCode['NO_LIABILITY_ACCOUNTS'] = 'NO_LIABILITY_ACCOUNTS'; -+ LinkErrorCode['PRODUCTS_NOT_SUPPORTED'] = 'PRODUCTS_NOT_SUPPORTED'; -+ LinkErrorCode['ITEM_NOT_FOUND'] = 'ITEM_NOT_FOUND'; -+ LinkErrorCode['ITEM_PRODUCT_NOT_READY'] = 'ITEM_PRODUCT_NOT_READY'; -+ // INSTITUTION_ERROR -+ LinkErrorCode['INSTITUTION_DOWN'] = 'INSTITUTION_DOWN'; -+ LinkErrorCode['INSTITUTION_NOT_RESPONDING'] = 'INSTITUTION_NOT_RESPONDING'; -+ LinkErrorCode['INSTITUTION_NOT_AVAILABLE'] = 'INSTITUTION_NOT_AVAILABLE'; -+ LinkErrorCode['INSTITUTION_NO_LONGER_SUPPORTED'] = -+ 'INSTITUTION_NO_LONGER_SUPPORTED'; -+ // API_ERROR -+ LinkErrorCode['INTERNAL_SERVER_ERROR'] = 'INTERNAL_SERVER_ERROR'; -+ LinkErrorCode['PLANNED_MAINTENANCE'] = 'PLANNED_MAINTENANCE'; -+ // ASSET_REPORT_ERROR -+ LinkErrorCode['PRODUCT_NOT_ENABLED'] = 'PRODUCT_NOT_ENABLED'; -+ LinkErrorCode['DATA_UNAVAILABLE'] = 'DATA_UNAVAILABLE'; -+ LinkErrorCode['ASSET_PRODUCT_NOT_READY'] = 'ASSET_PRODUCT_NOT_READY'; -+ LinkErrorCode['ASSET_REPORT_GENERATION_FAILED'] = -+ 'ASSET_REPORT_GENERATION_FAILED'; -+ LinkErrorCode['INVALID_PARENT'] = 'INVALID_PARENT'; -+ LinkErrorCode['INSIGHTS_NOT_ENABLED'] = 'INSIGHTS_NOT_ENABLED'; -+ LinkErrorCode['INSIGHTS_PREVIOUSLY_NOT_ENABLED'] = -+ 'INSIGHTS_PREVIOUSLY_NOT_ENABLED'; -+ // BANK_TRANSFER_ERROR -+ LinkErrorCode['BANK_TRANSFER_LIMIT_EXCEEDED'] = -+ 'BANK_TRANSFER_LIMIT_EXCEEDED'; -+ LinkErrorCode['BANK_TRANSFER_MISSING_ORIGINATION_ACCOUNT'] = -+ 'BANK_TRANSFER_MISSING_ORIGINATION_ACCOUNT'; -+ LinkErrorCode['BANK_TRANSFER_INVALID_ORIGINATION_ACCOUNT'] = -+ 'BANK_TRANSFER_INVALID_ORIGINATION_ACCOUNT'; -+ LinkErrorCode['BANK_TRANSFER_ACCOUNT_BLOCKED'] = -+ 'BANK_TRANSFER_ACCOUNT_BLOCKED'; -+ LinkErrorCode['BANK_TRANSFER_INSUFFICIENT_FUNDS'] = -+ 'BANK_TRANSFER_INSUFFICIENT_FUNDS'; -+ LinkErrorCode['BANK_TRANSFER_NOT_CANCELLABLE'] = -+ 'BANK_TRANSFER_NOT_CANCELLABLE'; -+ LinkErrorCode['BANK_TRANSFER_UNSUPPORTED_ACCOUNT_TYPE'] = -+ 'BANK_TRANSFER_UNSUPPORTED_ACCOUNT_TYPE'; -+ LinkErrorCode['BANK_TRANSFER_UNSUPPORTED_ENVIRONMENT'] = -+ 'BANK_TRANSFER_UNSUPPORTED_ENVIRONMENT'; -+ // SANDBOX_ERROR -+ LinkErrorCode['SANDBOX_PRODUCT_NOT_ENABLED'] = 'SANDBOX_PRODUCT_NOT_ENABLED'; -+ LinkErrorCode['SANDBOX_WEBHOOK_INVALID'] = 'SANDBOX_WEBHOOK_INVALID'; -+ LinkErrorCode['SANDBOX_BANK_TRANSFER_EVENT_TRANSITION_INVALID'] = -+ 'SANDBOX_BANK_TRANSFER_EVENT_TRANSITION_INVALID'; -+ // INVALID_REQUEST -+ LinkErrorCode['MISSING_FIELDS'] = 'MISSING_FIELDS'; -+ LinkErrorCode['UNKNOWN_FIELDS'] = 'UNKNOWN_FIELDS'; -+ LinkErrorCode['INVALID_FIELD'] = 'INVALID_FIELD'; -+ LinkErrorCode['INCOMPATIBLE_API_VERSION'] = 'INCOMPATIBLE_API_VERSION'; -+ LinkErrorCode['INVALID_BODY'] = 'INVALID_BODY'; -+ LinkErrorCode['INVALID_HEADERS'] = 'INVALID_HEADERS'; -+ LinkErrorCode['NOT_FOUND'] = 'NOT_FOUND'; -+ LinkErrorCode['NO_LONGER_AVAILABLE'] = 'NO_LONGER_AVAILABLE'; -+ LinkErrorCode['SANDBOX_ONLY'] = 'SANDBOX_ONLY'; -+ LinkErrorCode['INVALID_ACCOUNT_NUMBER'] = 'INVALID_ACCOUNT_NUMBER'; -+ // INVALID_INPUT -+ // From above ITEM_LOGIN_REQUIRED = "INVALID_CREDENTIALS", -+ LinkErrorCode['INCORRECT_DEPOSIT_AMOUNTS'] = 'INCORRECT_DEPOSIT_AMOUNTS'; -+ LinkErrorCode['UNAUTHORIZED_ENVIRONMENT'] = 'UNAUTHORIZED_ENVIRONMENT'; -+ LinkErrorCode['INVALID_PRODUCT'] = 'INVALID_PRODUCT'; -+ LinkErrorCode['UNAUTHORIZED_ROUTE_ACCESS'] = 'UNAUTHORIZED_ROUTE_ACCESS'; -+ LinkErrorCode['DIRECT_INTEGRATION_NOT_ENABLED'] = -+ 'DIRECT_INTEGRATION_NOT_ENABLED'; -+ LinkErrorCode['INVALID_API_KEYS'] = 'INVALID_API_KEYS'; -+ LinkErrorCode['INVALID_ACCESS_TOKEN'] = 'INVALID_ACCESS_TOKEN'; -+ LinkErrorCode['INVALID_PUBLIC_TOKEN'] = 'INVALID_PUBLIC_TOKEN'; -+ LinkErrorCode['INVALID_LINK_TOKEN'] = 'INVALID_LINK_TOKEN'; -+ LinkErrorCode['INVALID_PROCESSOR_TOKEN'] = 'INVALID_PROCESSOR_TOKEN'; -+ LinkErrorCode['INVALID_AUDIT_COPY_TOKEN'] = 'INVALID_AUDIT_COPY_TOKEN'; -+ LinkErrorCode['INVALID_ACCOUNT_ID'] = 'INVALID_ACCOUNT_ID'; -+ LinkErrorCode['MICRODEPOSITS_ALREADY_VERIFIED'] = -+ 'MICRODEPOSITS_ALREADY_VERIFIED'; -+ // INVALID_RESULT -+ LinkErrorCode['PLAID_DIRECT_ITEM_IMPORT_RETURNED_INVALID_MFA'] = -+ 'PLAID_DIRECT_ITEM_IMPORT_RETURNED_INVALID_MFA'; -+ // RATE_LIMIT_EXCEEDED -+ LinkErrorCode['ACCOUNTS_LIMIT'] = 'ACCOUNTS_LIMIT'; -+ LinkErrorCode['ADDITION_LIMIT'] = 'ADDITION_LIMIT'; -+ LinkErrorCode['AUTH_LIMIT'] = 'AUTH_LIMIT'; -+ LinkErrorCode['BALANCE_LIMIT'] = 'BALANCE_LIMIT'; -+ LinkErrorCode['IDENTITY_LIMIT'] = 'IDENTITY_LIMIT'; -+ LinkErrorCode['ITEM_GET_LIMIT'] = 'ITEM_GET_LIMIT'; -+ LinkErrorCode['RATE_LIMIT'] = 'RATE_LIMIT'; -+ LinkErrorCode['TRANSACTIONS_LIMIT'] = 'TRANSACTIONS_LIMIT'; -+ // RECAPTCHA_ERROR -+ LinkErrorCode['RECAPTCHA_REQUIRED'] = 'RECAPTCHA_REQUIRED'; -+ LinkErrorCode['RECAPTCHA_BAD'] = 'RECAPTCHA_BAD'; -+ // OAUTH_ERROR -+ LinkErrorCode['INCORRECT_OAUTH_NONCE'] = 'INCORRECT_OAUTH_NONCE'; -+ LinkErrorCode['OAUTH_STATE_ID_ALREADY_PROCESSED'] = -+ 'OAUTH_STATE_ID_ALREADY_PROCESSED'; - })(LinkErrorCode || (LinkErrorCode = {})); - export var LinkErrorType; --(function (LinkErrorType) { -- LinkErrorType["BANK_TRANSFER"] = "BANK_TRANSFER_ERROR"; -- LinkErrorType["INVALID_REQUEST"] = "INVALID_REQUEST"; -- LinkErrorType["INVALID_RESULT"] = "INVALID_RESULT"; -- LinkErrorType["INVALID_INPUT"] = "INVALID_INPUT"; -- LinkErrorType["INSTITUTION_ERROR"] = "INSTITUTION_ERROR"; -- LinkErrorType["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED"; -- LinkErrorType["API_ERROR"] = "API_ERROR"; -- LinkErrorType["ITEM_ERROR"] = "ITEM_ERROR"; -- LinkErrorType["AUTH_ERROR"] = "AUTH_ERROR"; -- LinkErrorType["ASSET_REPORT_ERROR"] = "ASSET_REPORT_ERROR"; -- LinkErrorType["SANDBOX_ERROR"] = "SANDBOX_ERROR"; -- LinkErrorType["RECAPTCHA_ERROR"] = "RECAPTCHA_ERROR"; -- LinkErrorType["OAUTH_ERROR"] = "OAUTH_ERROR"; -+(function(LinkErrorType) { -+ LinkErrorType['BANK_TRANSFER'] = 'BANK_TRANSFER_ERROR'; -+ LinkErrorType['INVALID_REQUEST'] = 'INVALID_REQUEST'; -+ LinkErrorType['INVALID_RESULT'] = 'INVALID_RESULT'; -+ LinkErrorType['INVALID_INPUT'] = 'INVALID_INPUT'; -+ LinkErrorType['INSTITUTION_ERROR'] = 'INSTITUTION_ERROR'; -+ LinkErrorType['RATE_LIMIT_EXCEEDED'] = 'RATE_LIMIT_EXCEEDED'; -+ LinkErrorType['API_ERROR'] = 'API_ERROR'; -+ LinkErrorType['ITEM_ERROR'] = 'ITEM_ERROR'; -+ LinkErrorType['AUTH_ERROR'] = 'AUTH_ERROR'; -+ LinkErrorType['ASSET_REPORT_ERROR'] = 'ASSET_REPORT_ERROR'; -+ LinkErrorType['SANDBOX_ERROR'] = 'SANDBOX_ERROR'; -+ LinkErrorType['RECAPTCHA_ERROR'] = 'RECAPTCHA_ERROR'; -+ LinkErrorType['OAUTH_ERROR'] = 'OAUTH_ERROR'; - })(LinkErrorType || (LinkErrorType = {})); - export var LinkEventName; --(function (LinkEventName) { -- LinkEventName["BANK_INCOME_INSIGHTS_COMPLETED"] = "BANK_INCOME_INSIGHTS_COMPLETED"; -- LinkEventName["CLOSE_OAUTH"] = "CLOSE_OAUTH"; -- LinkEventName["ERROR"] = "ERROR"; -- LinkEventName["EXIT"] = "EXIT"; -- LinkEventName["FAIL_OAUTH"] = "FAIL_OAUTH"; -- LinkEventName["HANDOFF"] = "HANDOFF"; -- LinkEventName["IDENTITY_VERIFICATION_START_STEP"] = "IDENTITY_VERIFICATION_START_STEP"; -- LinkEventName["IDENTITY_VERIFICATION_PASS_STEP"] = "IDENTITY_VERIFICATION_PASS_STEP"; -- LinkEventName["IDENTITY_VERIFICATION_FAIL_STEP"] = "IDENTITY_VERIFICATION_FAIL_STEP"; -- LinkEventName["IDENTITY_VERIFICATION_PENDING_REVIEW_STEP"] = "IDENTITY_VERIFICATION_PENDING_REVIEW_STEP"; -- LinkEventName["IDENTITY_VERIFICATION_PENDING_REVIEW_SESSION"] = "IDENTITY_VERIFICATION_PENDING_REVIEW_SESSION"; -- LinkEventName["IDENTITY_VERIFICATION_CREATE_SESSION"] = "IDENTITY_VERIFICATION_CREATE_SESSION"; -- LinkEventName["IDENTITY_VERIFICATION_RESUME_SESSION"] = "IDENTITY_VERIFICATION_RESUME_SESSION"; -- LinkEventName["IDENTITY_VERIFICATION_PASS_SESSION"] = "IDENTITY_VERIFICATION_PASS_SESSION"; -- LinkEventName["IDENTITY_VERIFICATION_FAIL_SESSION"] = "IDENTITY_VERIFICATION_FAIL_SESSION"; -- LinkEventName["IDENTITY_VERIFICATION_OPEN_UI"] = "IDENTITY_VERIFICATION_OPEN_UI"; -- LinkEventName["IDENTITY_VERIFICATION_RESUME_UI"] = "IDENTITY_VERIFICATION_RESUME_UI"; -- LinkEventName["IDENTITY_VERIFICATION_CLOSE_UI"] = "IDENTITY_VERIFICATION_CLOSE_UI"; -- LinkEventName["MATCHED_CONSENT"] = "MATCHED_CONSENT"; -- LinkEventName["MATCHED_SELECT_INSTITUTION"] = "MATCHED_SELECT_INSTITUTION"; -- LinkEventName["MATCHED_SELECT_VERIFY_METHOD"] = "MATCHED_SELECT_VERIFY_METHOD"; -- LinkEventName["OPEN"] = "OPEN"; -- LinkEventName["OPEN_MY_PLAID"] = "OPEN_MY_PLAID"; -- LinkEventName["OPEN_OAUTH"] = "OPEN_OAUTH"; -- LinkEventName["SEARCH_INSTITUTION"] = "SEARCH_INSTITUTION"; -- LinkEventName["SELECT_DEGRADED_INSTITUTION"] = "SELECT_DEGRADED_INSTITUTION"; -- LinkEventName["SELECT_DOWN_INSTITUTION"] = "SELECT_DOWN_INSTITUTION"; -- LinkEventName["SELECT_FILTERED_INSTITUTION"] = "SELECT_FILTERED_INSTITUTION"; -- LinkEventName["SELECT_INSTITUTION"] = "SELECT_INSTITUTION"; -- LinkEventName["SELECT_BRAND"] = "SELECT_BRAND"; -- LinkEventName["SELECT_AUTH_TYPE"] = "SELECT_AUTH_TYPE"; -- LinkEventName["SUBMIT_ACCOUNT_NUMBER"] = "SUBMIT_ACCOUNT_NUMBER"; -- LinkEventName["SUBMIT_DOCUMENTS"] = "SUBMIT_DOCUMENTS"; -- LinkEventName["SUBMIT_DOCUMENTS_SUCCESS"] = "SUBMIT_DOCUMENTS_SUCCESS"; -- LinkEventName["SUBMIT_DOCUMENTS_ERROR"] = "SUBMIT_DOCUMENTS_ERROR"; -- LinkEventName["SUBMIT_ROUTING_NUMBER"] = "SUBMIT_ROUTING_NUMBER"; -- LinkEventName["VIEW_DATA_TYPES"] = "VIEW_DATA_TYPES"; -- LinkEventName["SUBMIT_PHONE"] = "SUBMIT_PHONE"; -- LinkEventName["SKIP_SUBMIT_PHONE"] = "SKIP_SUBMIT_PHONE"; -- LinkEventName["VERIFY_PHONE"] = "VERIFY_PHONE"; -- LinkEventName["SUBMIT_CREDENTIALS"] = "SUBMIT_CREDENTIALS"; -- LinkEventName["SUBMIT_MFA"] = "SUBMIT_MFA"; -- LinkEventName["TRANSITION_VIEW"] = "TRANSITION_VIEW"; -- LinkEventName["CONNECT_NEW_INSTITUTION"] = "CONNECT_NEW_INSTITUTION"; -+(function(LinkEventName) { -+ LinkEventName['BANK_INCOME_INSIGHTS_COMPLETED'] = -+ 'BANK_INCOME_INSIGHTS_COMPLETED'; -+ LinkEventName['CLOSE_OAUTH'] = 'CLOSE_OAUTH'; -+ LinkEventName['ERROR'] = 'ERROR'; -+ LinkEventName['EXIT'] = 'EXIT'; -+ LinkEventName['FAIL_OAUTH'] = 'FAIL_OAUTH'; -+ LinkEventName['HANDOFF'] = 'HANDOFF'; -+ LinkEventName['IDENTITY_VERIFICATION_START_STEP'] = -+ 'IDENTITY_VERIFICATION_START_STEP'; -+ LinkEventName['IDENTITY_VERIFICATION_PASS_STEP'] = -+ 'IDENTITY_VERIFICATION_PASS_STEP'; -+ LinkEventName['IDENTITY_VERIFICATION_FAIL_STEP'] = -+ 'IDENTITY_VERIFICATION_FAIL_STEP'; -+ LinkEventName['IDENTITY_VERIFICATION_PENDING_REVIEW_STEP'] = -+ 'IDENTITY_VERIFICATION_PENDING_REVIEW_STEP'; -+ LinkEventName['IDENTITY_VERIFICATION_PENDING_REVIEW_SESSION'] = -+ 'IDENTITY_VERIFICATION_PENDING_REVIEW_SESSION'; -+ LinkEventName['IDENTITY_VERIFICATION_CREATE_SESSION'] = -+ 'IDENTITY_VERIFICATION_CREATE_SESSION'; -+ LinkEventName['IDENTITY_VERIFICATION_RESUME_SESSION'] = -+ 'IDENTITY_VERIFICATION_RESUME_SESSION'; -+ LinkEventName['IDENTITY_VERIFICATION_PASS_SESSION'] = -+ 'IDENTITY_VERIFICATION_PASS_SESSION'; -+ LinkEventName['IDENTITY_VERIFICATION_FAIL_SESSION'] = -+ 'IDENTITY_VERIFICATION_FAIL_SESSION'; -+ LinkEventName['IDENTITY_VERIFICATION_OPEN_UI'] = -+ 'IDENTITY_VERIFICATION_OPEN_UI'; -+ LinkEventName['IDENTITY_VERIFICATION_RESUME_UI'] = -+ 'IDENTITY_VERIFICATION_RESUME_UI'; -+ LinkEventName['IDENTITY_VERIFICATION_CLOSE_UI'] = -+ 'IDENTITY_VERIFICATION_CLOSE_UI'; -+ LinkEventName['MATCHED_CONSENT'] = 'MATCHED_CONSENT'; -+ LinkEventName['MATCHED_SELECT_INSTITUTION'] = 'MATCHED_SELECT_INSTITUTION'; -+ LinkEventName['MATCHED_SELECT_VERIFY_METHOD'] = -+ 'MATCHED_SELECT_VERIFY_METHOD'; -+ LinkEventName['OPEN'] = 'OPEN'; -+ LinkEventName['OPEN_MY_PLAID'] = 'OPEN_MY_PLAID'; -+ LinkEventName['OPEN_OAUTH'] = 'OPEN_OAUTH'; -+ LinkEventName['SEARCH_INSTITUTION'] = 'SEARCH_INSTITUTION'; -+ LinkEventName['SELECT_DEGRADED_INSTITUTION'] = 'SELECT_DEGRADED_INSTITUTION'; -+ LinkEventName['SELECT_DOWN_INSTITUTION'] = 'SELECT_DOWN_INSTITUTION'; -+ LinkEventName['SELECT_FILTERED_INSTITUTION'] = 'SELECT_FILTERED_INSTITUTION'; -+ LinkEventName['SELECT_INSTITUTION'] = 'SELECT_INSTITUTION'; -+ LinkEventName['SELECT_BRAND'] = 'SELECT_BRAND'; -+ LinkEventName['SELECT_AUTH_TYPE'] = 'SELECT_AUTH_TYPE'; -+ LinkEventName['SUBMIT_ACCOUNT_NUMBER'] = 'SUBMIT_ACCOUNT_NUMBER'; -+ LinkEventName['SUBMIT_DOCUMENTS'] = 'SUBMIT_DOCUMENTS'; -+ LinkEventName['SUBMIT_DOCUMENTS_SUCCESS'] = 'SUBMIT_DOCUMENTS_SUCCESS'; -+ LinkEventName['SUBMIT_DOCUMENTS_ERROR'] = 'SUBMIT_DOCUMENTS_ERROR'; -+ LinkEventName['SUBMIT_ROUTING_NUMBER'] = 'SUBMIT_ROUTING_NUMBER'; -+ LinkEventName['VIEW_DATA_TYPES'] = 'VIEW_DATA_TYPES'; -+ LinkEventName['SUBMIT_PHONE'] = 'SUBMIT_PHONE'; -+ LinkEventName['SKIP_SUBMIT_PHONE'] = 'SKIP_SUBMIT_PHONE'; -+ LinkEventName['VERIFY_PHONE'] = 'VERIFY_PHONE'; -+ LinkEventName['SUBMIT_CREDENTIALS'] = 'SUBMIT_CREDENTIALS'; -+ LinkEventName['SUBMIT_MFA'] = 'SUBMIT_MFA'; -+ LinkEventName['TRANSITION_VIEW'] = 'TRANSITION_VIEW'; -+ LinkEventName['CONNECT_NEW_INSTITUTION'] = 'CONNECT_NEW_INSTITUTION'; - })(LinkEventName || (LinkEventName = {})); - export var LinkEventViewName; --(function (LinkEventViewName) { -- LinkEventViewName["ACCEPT_TOS"] = "ACCEPT_TOS"; -- LinkEventViewName["CONNECTED"] = "CONNECTED"; -- LinkEventViewName["CONSENT"] = "CONSENT"; -- LinkEventViewName["CREDENTIAL"] = "CREDENTIAL"; -- LinkEventViewName["DATA_TRANSPARENCY"] = "DATA_TRANSPARENCY"; -- LinkEventViewName["DATA_TRANSPARENCY_CONSENT"] = "DATA_TRANSPARENCY_CONSENT"; -- LinkEventViewName["DOCUMENTARY_VERIFICATION"] = "DOCUMENTARY_VERIFICATION"; -- LinkEventViewName["ERROR"] = "ERROR"; -- LinkEventViewName["EXIT"] = "EXIT"; -- LinkEventViewName["KYC_CHECK"] = "KYC_CHECK"; -- LinkEventViewName["SELFIE_CHECK"] = "SELFIE_CHECK"; -- LinkEventViewName["LOADING"] = "LOADING"; -- LinkEventViewName["MATCHED_CONSENT"] = "MATCHED_CONSENT"; -- LinkEventViewName["MATCHED_CREDENTIAL"] = "MATCHED_CREDENTIAL"; -- LinkEventViewName["MATCHED_MFA"] = "MATCHED_MFA"; -- LinkEventViewName["MFA"] = "MFA"; -- LinkEventViewName["NUMBERS"] = "NUMBERS"; -- LinkEventViewName["NUMBERS_SELECT_INSTITUTION"] = "NUMBERS_SELECT_INSTITUTION"; -- LinkEventViewName["OAUTH"] = "OAUTH"; -- LinkEventViewName["RECAPTCHA"] = "RECAPTCHA"; -- LinkEventViewName["RISK_CHECK"] = "RISK_CHECK"; -- LinkEventViewName["SCREENING"] = "SCREENING"; -- LinkEventViewName["SELECT_ACCOUNT"] = "SELECT_ACCOUNT"; -- LinkEventViewName["SELECT_AUTH_TYPE"] = "SELECT_AUTH_TYPE"; -- LinkEventViewName["SUBMIT_PHONE"] = "SUBMIT_PHONE"; -- LinkEventViewName["VERIFY_PHONE"] = "VERIFY_PHONE"; -- LinkEventViewName["SELECT_SAVED_INSTITUTION"] = "SELECT_SAVED_INSTITUTION"; -- LinkEventViewName["SELECT_SAVED_ACCOUNT"] = "SELECT_SAVED_ACCOUNT"; -- LinkEventViewName["SELECT_BRAND"] = "SELECT_BRAND"; -- LinkEventViewName["SELECT_INSTITUTION"] = "SELECT_INSTITUTION"; -- LinkEventViewName["SUBMIT_DOCUMENTS"] = "SUBMIT_DOCUMENTS"; -- LinkEventViewName["SUBMIT_DOCUMENTS_SUCCESS"] = "SUBMIT_DOCUMENTS_SUCCESS"; -- LinkEventViewName["SUBMIT_DOCUMENTS_ERROR"] = "SUBMIT_DOCUMENTS_ERROR"; -- LinkEventViewName["UPLOAD_DOCUMENTS"] = "UPLOAD_DOCUMENTS"; -- LinkEventViewName["VERIFY_SMS"] = "VERIFY_SMS"; -+(function(LinkEventViewName) { -+ LinkEventViewName['ACCEPT_TOS'] = 'ACCEPT_TOS'; -+ LinkEventViewName['CONNECTED'] = 'CONNECTED'; -+ LinkEventViewName['CONSENT'] = 'CONSENT'; -+ LinkEventViewName['CREDENTIAL'] = 'CREDENTIAL'; -+ LinkEventViewName['DATA_TRANSPARENCY'] = 'DATA_TRANSPARENCY'; -+ LinkEventViewName['DATA_TRANSPARENCY_CONSENT'] = 'DATA_TRANSPARENCY_CONSENT'; -+ LinkEventViewName['DOCUMENTARY_VERIFICATION'] = 'DOCUMENTARY_VERIFICATION'; -+ LinkEventViewName['ERROR'] = 'ERROR'; -+ LinkEventViewName['EXIT'] = 'EXIT'; -+ LinkEventViewName['KYC_CHECK'] = 'KYC_CHECK'; -+ LinkEventViewName['SELFIE_CHECK'] = 'SELFIE_CHECK'; -+ LinkEventViewName['LOADING'] = 'LOADING'; -+ LinkEventViewName['MATCHED_CONSENT'] = 'MATCHED_CONSENT'; -+ LinkEventViewName['MATCHED_CREDENTIAL'] = 'MATCHED_CREDENTIAL'; -+ LinkEventViewName['MATCHED_MFA'] = 'MATCHED_MFA'; -+ LinkEventViewName['MFA'] = 'MFA'; -+ LinkEventViewName['NUMBERS'] = 'NUMBERS'; -+ LinkEventViewName['NUMBERS_SELECT_INSTITUTION'] = -+ 'NUMBERS_SELECT_INSTITUTION'; -+ LinkEventViewName['OAUTH'] = 'OAUTH'; -+ LinkEventViewName['RECAPTCHA'] = 'RECAPTCHA'; -+ LinkEventViewName['RISK_CHECK'] = 'RISK_CHECK'; -+ LinkEventViewName['SCREENING'] = 'SCREENING'; -+ LinkEventViewName['SELECT_ACCOUNT'] = 'SELECT_ACCOUNT'; -+ LinkEventViewName['SELECT_AUTH_TYPE'] = 'SELECT_AUTH_TYPE'; -+ LinkEventViewName['SUBMIT_PHONE'] = 'SUBMIT_PHONE'; -+ LinkEventViewName['VERIFY_PHONE'] = 'VERIFY_PHONE'; -+ LinkEventViewName['SELECT_SAVED_INSTITUTION'] = 'SELECT_SAVED_INSTITUTION'; -+ LinkEventViewName['SELECT_SAVED_ACCOUNT'] = 'SELECT_SAVED_ACCOUNT'; -+ LinkEventViewName['SELECT_BRAND'] = 'SELECT_BRAND'; -+ LinkEventViewName['SELECT_INSTITUTION'] = 'SELECT_INSTITUTION'; -+ LinkEventViewName['SUBMIT_DOCUMENTS'] = 'SUBMIT_DOCUMENTS'; -+ LinkEventViewName['SUBMIT_DOCUMENTS_SUCCESS'] = 'SUBMIT_DOCUMENTS_SUCCESS'; -+ LinkEventViewName['SUBMIT_DOCUMENTS_ERROR'] = 'SUBMIT_DOCUMENTS_ERROR'; -+ LinkEventViewName['UPLOAD_DOCUMENTS'] = 'UPLOAD_DOCUMENTS'; -+ LinkEventViewName['VERIFY_SMS'] = 'VERIFY_SMS'; - })(LinkEventViewName || (LinkEventViewName = {})); - /// Methods to present Link on iOS. - /// FULL_SCREEN is the converts to UIModalPresentationOverFullScreen on the native side. - /// MODAL will use the default presentation style for iOS which is UIModalPresentationAutomatic. - export var LinkIOSPresentationStyle; --(function (LinkIOSPresentationStyle) { -- LinkIOSPresentationStyle["FULL_SCREEN"] = "FULL_SCREEN"; -- LinkIOSPresentationStyle["MODAL"] = "MODAL"; -+(function(LinkIOSPresentationStyle) { -+ LinkIOSPresentationStyle['FULL_SCREEN'] = 'FULL_SCREEN'; -+ LinkIOSPresentationStyle['MODAL'] = 'MODAL'; - })(LinkIOSPresentationStyle || (LinkIOSPresentationStyle = {})); -diff --git a/node_modules/react-native-plaid-link-sdk/dist/__tests__/Types.tests.d.ts b/node_modules/react-native-plaid-link-sdk/dist/__tests__/Types.tests.d.ts -index cd4ccde..cb0ff5c 100644 ---- a/node_modules/react-native-plaid-link-sdk/dist/__tests__/Types.tests.d.ts -+++ b/node_modules/react-native-plaid-link-sdk/dist/__tests__/Types.tests.d.ts -@@ -1 +1 @@ --declare const Types: any; -+export {}; -diff --git a/node_modules/react-native-plaid-link-sdk/dist/__tests__/Types.tests.js b/node_modules/react-native-plaid-link-sdk/dist/__tests__/Types.tests.js -index 831866b..f38aff8 100644 ---- a/node_modules/react-native-plaid-link-sdk/dist/__tests__/Types.tests.js -+++ b/node_modules/react-native-plaid-link-sdk/dist/__tests__/Types.tests.js -@@ -1,14 +1,14 @@ --"use strict"; - const Types = require('./../Types'); - test('test token configuration', () => { -- const linkTokenConfiguration = { -- token: "test-token", -- noLoadingState: false, -- logLevel: Types.LinkLogLevel.DEBUG, -- extras: null, -- }; -- expect(linkTokenConfiguration.noLoadingState).toBe(false); -- expect(linkTokenConfiguration.token).toBe("test-token"); -- expect(linkTokenConfiguration.logLevel).toBe(Types.LinkLogLevel.DEBUG); -- expect(linkTokenConfiguration.extras).toBe(null); -+ const linkTokenConfiguration = { -+ token: 'test-token', -+ noLoadingState: false, -+ logLevel: Types.LinkLogLevel.DEBUG, -+ extras: null, -+ }; -+ expect(linkTokenConfiguration.noLoadingState).toBe(false); -+ expect(linkTokenConfiguration.token).toBe('test-token'); -+ expect(linkTokenConfiguration.logLevel).toBe(Types.LinkLogLevel.DEBUG); -+ expect(linkTokenConfiguration.extras).toBe(null); - }); -+export {}; -diff --git a/node_modules/react-native-plaid-link-sdk/dist/check_version_hook.d.ts b/node_modules/react-native-plaid-link-sdk/dist/check_version_hook.d.ts -new file mode 100644 -index 0000000..cb0ff5c ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/dist/check_version_hook.d.ts -@@ -0,0 +1 @@ -+export {}; -diff --git a/node_modules/react-native-plaid-link-sdk/dist/check_version_hook.js b/node_modules/react-native-plaid-link-sdk/dist/check_version_hook.js -new file mode 100644 -index 0000000..34a47d4 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/dist/check_version_hook.js -@@ -0,0 +1,15 @@ -+'use strict'; -+const fs = require('fs'); -+const rn_version = JSON.parse(fs.readFileSync('package.json', 'utf-8'))[ -+ 'version' -+]; -+const android_version = fs -+ .readFileSync('android/src/main/AndroidManifest.xml', 'utf-8') -+ .match(/(?<=value=").*(?=")/)[0]; -+if (rn_version != android_version) { -+ console.error('Commit failed SDK version mismatch'); -+ console.error( -+ 'Please ensure package.json and android/src/main/AndroidManifest.xml have the same version', -+ ); -+ process.exit(-1); -+} -diff --git a/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModule.d.ts b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModule.d.ts -new file mode 100644 -index 0000000..1de3019 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModule.d.ts -@@ -0,0 +1,14 @@ -+import { TurboModule } from 'react-native'; -+import { Int32 } from 'react-native/Libraries/Types/CodegenTypes'; -+import { LinkSuccess as UnsafeObject, LinkExit as Double, LinkError as Float } from '../Types'; -+export interface Spec extends TurboModule { -+ continueFromRedirectUriString(redirectUriString: string): void; -+ create(configuration: Object): void; -+ open(onSuccess: (success: UnsafeObject) => void, onExit: (error: Float, result: Double) => void): void; -+ dismiss(): void; -+ startLinkActivityForResult(data: string, onSuccessCallback: (result: UnsafeObject) => void, onExitCallback: (result: Double) => void): void; -+ addListener(eventName: string): void; -+ removeListeners(count: Int32): void; -+} -+declare const _default: Spec; -+export default _default; -diff --git a/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModule.js b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModule.js -new file mode 100644 -index 0000000..310a9c5 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModule.js -@@ -0,0 +1,4 @@ -+// we use Object type because methods on the native side use NSDictionary and ReadableMap -+// and we want to stay compatible with those -+import { TurboModuleRegistry } from 'react-native'; -+export default TurboModuleRegistry.getEnforcing('RNLinksdk'); -diff --git a/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleAndroid.d.ts b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleAndroid.d.ts -new file mode 100644 -index 0000000..82f29a1 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleAndroid.d.ts -@@ -0,0 +1,9 @@ -+import { TurboModule } from 'react-native'; -+import { Int32, UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'; -+export interface Spec extends TurboModule { -+ startLinkActivityForResult(token: string, noLoadingState: boolean, logLevel: string, onSuccessCallback: (result: UnsafeObject) => void, onExitCallback: (result: UnsafeObject) => void): void; -+ addListener(eventName: string): void; -+ removeListeners(count: Int32): void; -+} -+declare const _default: Spec | null; -+export default _default; -diff --git a/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleAndroid.js b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleAndroid.js -new file mode 100644 -index 0000000..d0ea456 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleAndroid.js -@@ -0,0 +1,4 @@ -+// we use Object type because methods on the native side use NSDictionary and ReadableMap -+// and we want to stay compatible with those -+import { TurboModuleRegistry } from 'react-native'; -+export default TurboModuleRegistry.get('PlaidAndroid'); -diff --git a/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleiOS.d.ts b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleiOS.d.ts -new file mode 100644 -index 0000000..aefee8c ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleiOS.d.ts -@@ -0,0 +1,11 @@ -+import { TurboModule } from 'react-native'; -+import { Int32, UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'; -+export interface Spec extends TurboModule { -+ create(token: string, noLoadingState: boolean): void; -+ open(fullScreen: boolean, onSuccess: (success: UnsafeObject) => void, onExit: (error: UnsafeObject, result: UnsafeObject) => void): void; -+ dismiss(): void; -+ addListener(eventName: string): void; -+ removeListeners(count: Int32): void; -+} -+declare const _default: Spec | null; -+export default _default; -diff --git a/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleiOS.js b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleiOS.js -new file mode 100644 -index 0000000..99845a1 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/dist/fabric/NativePlaidLinkModuleiOS.js -@@ -0,0 +1,4 @@ -+// we use Object type because methods on the native side use NSDictionary and ReadableMap -+// and we want to stay compatible with those -+import { TurboModuleRegistry } from 'react-native'; -+export default TurboModuleRegistry.get('RNLinksdk'); -diff --git a/node_modules/react-native-plaid-link-sdk/dist/index.js b/node_modules/react-native-plaid-link-sdk/dist/index.js -index 68c2d4b..acad079 100644 ---- a/node_modules/react-native-plaid-link-sdk/dist/index.js -+++ b/node_modules/react-native-plaid-link-sdk/dist/index.js -@@ -1,6 +1,6 @@ --import { openLink, dismissLink, usePlaidEmitter, PlaidLink, } from './PlaidLink'; -+import { openLink, dismissLink, usePlaidEmitter, PlaidLink } from './PlaidLink'; - export * from './Types'; - export default PlaidLink; --export { PlaidLink, openLink, dismissLink, usePlaidEmitter, }; -+export { PlaidLink, openLink, dismissLink, usePlaidEmitter }; - // Components - export { EmbeddedLinkView } from './EmbeddedLink/EmbeddedLinkView'; -diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.h b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.h -index 8a1c350..035b91c 100644 ---- a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.h -+++ b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.h -@@ -1,15 +1,17 @@ -- --#if __has_include() --#import --#import --#else --#import "RCTBridgeModule.h" --#import "RCTEventEmitter.h" -+#ifdef RCT_NEW_ARCH_ENABLED -+#import - #endif -+#import -+#import "RCTEventEmitter.h" - - #import - --@interface RNLinksdk : RCTEventEmitter -+@interface RNLinksdk : RCTEventEmitter -+#ifdef RCT_NEW_ARCH_ENABLED -+ -+#else -+ -+#endif - - + (NSDictionary *)dictionaryFromSuccess:(PLKLinkSuccess *)success; - + (NSDictionary *)dictionaryFromEvent:(PLKLinkEvent *)event; -diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.mm b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.mm -index ef3fe85..b3b92d6 100644 ---- a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.mm -+++ b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.mm -@@ -1,4 +1,5 @@ - #import "RNLinksdk.h" -+#import "RNPlaidHelper.h" - - #import - #import -@@ -66,11 +67,11 @@ - (void)stopObserving { - self.hasObservers = NO; - } - --RCT_EXPORT_METHOD(create:(NSString*)token :(BOOL)noLoadingState) { -- __weak typeof(self) weakSelf = self; -+RCT_EXPORT_METHOD(create:(NSString*)token noLoadingState:(BOOL)noLoadingState) { -+ __weak RNLinksdk *weakSelf = self; - - void (^onSuccess)(PLKLinkSuccess *) = ^(PLKLinkSuccess *success) { -- __typeof(weakSelf) strongSelf = weakSelf; -+ RNLinksdk *strongSelf = weakSelf; - - if (strongSelf.successCallback) { - NSDictionary *jsMetadata = [RNLinksdk dictionaryFromSuccess:success]; -@@ -80,7 +81,7 @@ - (void)stopObserving { - }; - - void (^onExit)(PLKLinkExit *) = ^(PLKLinkExit *exit) { -- __typeof(weakSelf) strongSelf = weakSelf; -+ RNLinksdk *strongSelf = weakSelf; - - if (strongSelf.exitCallback) { - NSDictionary *exitMetadata = [RNLinksdk dictionaryFromExit:exit]; -@@ -94,7 +95,7 @@ - (void)stopObserving { - }; - - void (^onEvent)(PLKLinkEvent *) = ^(PLKLinkEvent *event) { -- __typeof(weakSelf) strongSelf = weakSelf; -+ RNLinksdk *strongSelf = weakSelf; - if (strongSelf.hasObservers) { - NSDictionary *eventDictionary = [RNLinksdk dictionaryFromEvent:event]; - [strongSelf sendEventWithName:kRNLinkKitOnEventEvent -@@ -108,11 +109,11 @@ - (void)stopObserving { - config.noLoadingState = noLoadingState; - - NSError *creationError = nil; -- self.linkHandler = [PLKPlaid createWithLinkTokenConfiguration:config error:&creationError]; -+ self.linkHandler = [RNPlaidHelper createWithLinkTokenConfiguration:config error:&creationError]; - self.creationError = creationError; - } - --RCT_EXPORT_METHOD(open: (BOOL)fullScreen :(RCTResponseSenderBlock)onSuccess :(RCTResponseSenderBlock)onExit) { -+RCT_EXPORT_METHOD(open:(BOOL)fullScreen onSuccess:(RCTResponseSenderBlock)onSuccess onExit:(RCTResponseSenderBlock)onExit) { - if (self.linkHandler) { - self.successCallback = onSuccess; - self.exitCallback = onExit; -@@ -122,7 +123,7 @@ - (void)stopObserving { - // unnecessarily invoked. - __block bool didPresent = NO; - -- __weak typeof(self) weakSelf = self; -+ __weak RNLinksdk *weakSelf = self; - void(^presentationHandler)(UIViewController *) = ^(UIViewController *linkViewController) { - - if (fullScreen) { -@@ -619,4 +620,12 @@ + (NSDictionary *)dictionaryFromExit:(PLKLinkExit *)exit { - }; - } - -+#if RCT_NEW_ARCH_ENABLED -+- (std::shared_ptr)getTurboModule: -+ (const facebook::react::ObjCTurboModule::InitParams &)params -+{ -+ return std::make_shared(params); -+} -+#endif -+ - @end -diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata -deleted file mode 100644 -index 919434a..0000000 ---- a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata -+++ /dev/null -@@ -1,7 +0,0 @@ -- -- -- -- -- -diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist -deleted file mode 100644 -index 18d9810..0000000 ---- a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist -+++ /dev/null -@@ -1,8 +0,0 @@ -- -- -- -- -- IDEDidComputeMac32BitWarning -- -- -- -diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/project.xcworkspace/xcuserdata/dtroupe.xcuserdatad/UserInterfaceState.xcuserstate b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/project.xcworkspace/xcuserdata/dtroupe.xcuserdatad/UserInterfaceState.xcuserstate -deleted file mode 100644 -index 47e9cc2..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/project.xcworkspace/xcuserdata/dtroupe.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/xcuserdata/dtroupe.xcuserdatad/xcschemes/xcschememanagement.plist b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/xcuserdata/dtroupe.xcuserdatad/xcschemes/xcschememanagement.plist -deleted file mode 100644 -index 5b4fa70..0000000 ---- a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcodeproj/xcuserdata/dtroupe.xcuserdatad/xcschemes/xcschememanagement.plist -+++ /dev/null -@@ -1,19 +0,0 @@ -- -- -- -- -- SchemeUserState -- -- RNLinksdk.xcscheme_^#shared#^_ -- -- orderHint -- 0 -- -- RNLinksdkUnitTests.xcscheme_^#shared#^_ -- -- orderHint -- 1 -- -- -- -- -diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcworkspace/xcuserdata/dtroupe.xcuserdatad/UserInterfaceState.xcuserstate b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcworkspace/xcuserdata/dtroupe.xcuserdatad/UserInterfaceState.xcuserstate -deleted file mode 100644 -index 824773d..0000000 -Binary files a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.xcworkspace/xcuserdata/dtroupe.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ -diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNPlaidHelper.h b/node_modules/react-native-plaid-link-sdk/ios/RNPlaidHelper.h -new file mode 100644 -index 0000000..535d333 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/ios/RNPlaidHelper.h -@@ -0,0 +1,7 @@ -+#import -+ -+@interface RNPlaidHelper : NSObject -+ -++ (id _Nullable)createWithLinkTokenConfiguration:(PLKLinkTokenConfiguration * _Nonnull)linkTokenConfiguration error:(NSError * _Nullable * _Nullable)error; -+ -+@end -diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNPlaidHelper.m b/node_modules/react-native-plaid-link-sdk/ios/RNPlaidHelper.m -new file mode 100644 -index 0000000..2288299 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/ios/RNPlaidHelper.m -@@ -0,0 +1,10 @@ -+#import "RNPlaidHelper.h" -+ -+@implementation RNPlaidHelper -+ -++ (id _Nullable)createWithLinkTokenConfiguration:(PLKLinkTokenConfiguration * _Nonnull)linkTokenConfiguration error:(NSError * _Nullable * _Nullable)error -+{ -+ return [PLKPlaid createWithLinkTokenConfiguration:linkTokenConfiguration error:error]; -+} -+ -+@end -diff --git a/node_modules/react-native-plaid-link-sdk/package.json b/node_modules/react-native-plaid-link-sdk/package.json -index 22c7d2c..3d1b85c 100644 ---- a/node_modules/react-native-plaid-link-sdk/package.json -+++ b/node_modules/react-native-plaid-link-sdk/package.json -@@ -4,11 +4,13 @@ - "description": "React Native Plaid Link SDK", - "main": "dist/index.js", - "types": "dist/index.d.ts", -+ "react-native": "src/index.ts", - "files": [ - "dist/**/*", - "android/**/*", -- "ios", -- "react-native-plaid-link-sdk.podspec" -+ "ios/**/*", -+ "react-native-plaid-link-sdk.podspec", -+ "src/**/*" - ], - "scripts": { - "lint": "eslint \"./**/*.{js,jsx}\" --fix", -@@ -47,7 +49,7 @@ - "@react-native-community/eslint-plugin": "^1.1.0", - "@types/jest": "^26.0.14", - "@types/react": "^16.14.20", -- "@types/react-native": "^0.66.0", -+ "@types/react-native": "^0.71.3", - "@types/react-test-renderer": "^16.9.3", - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/parser": "^4.33.0", -@@ -62,5 +64,16 @@ - "react": "18.0.0", - "react-native": "0.69.9", - "typescript": "^4.9.5" -+ }, -+ "dependencies": { -+ "react-native-plaid-link-sdk": "^10.4.0" -+ }, -+ "codegenConfig": { -+ "name": "rnplaidlink", -+ "type": "modules", -+ "jsSrcsDir": "./src/fabric", -+ "android": { -+ "javaPackageName": "com.plaid" -+ } - } - } -diff --git a/node_modules/react-native-plaid-link-sdk/react-native-plaid-link-sdk.podspec b/node_modules/react-native-plaid-link-sdk/react-native-plaid-link-sdk.podspec -index ee59a19..40ac7df 100644 ---- a/node_modules/react-native-plaid-link-sdk/react-native-plaid-link-sdk.podspec -+++ b/node_modules/react-native-plaid-link-sdk/react-native-plaid-link-sdk.podspec -@@ -2,6 +2,8 @@ require 'json' - - package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) - -+fabric_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1' -+ - Pod::Spec.new do |s| - s.name = package['name'] - s.version = package['version'] -@@ -13,8 +15,13 @@ Pod::Spec.new do |s| - s.platform = :ios, "14.0" - - s.source = { :git => "https://github.com/plaid/react-native-plaid-link-sdk.git", :tag => "v#{s.version}" } -- s.source_files = "ios/*.{h,m,swift}" -+ s.source_files = "ios/**/*.{h,m,mm,swift}" -+ -+ if fabric_enabled -+ install_modules_dependencies(s) -+ else -+ s.dependency "React-Core" -+ end - -- s.dependency 'React-Core' - s.dependency 'Plaid', '~> 5.2.0' - end -diff --git a/node_modules/react-native-plaid-link-sdk/src/EmbeddedLink/EmbeddedLinkView.tsx b/node_modules/react-native-plaid-link-sdk/src/EmbeddedLink/EmbeddedLinkView.tsx -new file mode 100644 -index 0000000..0243de2 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/src/EmbeddedLink/EmbeddedLinkView.tsx -@@ -0,0 +1,95 @@ -+import React from 'react'; -+import { StyleProp, ViewStyle } from 'react-native'; -+import NativeEmbeddedLinkView from './NativeEmbeddedLinkView'; -+import { -+ LinkSuccessListener, -+ LinkSuccess, -+ LinkExitListener, -+ LinkExit, -+ LinkIOSPresentationStyle, -+ LinkOnEventListener, -+ LinkEvent, -+ LinkEventName, -+ LinkEventMetadata, -+ LinkError, -+ LinkExitMetadata, -+ LinkSuccessMetadata, -+} from '../Types'; -+ -+type EmbeddedLinkProps = { -+ token: String, -+ iOSPresentationStyle: LinkIOSPresentationStyle, -+ onEvent: LinkOnEventListener | undefined, -+ onSuccess: LinkSuccessListener, -+ onExit: LinkExitListener | undefined, -+ style: StyleProp | undefined, -+} -+ -+class EmbeddedEvent implements LinkEvent { -+ eventName: LinkEventName; -+ metadata: LinkEventMetadata; -+ -+ constructor(event: any) { -+ this.eventName = event.eventName -+ this.metadata = event.metadata -+ } -+} -+ -+class EmbeddedExit implements LinkExit { -+ error: LinkError | undefined; -+ metadata: LinkExitMetadata; -+ -+ constructor(event: any) { -+ this.error = event.error; -+ this.metadata = event.metadata; -+ } -+} -+ -+class EmbeddedSuccess implements LinkSuccess { -+ publicToken: string; -+ metadata: LinkSuccessMetadata; -+ -+ constructor(event: any) { -+ this.publicToken = event.publicToken; -+ this.metadata = event.metadata; -+ } -+} -+ -+export const EmbeddedLinkView: React.FC = (props) => { -+ -+ const {token, iOSPresentationStyle, onEvent, onSuccess, onExit, style} = props; -+ -+ const onEmbeddedEvent = (event: any) => { -+ -+ switch (event.nativeEvent.embeddedEventName) { -+ case 'onSuccess': { -+ if (!onSuccess) { return; } -+ const embeddedSuccess = new EmbeddedSuccess(event.nativeEvent); -+ onSuccess(embeddedSuccess); -+ break; -+ } -+ case 'onExit': { -+ if (!onExit) {return; } -+ const embeddedExit = new EmbeddedExit(event.nativeEvent); -+ onExit(embeddedExit); -+ break; -+ } -+ case 'onEvent': { -+ if (!onEvent) { return; } -+ const embeddedEvent = new EmbeddedEvent(event.nativeEvent); -+ onEvent(embeddedEvent); -+ break; -+ } -+ default: { -+ return; -+ } -+ } -+ } -+ -+ return -+}; -\ No newline at end of file -diff --git a/node_modules/react-native-plaid-link-sdk/src/EmbeddedLink/EmbeddedLinkView.web.tsx b/node_modules/react-native-plaid-link-sdk/src/EmbeddedLink/EmbeddedLinkView.web.tsx -new file mode 100644 -index 0000000..7a71609 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/src/EmbeddedLink/EmbeddedLinkView.web.tsx -@@ -0,0 +1,5 @@ -+// EmbeddedLinkView.web.tsx is a shim file which causes web bundlers to ignore the EmbeddedLinkView.tsx file -+// which imports requireNativeComponent (causing a runtime error with react-native-web). -+// Ref - https://github.com/plaid/react-native-plaid-link-sdk/issues/564 -+import React from 'react'; -+export const EmbeddedLinkView = () => null; -diff --git a/node_modules/react-native-plaid-link-sdk/src/EmbeddedLink/NativeEmbeddedLinkView.tsx b/node_modules/react-native-plaid-link-sdk/src/EmbeddedLink/NativeEmbeddedLinkView.tsx -new file mode 100644 -index 0000000..da05fb1 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/src/EmbeddedLink/NativeEmbeddedLinkView.tsx -@@ -0,0 +1,9 @@ -+import { requireNativeComponent } from 'react-native'; -+ -+// Error "Tried to register two views with the same name PLKEmbeddedView" -+// will be thrown during hot reload when any change is made to the -+// file that is calling this requireNativeComponent('PLKEmbeddedView') call. -+// Leaving this in its own file resolves this issue. -+const NativeEmbeddedLinkView = requireNativeComponent('PLKEmbeddedView'); -+ -+export default NativeEmbeddedLinkView; -\ No newline at end of file -diff --git a/node_modules/react-native-plaid-link-sdk/src/PlaidLink.tsx b/node_modules/react-native-plaid-link-sdk/src/PlaidLink.tsx -new file mode 100644 -index 0000000..b35c06f ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/src/PlaidLink.tsx -@@ -0,0 +1,117 @@ -+import React, { useEffect } from 'react'; -+import { NativeEventEmitter, Platform, TouchableOpacity } from 'react-native'; -+import { -+ LinkError, -+ LinkEventListener, -+ LinkExit, -+ LinkIOSPresentationStyle, -+ LinkLogLevel, -+ LinkSuccess, -+ PlaidLinkComponentProps, -+ PlaidLinkProps, -+} from './Types'; -+import RNLinksdkAndroid from './fabric/NativePlaidLinkModuleAndroid'; -+import RNLinksdkiOS from './fabric/NativePlaidLinkModuleiOS'; -+ -+const RNLinksdk = (Platform.OS === 'android' ? RNLinksdkAndroid : RNLinksdkiOS) ?? undefined; -+ -+/** -+ * A hook that registers a listener on the Plaid emitter for the 'onEvent' type. -+ * The listener is cleaned up when this view is unmounted -+ * -+ * @param linkEventListener the listener to call -+ */ -+export const usePlaidEmitter = (linkEventListener: LinkEventListener) => { -+ useEffect(() => { -+ const emitter = new NativeEventEmitter(RNLinksdk); -+ const listener = emitter.addListener('onEvent', linkEventListener); -+ // Clean up after this effect: -+ return function cleanup() { -+ listener.remove(); -+ }; -+ }, []); -+}; -+ -+export const openLink = async (props: PlaidLinkProps) => { -+ let config = props.tokenConfig; -+ let noLoadingState = config.noLoadingState ?? false; -+ -+ if (Platform.OS === 'android') { -+ if (RNLinksdkAndroid === null) { -+ throw new Error('[react-native-plaid-link-sdk] RNLinksdkAndroid is not defined'); -+ } -+ -+ RNLinksdkAndroid.startLinkActivityForResult( -+ config.token, -+ noLoadingState, -+ config.logLevel ?? LinkLogLevel.ERROR, -+ // @ts-ignore we use Object type in the spec file as it maps to NSDictionary and ReadableMap -+ (result: LinkSuccess) => { -+ if (props.onSuccess != null) { -+ props.onSuccess(result); -+ } -+ }, -+ (result: LinkExit) => { -+ if (props.onExit != null) { -+ if (result.error != null && result.error.displayMessage != null) { -+ //TODO(RNSDK-118): Remove errorDisplayMessage field in next major update. -+ result.error.errorDisplayMessage = result.error.displayMessage; -+ } -+ props.onExit(result); -+ } -+ }, -+ ); -+ } else { -+ if (RNLinksdkiOS === null) { -+ throw new Error('[react-native-plaid-link-sdk] RNLinksdkiOS is not defined'); -+ } -+ -+ RNLinksdkiOS.create(config.token, noLoadingState); -+ -+ let presentFullScreen = -+ props.iOSPresentationStyle == LinkIOSPresentationStyle.FULL_SCREEN; -+ -+ RNLinksdkiOS.open( -+ presentFullScreen, -+ // @ts-ignore we use Object type in the spec file as it maps to NSDictionary and ReadableMap -+ (result: LinkSuccess) => { -+ if (props.onSuccess != null) { -+ props.onSuccess(result); -+ } -+ }, -+ (error: LinkError, result: LinkExit) => { -+ if (props.onExit != null) { -+ if (error) { -+ var data = result || {}; -+ data.error = error; -+ props.onExit(data); -+ } else { -+ props.onExit(result); -+ } -+ } -+ }, -+ ); -+ } -+}; -+ -+export const dismissLink = () => { -+ if (Platform.OS === 'ios') { -+ if (RNLinksdkiOS === null) { -+ throw new Error('[react-native-plaid-link-sdk] RNLinksdkiOS is not defined'); -+ } -+ -+ RNLinksdkiOS.dismiss(); -+ } -+}; -+ -+export const PlaidLink = (props: PlaidLinkComponentProps) => { -+ function onPress() { -+ props.onPress?.(); -+ openLink(props); -+ } -+ -+ return ( -+ // @ts-ignore some types directories misconfiguration -+ {props.children} -+ ); -+}; -diff --git a/node_modules/react-native-plaid-link-sdk/src/Types.ts b/node_modules/react-native-plaid-link-sdk/src/Types.ts -new file mode 100644 -index 0000000..a7d30c6 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/src/Types.ts -@@ -0,0 +1,550 @@ -+interface CommonPlaidLinkOptions { -+ logLevel?: LinkLogLevel; -+ extras?: Record; -+} -+ -+export type LinkTokenConfiguration = (CommonPlaidLinkOptions & { -+ token: string; -+ // A `Bool` indicating that Link should skip displaying a loading animation until the Link UI is fully loaded. -+ // This can be used to display custom loading UI while Link content is loading (and will skip any initial loading UI in Link). -+ // Note: Dismiss custom loading UI on the OPEN & EXIT events. -+ // -+ // Note: This should be set to `true` when setting the `eu_config.headless` field in /link/token/create requests to `true`. -+ // For reference, see https://plaid.com/docs/api/tokens/#link-token-create-request-eu-config-headless -+ noLoadingState?: boolean; -+}); -+ -+export enum LinkLogLevel { -+ DEBUG="debug", -+ INFO="info", -+ WARN="warn", -+ ERROR="error", -+} -+ -+export enum PlaidEnvironment { -+ PRODUCTION = 'production', -+ DEVELOPMENT = 'development', -+ SANDBOX = 'sandbox', -+} -+ -+export enum PlaidProduct { -+ ASSETS="assets", -+ AUTH="auth", -+ DEPOSIT_SWITCH="deposit_switch", -+ IDENTITY="identity", -+ INCOME="income", -+ INVESTMENTS="investments", -+ LIABILITIES="liabilities", -+ LIABILITIES_REPORT="liabilities_report", -+ PAYMENT_INITIATION="payment_initiation", -+ TRANSACTIONS="transactions", -+} -+ -+export enum LinkAccountType { -+ CREDIT = 'credit', -+ DEPOSITORY = 'depository', -+ INVESTMENT = 'investment', -+ LOAN = 'loan', -+ OTHER = 'other', -+} -+ -+export enum LinkAccountSubtypes { -+ ALL = 'all', -+ CREDIT_CARD = 'credit card', -+ PAYPAL = 'paypal', -+ AUTO = 'auto', -+ BUSINESS = 'business', -+ COMMERCIAL = 'commercial', -+ CONSTRUCTION = 'construction', -+ CONSUMER = 'consumer', -+ HOME_EQUITY = 'home equity', -+ LINE_OF_CREDIT = 'line of credit', -+ LOAN = 'loan', -+ MORTGAGE = 'mortgage', -+ OVERDRAFT = 'overdraft', -+ STUDENT = 'student', -+ CASH_MANAGEMENT = 'cash management', -+ CD = 'cd', -+ CHECKING = 'checking', -+ EBT = 'ebt', -+ HSA = 'hsa', -+ MONEY_MARKET = 'money market', -+ PREPAID = 'prepaid', -+ SAVINGS = 'savings', -+ FOUR_0_1_A = '401a', -+ FOUR_0_1_K = '401k', -+ FOUR_0_3_B = '403B', -+ FOUR_5_7_B = '457b', -+ FIVE_2_9 = '529', -+ BROKERAGE = 'brokerage', -+ CASH_ISA = 'cash isa', -+ EDUCATION_SAVINGS_ACCOUNT = 'education savings account', -+ FIXED_ANNUNITY = 'fixed annuity', -+ GIC = 'gic', -+ HEALTH_REIMBURSEMENT_ARRANGEMENT = 'health reimbursement arrangement', -+ IRA = 'ira', -+ ISA = 'isa', -+ KEOGH = 'keogh', -+ LIF = 'lif', -+ LIRA = 'lira', -+ LRIF = 'lrif', -+ LRSP = 'lrsp', -+ MUTUAL_FUND = 'mutual fund', -+ NON_TAXABLE_BROKERAGE_ACCOUNT = 'non-taxable brokerage account', -+ PENSION = 'pension', -+ PLAN = 'plan', -+ PRIF = 'prif', -+ PROFIT_SHARING_PLAN = 'profit sharing plan', -+ RDSP = 'rdsp', -+ RESP = 'resp', -+ RETIREMENT = 'retirement', -+ RLIF = 'rlif', -+ ROTH_401K = 'roth 401k', -+ ROTH = 'roth', -+ RRIF = 'rrif', -+ RRSP = 'rrsp', -+ SARSEP = 'sarsep', -+ SEP_IRA = 'sep ira', -+ SIMPLE_IRA = 'simple ira', -+ SIPP = 'sipp', -+ STOCK_PLAN = 'stock plan', -+ TFSA = 'tfsa', -+ THRIFT_SAVINGS_PLAN = 'thrift savings plan', -+ TRUST = 'trust', -+ UGMA = 'ugma', -+ UTMA = 'utma', -+ VARIABLE_ANNUITY = 'variable annuity' -+} -+ -+export interface LinkAccountSubtype { -+} -+ -+export class LinkAccountSubtypeCredit implements LinkAccountSubtype { -+ public static readonly ALL = new LinkAccountSubtypeCredit(LinkAccountType.CREDIT, LinkAccountSubtypes.ALL); -+ public static readonly CREDIT_CARD = new LinkAccountSubtypeCredit(LinkAccountType.CREDIT, LinkAccountSubtypes.CREDIT_CARD); -+ public static readonly PAYPAL = new LinkAccountSubtypeCredit(LinkAccountType.CREDIT, LinkAccountSubtypes.PAYPAL); -+ -+ private constructor(public readonly type: LinkAccountType, public readonly subtype: LinkAccountSubtype) { } -+} -+ -+export class LinkAccountSubtypeDepository implements LinkAccountSubtype { -+ public static readonly ALL = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.ALL); -+ public static readonly CASH_MANAGEMENT = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.CASH_MANAGEMENT); -+ public static readonly CD = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.CD); -+ public static readonly CHECKING = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.CHECKING); -+ public static readonly EBT = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.EBT); -+ public static readonly HSA = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.HSA); -+ public static readonly MONEY_MARKET = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.MONEY_MARKET); -+ public static readonly PAYPAL = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.PAYPAL); -+ public static readonly PREPAID = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.PREPAID); -+ public static readonly SAVINGS = new LinkAccountSubtypeDepository(LinkAccountType.DEPOSITORY, LinkAccountSubtypes.SAVINGS); -+ -+ private constructor(public readonly type: LinkAccountType, public readonly subtype: LinkAccountSubtype) { } -+} -+ -+export class LinkAccountSubtypeInvestment implements LinkAccountSubtype { -+ public static readonly ALL = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.ALL); -+ public static readonly BROKERAGE = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.BROKERAGE); -+ public static readonly CASH_ISA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.CASH_ISA); -+ public static readonly EDUCATION_SAVINGS_ACCOUNT = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.EDUCATION_SAVINGS_ACCOUNT); -+ public static readonly FIXED_ANNUNITY = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FIXED_ANNUNITY); -+ public static readonly GIC = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.GIC); -+ public static readonly HEALTH_REIMBURSEMENT_ARRANGEMENT = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.HEALTH_REIMBURSEMENT_ARRANGEMENT); -+ public static readonly HSA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.HSA); -+ public static readonly INVESTMENT_401A = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FOUR_0_1_A); -+ public static readonly INVESTMENT_401K = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FOUR_0_1_K); -+ public static readonly INVESTMENT_403B = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FOUR_0_3_B); -+ public static readonly INVESTMENT_457B = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FOUR_5_7_B); -+ public static readonly INVESTMENT_529 = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.FIVE_2_9); -+ public static readonly IRA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.IRA); -+ public static readonly ISA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.ISA); -+ public static readonly KEOGH = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.KEOGH); -+ public static readonly LIF = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.LIF); -+ public static readonly LIRA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.LIRA); -+ public static readonly LRIF = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.LRIF); -+ public static readonly LRSP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.LRSP); -+ public static readonly MUTUAL_FUND = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.MUTUAL_FUND); -+ public static readonly NON_TAXABLE_BROKERAGE_ACCOUNT = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.NON_TAXABLE_BROKERAGE_ACCOUNT); -+ public static readonly PENSION = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.PENSION); -+ public static readonly PLAN = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.PLAN); -+ public static readonly PRIF = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.PRIF); -+ public static readonly PROFIT_SHARING_PLAN = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.PROFIT_SHARING_PLAN); -+ public static readonly RDSP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RDSP); -+ public static readonly RESP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RESP); -+ public static readonly RETIREMENT = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RETIREMENT); -+ public static readonly RLIF = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RLIF); -+ public static readonly ROTH = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.ROTH); -+ public static readonly ROTH_401K = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.ROTH_401K); -+ public static readonly RRIF = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RRIF); -+ public static readonly RRSP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.RRSP); -+ public static readonly SARSEP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.SARSEP); -+ public static readonly SEP_IRA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.SEP_IRA); -+ public static readonly SIMPLE_IRA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.SIMPLE_IRA); -+ public static readonly SIIP = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.SIPP); -+ public static readonly STOCK_PLAN = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.STOCK_PLAN); -+ public static readonly TFSA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.TFSA); -+ public static readonly THRIFT_SAVINGS_PLAN = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.THRIFT_SAVINGS_PLAN); -+ public static readonly TRUST = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.TRUST); -+ public static readonly UGMA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.UGMA); -+ public static readonly UTMA = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.UTMA); -+ public static readonly VARIABLE_ANNUITY = new LinkAccountSubtypeInvestment(LinkAccountType.INVESTMENT, LinkAccountSubtypes.VARIABLE_ANNUITY); -+ -+ private constructor(public readonly type: LinkAccountType, public readonly subtype: LinkAccountSubtype) { } -+} -+ -+export class LinkAccountSubtypeLoan implements LinkAccountSubtype { -+ public static readonly ALL = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.ALL); -+ public static readonly AUTO = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.AUTO); -+ public static readonly BUSINESS = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.BUSINESS); -+ public static readonly COMMERCIAL = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.COMMERCIAL); -+ public static readonly CONSTRUCTION = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.CONSTRUCTION); -+ public static readonly CONSUMER = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.CONSUMER); -+ public static readonly HOME_EQUITY = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.HOME_EQUITY); -+ public static readonly LINE_OF_CREDIT = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.LINE_OF_CREDIT); -+ public static readonly LOAN = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.LOAN); -+ public static readonly MORTGAGE = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.MORTGAGE); -+ public static readonly OVERDRAFT = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.OVERDRAFT); -+ public static readonly STUDENT = new LinkAccountSubtypeLoan(LinkAccountType.CREDIT, LinkAccountSubtypes.STUDENT); -+ -+ private constructor(public readonly type: LinkAccountType, public readonly subtype: LinkAccountSubtype) { } -+} -+ -+export class LinkAccountSubtypeUnknown implements LinkAccountSubtype { -+ constructor(public readonly type: string, public readonly subtype: string) { } -+} -+ -+export interface LinkSuccess { -+ publicToken: string; -+ metadata: LinkSuccessMetadata; -+} -+ -+export interface LinkSuccessMetadata { -+ institution?: LinkInstitution; -+ accounts: LinkAccount[]; -+ linkSessionId: string; -+ metadataJson?: string; -+} -+ -+export interface LinkAccount { -+ id: string; -+ name?: string; -+ mask?: string; -+ type: LinkAccountType; -+ subtype: LinkAccountSubtype; -+ verificationStatus?: LinkAccountVerificationStatus; -+} -+ -+export enum LinkAccountVerificationStatus { -+ PENDING_AUTOMATIC_VERIFICATION = 'pending_automatic_verification', -+ PENDING_MANUAL_VERIFICATION = 'pending_manual_verification', -+ MANUALLY_VERIFIED = 'manually_verified', -+} -+ -+export interface LinkInstitution { -+ id: string; -+ name: string; -+} -+ -+export interface LinkExit { -+ error?: LinkError; -+ metadata: LinkExitMetadata; -+} -+ -+export interface LinkExitMetadata { -+ status?: LinkExitMetadataStatus; -+ institution?: LinkInstitution; -+ linkSessionId: string; -+ requestId: string; -+ metadataJson?: string; -+} -+ -+export enum LinkExitMetadataStatus { -+ CONNECTED = 'connected', -+ CHOOSE_DEVICE = 'choose_device', -+ REQUIRES_ACCOUNT_SELECTION = 'requires_account_selection', -+ REQUIRES_CODE = 'requires_code', -+ REQUIRES_CREDENTIALS = 'requires_credentials', -+ REQUIRES_EXTERNAL_ACTION = 'requires_external_action', -+ REQUIRES_OAUTH = 'requires_oauth', -+ REQUIRES_QUESTIONS = 'requires_questions', -+ REQUIRES_RECAPTCHA = 'requires_recaptcha', -+ REQUIRES_SELECTIONS = 'requires_selections', -+ REQUIRES_DEPOSIT_SWITCH_ALLOCATION_CONFIGURATION = 'requires_deposit_switch_allocation_configuration', -+ REQUIRES_DEPOSIT_SWITCH_ALLOCATION_SELECTION = 'requires_deposit_switch_allocation_selection', -+} -+ -+export interface LinkError { -+ errorCode: LinkErrorCode; -+ errorType: LinkErrorType; -+ errorMessage: string; -+ /** @deprecated DO NOT USE, data not guaranteed. Use `displayMessage` instead */ -+ errorDisplayMessage?: string; -+ displayMessage?: string; -+ errorJson?: string; -+} -+ -+export enum LinkErrorCode { -+ // ITEM_ERROR -+ INVALID_CREDENTIALS = "INVALID_CREDENTIALS", -+ INVALID_MFA = "INVALID_MFA", -+ ITEM_LOGIN_REQUIRED = "ITEM_LOGIN_REQUIRED", -+ INSUFFICIENT_CREDENTIALS = "INSUFFICIENT_CREDENTIALS", -+ ITEM_LOCKED = "ITEM_LOCKED", -+ USER_SETUP_REQUIRED = "USER_SETUP_REQUIRED", -+ MFA_NOT_SUPPORTED = "MFA_NOT_SUPPORTED", -+ INVALID_SEND_METHOD = "INVALID_SEND_METHOD", -+ NO_ACCOUNTS = "NO_ACCOUNTS", -+ ITEM_NOT_SUPPORTED = "ITEM_NOT_SUPPORTED", -+ TOO_MANY_VERIFICATION_ATTEMPTS = "TOO_MANY_VERIFICATION_ATTEMPTS", -+ -+ INVALD_UPDATED_USERNAME = "INVALD_UPDATED_USERNAME", -+ INVALID_UPDATED_USERNAME = "INVALID_UPDATED_USERNAME", -+ -+ ITEM_NO_ERROR = "ITEM_NO_ERROR", -+ item_no_error = "item-no-error", -+ NO_AUTH_ACCOUNTS = "NO_AUTH_ACCOUNTS", -+ NO_INVESTMENT_ACCOUNTS = "NO_INVESTMENT_ACCOUNTS", -+ NO_INVESTMENT_AUTH_ACCOUNTS = "NO_INVESTMENT_AUTH_ACCOUNTS", -+ NO_LIABILITY_ACCOUNTS = "NO_LIABILITY_ACCOUNTS", -+ PRODUCTS_NOT_SUPPORTED = "PRODUCTS_NOT_SUPPORTED", -+ ITEM_NOT_FOUND = "ITEM_NOT_FOUND", -+ ITEM_PRODUCT_NOT_READY = "ITEM_PRODUCT_NOT_READY", -+ -+ // INSTITUTION_ERROR -+ INSTITUTION_DOWN = "INSTITUTION_DOWN", -+ INSTITUTION_NOT_RESPONDING = "INSTITUTION_NOT_RESPONDING", -+ INSTITUTION_NOT_AVAILABLE = "INSTITUTION_NOT_AVAILABLE", -+ INSTITUTION_NO_LONGER_SUPPORTED = "INSTITUTION_NO_LONGER_SUPPORTED", -+ -+ // API_ERROR -+ INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR", -+ PLANNED_MAINTENANCE = "PLANNED_MAINTENANCE", -+ -+ // ASSET_REPORT_ERROR -+ PRODUCT_NOT_ENABLED = "PRODUCT_NOT_ENABLED", -+ DATA_UNAVAILABLE = "DATA_UNAVAILABLE", -+ ASSET_PRODUCT_NOT_READY = "ASSET_PRODUCT_NOT_READY", -+ ASSET_REPORT_GENERATION_FAILED = "ASSET_REPORT_GENERATION_FAILED", -+ INVALID_PARENT = "INVALID_PARENT", -+ INSIGHTS_NOT_ENABLED = "INSIGHTS_NOT_ENABLED", -+ INSIGHTS_PREVIOUSLY_NOT_ENABLED = "INSIGHTS_PREVIOUSLY_NOT_ENABLED", -+ -+ // BANK_TRANSFER_ERROR -+ BANK_TRANSFER_LIMIT_EXCEEDED = "BANK_TRANSFER_LIMIT_EXCEEDED", -+ BANK_TRANSFER_MISSING_ORIGINATION_ACCOUNT = "BANK_TRANSFER_MISSING_ORIGINATION_ACCOUNT", -+ BANK_TRANSFER_INVALID_ORIGINATION_ACCOUNT = "BANK_TRANSFER_INVALID_ORIGINATION_ACCOUNT", -+ BANK_TRANSFER_ACCOUNT_BLOCKED = "BANK_TRANSFER_ACCOUNT_BLOCKED", -+ BANK_TRANSFER_INSUFFICIENT_FUNDS = "BANK_TRANSFER_INSUFFICIENT_FUNDS", -+ BANK_TRANSFER_NOT_CANCELLABLE = "BANK_TRANSFER_NOT_CANCELLABLE", -+ BANK_TRANSFER_UNSUPPORTED_ACCOUNT_TYPE = "BANK_TRANSFER_UNSUPPORTED_ACCOUNT_TYPE", -+ BANK_TRANSFER_UNSUPPORTED_ENVIRONMENT = "BANK_TRANSFER_UNSUPPORTED_ENVIRONMENT", -+ -+ // SANDBOX_ERROR -+ SANDBOX_PRODUCT_NOT_ENABLED = "SANDBOX_PRODUCT_NOT_ENABLED", -+ SANDBOX_WEBHOOK_INVALID = "SANDBOX_WEBHOOK_INVALID", -+ SANDBOX_BANK_TRANSFER_EVENT_TRANSITION_INVALID = "SANDBOX_BANK_TRANSFER_EVENT_TRANSITION_INVALID", -+ -+ // INVALID_REQUEST -+ MISSING_FIELDS = "MISSING_FIELDS", -+ UNKNOWN_FIELDS = "UNKNOWN_FIELDS", -+ INVALID_FIELD = "INVALID_FIELD", -+ INCOMPATIBLE_API_VERSION = "INCOMPATIBLE_API_VERSION", -+ INVALID_BODY = "INVALID_BODY", -+ INVALID_HEADERS = "INVALID_HEADERS", -+ NOT_FOUND = "NOT_FOUND", -+ NO_LONGER_AVAILABLE = "NO_LONGER_AVAILABLE", -+ SANDBOX_ONLY = "SANDBOX_ONLY", -+ INVALID_ACCOUNT_NUMBER = "INVALID_ACCOUNT_NUMBER", -+ -+ // INVALID_INPUT -+ // From above ITEM_LOGIN_REQUIRED = "INVALID_CREDENTIALS", -+ INCORRECT_DEPOSIT_AMOUNTS = "INCORRECT_DEPOSIT_AMOUNTS", -+ UNAUTHORIZED_ENVIRONMENT = "UNAUTHORIZED_ENVIRONMENT", -+ INVALID_PRODUCT = "INVALID_PRODUCT", -+ UNAUTHORIZED_ROUTE_ACCESS = "UNAUTHORIZED_ROUTE_ACCESS", -+ DIRECT_INTEGRATION_NOT_ENABLED = "DIRECT_INTEGRATION_NOT_ENABLED", -+ INVALID_API_KEYS = "INVALID_API_KEYS", -+ INVALID_ACCESS_TOKEN = "INVALID_ACCESS_TOKEN", -+ INVALID_PUBLIC_TOKEN = "INVALID_PUBLIC_TOKEN", -+ INVALID_LINK_TOKEN = "INVALID_LINK_TOKEN", -+ INVALID_PROCESSOR_TOKEN = "INVALID_PROCESSOR_TOKEN", -+ INVALID_AUDIT_COPY_TOKEN = "INVALID_AUDIT_COPY_TOKEN", -+ INVALID_ACCOUNT_ID = "INVALID_ACCOUNT_ID", -+ MICRODEPOSITS_ALREADY_VERIFIED = "MICRODEPOSITS_ALREADY_VERIFIED", -+ -+ // INVALID_RESULT -+ PLAID_DIRECT_ITEM_IMPORT_RETURNED_INVALID_MFA = "PLAID_DIRECT_ITEM_IMPORT_RETURNED_INVALID_MFA", -+ -+ // RATE_LIMIT_EXCEEDED -+ ACCOUNTS_LIMIT = "ACCOUNTS_LIMIT", -+ ADDITION_LIMIT = "ADDITION_LIMIT", -+ AUTH_LIMIT = "AUTH_LIMIT", -+ BALANCE_LIMIT = "BALANCE_LIMIT", -+ IDENTITY_LIMIT = "IDENTITY_LIMIT", -+ ITEM_GET_LIMIT = "ITEM_GET_LIMIT", -+ RATE_LIMIT = "RATE_LIMIT", -+ TRANSACTIONS_LIMIT = "TRANSACTIONS_LIMIT", -+ -+ // RECAPTCHA_ERROR -+ RECAPTCHA_REQUIRED = "RECAPTCHA_REQUIRED", -+ RECAPTCHA_BAD = "RECAPTCHA_BAD", -+ -+ // OAUTH_ERROR -+ INCORRECT_OAUTH_NONCE = "INCORRECT_OAUTH_NONCE", -+ OAUTH_STATE_ID_ALREADY_PROCESSED = "OAUTH_STATE_ID_ALREADY_PROCESSED", -+} -+ -+export enum LinkErrorType { -+ BANK_TRANSFER = 'BANK_TRANSFER_ERROR', -+ INVALID_REQUEST = 'INVALID_REQUEST', -+ INVALID_RESULT = 'INVALID_RESULT', -+ INVALID_INPUT = 'INVALID_INPUT', -+ INSTITUTION_ERROR = 'INSTITUTION_ERROR', -+ RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED', -+ API_ERROR = 'API_ERROR', -+ ITEM_ERROR = 'ITEM_ERROR', -+ AUTH_ERROR = 'AUTH_ERROR', -+ ASSET_REPORT_ERROR = 'ASSET_REPORT_ERROR', -+ SANDBOX_ERROR = 'SANDBOX_ERROR', -+ RECAPTCHA_ERROR = 'RECAPTCHA_ERROR', -+ OAUTH_ERROR = 'OAUTH_ERROR', -+} -+ -+export type LinkEventListener = (linkEvent: LinkEvent) => void -+ -+export interface LinkEvent { -+ eventName: LinkEventName; -+ metadata: LinkEventMetadata; -+} -+ -+export interface LinkEventMetadata { -+ accountNumberMask?: string; -+ linkSessionId: string; -+ mfaType?: string; -+ requestId?: string; -+ viewName: LinkEventViewName; -+ errorCode?: string; -+ errorMessage?: string; -+ errorType?: string; -+ exitStatus?: string; -+ institutionId?: string; -+ institutionName?: string; -+ institutionSearchQuery?: string; -+ isUpdateMode?: string; -+ matchReason?: string; -+ // see possible values for selection at https://plaid.com/docs/link/web/#link-web-onevent-selection -+ selection?: null | string; -+ timestamp: string; -+} -+ -+export enum LinkEventName { -+ BANK_INCOME_INSIGHTS_COMPLETED = "BANK_INCOME_INSIGHTS_COMPLETED", -+ CLOSE_OAUTH = 'CLOSE_OAUTH', -+ ERROR = 'ERROR', -+ EXIT = 'EXIT', -+ FAIL_OAUTH = 'FAIL_OAUTH', -+ HANDOFF = 'HANDOFF', -+ IDENTITY_VERIFICATION_START_STEP = 'IDENTITY_VERIFICATION_START_STEP', -+ IDENTITY_VERIFICATION_PASS_STEP = 'IDENTITY_VERIFICATION_PASS_STEP', -+ IDENTITY_VERIFICATION_FAIL_STEP = 'IDENTITY_VERIFICATION_FAIL_STEP', -+ IDENTITY_VERIFICATION_PENDING_REVIEW_STEP = 'IDENTITY_VERIFICATION_PENDING_REVIEW_STEP', -+ IDENTITY_VERIFICATION_PENDING_REVIEW_SESSION = 'IDENTITY_VERIFICATION_PENDING_REVIEW_SESSION', -+ IDENTITY_VERIFICATION_CREATE_SESSION = 'IDENTITY_VERIFICATION_CREATE_SESSION', -+ IDENTITY_VERIFICATION_RESUME_SESSION = 'IDENTITY_VERIFICATION_RESUME_SESSION', -+ IDENTITY_VERIFICATION_PASS_SESSION = 'IDENTITY_VERIFICATION_PASS_SESSION', -+ IDENTITY_VERIFICATION_FAIL_SESSION = 'IDENTITY_VERIFICATION_FAIL_SESSION', -+ IDENTITY_VERIFICATION_OPEN_UI = 'IDENTITY_VERIFICATION_OPEN_UI', -+ IDENTITY_VERIFICATION_RESUME_UI = 'IDENTITY_VERIFICATION_RESUME_UI', -+ IDENTITY_VERIFICATION_CLOSE_UI = 'IDENTITY_VERIFICATION_CLOSE_UI', -+ MATCHED_CONSENT = 'MATCHED_CONSENT', -+ MATCHED_SELECT_INSTITUTION = 'MATCHED_SELECT_INSTITUTION', -+ MATCHED_SELECT_VERIFY_METHOD = 'MATCHED_SELECT_VERIFY_METHOD', -+ OPEN = 'OPEN', -+ OPEN_MY_PLAID = 'OPEN_MY_PLAID', -+ OPEN_OAUTH = 'OPEN_OAUTH', -+ SEARCH_INSTITUTION = 'SEARCH_INSTITUTION', -+ SELECT_DEGRADED_INSTITUTION = 'SELECT_DEGRADED_INSTITUTION', -+ SELECT_DOWN_INSTITUTION = 'SELECT_DOWN_INSTITUTION', -+ SELECT_FILTERED_INSTITUTION = 'SELECT_FILTERED_INSTITUTION', -+ SELECT_INSTITUTION = 'SELECT_INSTITUTION', -+ SELECT_BRAND = 'SELECT_BRAND', -+ SELECT_AUTH_TYPE = 'SELECT_AUTH_TYPE', -+ SUBMIT_ACCOUNT_NUMBER = 'SUBMIT_ACCOUNT_NUMBER', -+ SUBMIT_DOCUMENTS = 'SUBMIT_DOCUMENTS', -+ SUBMIT_DOCUMENTS_SUCCESS = 'SUBMIT_DOCUMENTS_SUCCESS', -+ SUBMIT_DOCUMENTS_ERROR = 'SUBMIT_DOCUMENTS_ERROR', -+ SUBMIT_ROUTING_NUMBER = 'SUBMIT_ROUTING_NUMBER', -+ VIEW_DATA_TYPES = 'VIEW_DATA_TYPES', -+ SUBMIT_PHONE = 'SUBMIT_PHONE', -+ SKIP_SUBMIT_PHONE = 'SKIP_SUBMIT_PHONE', -+ VERIFY_PHONE = 'VERIFY_PHONE', -+ SUBMIT_CREDENTIALS = 'SUBMIT_CREDENTIALS', -+ SUBMIT_MFA = 'SUBMIT_MFA', -+ TRANSITION_VIEW = 'TRANSITION_VIEW', -+ CONNECT_NEW_INSTITUTION = 'CONNECT_NEW_INSTITUTION', -+} -+ -+export enum LinkEventViewName { -+ ACCEPT_TOS = 'ACCEPT_TOS', -+ CONNECTED = 'CONNECTED', -+ CONSENT = 'CONSENT', -+ CREDENTIAL = 'CREDENTIAL', -+ DATA_TRANSPARENCY = 'DATA_TRANSPARENCY', -+ DATA_TRANSPARENCY_CONSENT = 'DATA_TRANSPARENCY_CONSENT', -+ DOCUMENTARY_VERIFICATION = 'DOCUMENTARY_VERIFICATION', -+ ERROR = 'ERROR', -+ EXIT = 'EXIT', -+ KYC_CHECK = 'KYC_CHECK', -+ SELFIE_CHECK = 'SELFIE_CHECK', -+ LOADING = 'LOADING', -+ MATCHED_CONSENT = 'MATCHED_CONSENT', -+ MATCHED_CREDENTIAL = 'MATCHED_CREDENTIAL', -+ MATCHED_MFA = 'MATCHED_MFA', -+ MFA = 'MFA', -+ NUMBERS = 'NUMBERS', -+ NUMBERS_SELECT_INSTITUTION = 'NUMBERS_SELECT_INSTITUTION', -+ OAUTH = 'OAUTH', -+ RECAPTCHA = 'RECAPTCHA', -+ RISK_CHECK = 'RISK_CHECK', -+ SCREENING = 'SCREENING', -+ SELECT_ACCOUNT = 'SELECT_ACCOUNT', -+ SELECT_AUTH_TYPE = 'SELECT_AUTH_TYPE', -+ SUBMIT_PHONE = 'SUBMIT_PHONE', -+ VERIFY_PHONE = 'VERIFY_PHONE', -+ SELECT_SAVED_INSTITUTION = 'SELECT_SAVED_INSTITUTION', -+ SELECT_SAVED_ACCOUNT = 'SELECT_SAVED_ACCOUNT', -+ SELECT_BRAND = 'SELECT_BRAND', -+ SELECT_INSTITUTION = 'SELECT_INSTITUTION', -+ SUBMIT_DOCUMENTS = 'SUBMIT_DOCUMENTS', -+ SUBMIT_DOCUMENTS_SUCCESS = 'SUBMIT_DOCUMENTS_SUCCESS', -+ SUBMIT_DOCUMENTS_ERROR = 'SUBMIT_DOCUMENTS_ERROR', -+ UPLOAD_DOCUMENTS = 'UPLOAD_DOCUMENTS', -+ VERIFY_SMS = 'VERIFY_SMS', -+} -+ -+/// Methods to present Link on iOS. -+/// FULL_SCREEN is the converts to UIModalPresentationOverFullScreen on the native side. -+/// MODAL will use the default presentation style for iOS which is UIModalPresentationAutomatic. -+export enum LinkIOSPresentationStyle { -+ FULL_SCREEN = 'FULL_SCREEN', -+ MODAL = 'MODAL' -+} -+ -+export type LinkSuccessListener = (LinkSuccess: LinkSuccess) => void -+ -+export type LinkExitListener = (LinkExit: LinkExit) => void -+ -+export type LinkOnEventListener = (LinkEvent: LinkEvent) => void -+ -+export interface PlaidLinkProps { -+ tokenConfig: LinkTokenConfiguration -+ onSuccess: LinkSuccessListener -+ onExit?: LinkExitListener -+ iOSPresentationStyle?: LinkIOSPresentationStyle -+ logLevel?: LinkLogLevel -+ onPress?(): any -+} -+ -+export type PlaidLinkComponentProps = (PlaidLinkProps & { -+ children: React.ReactNode -+}); -diff --git a/node_modules/react-native-plaid-link-sdk/src/__tests__/Types.tests.ts b/node_modules/react-native-plaid-link-sdk/src/__tests__/Types.tests.ts -new file mode 100644 -index 0000000..2dd7a54 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/src/__tests__/Types.tests.ts -@@ -0,0 +1,15 @@ -+const Types = require('./../Types'); -+ -+test('test token configuration', () => { -+ const linkTokenConfiguration = { -+ token: "test-token", -+ noLoadingState: false, -+ logLevel: Types.LinkLogLevel.DEBUG, -+ extras: null, -+ }; -+ -+ expect(linkTokenConfiguration.noLoadingState).toBe(false); -+ expect(linkTokenConfiguration.token).toBe("test-token"); -+ expect(linkTokenConfiguration.logLevel).toBe(Types.LinkLogLevel.DEBUG); -+ expect(linkTokenConfiguration.extras).toBe(null); -+}); -\ No newline at end of file -diff --git a/node_modules/react-native-plaid-link-sdk/src/fabric/NativePlaidLinkModuleAndroid.ts b/node_modules/react-native-plaid-link-sdk/src/fabric/NativePlaidLinkModuleAndroid.ts -new file mode 100644 -index 0000000..d1e4062 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/src/fabric/NativePlaidLinkModuleAndroid.ts -@@ -0,0 +1,20 @@ -+// we use Object type because methods on the native side use NSDictionary and ReadableMap -+// and we want to stay compatible with those -+import {TurboModuleRegistry, TurboModule} from 'react-native'; -+import { Int32, UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'; -+ -+export interface Spec extends TurboModule { -+ startLinkActivityForResult( -+ token: string, -+ noLoadingState: boolean, -+ logLevel: string, -+ onSuccessCallback: (result: UnsafeObject) => void, -+ onExitCallback: (result: UnsafeObject) => void -+ ): void; -+ -+ // those two are here for event emitter methods -+ addListener(eventName: string): void; -+ removeListeners(count: Int32): void; -+} -+ -+export default TurboModuleRegistry.get('PlaidAndroid'); -diff --git a/node_modules/react-native-plaid-link-sdk/src/fabric/NativePlaidLinkModuleiOS.ts b/node_modules/react-native-plaid-link-sdk/src/fabric/NativePlaidLinkModuleiOS.ts -new file mode 100644 -index 0000000..d1b3565 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/src/fabric/NativePlaidLinkModuleiOS.ts -@@ -0,0 +1,19 @@ -+// we use Object type because methods on the native side use NSDictionary and ReadableMap -+// and we want to stay compatible with those -+import {TurboModuleRegistry, TurboModule} from 'react-native'; -+import { Int32, UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'; -+ -+export interface Spec extends TurboModule { -+ create(token: string, noLoadingState: boolean): void; -+ open( -+ fullScreen: boolean, -+ onSuccess: (success: UnsafeObject) => void, -+ onExit: (error: UnsafeObject, result: UnsafeObject) => void, -+ ): void; -+ dismiss(): void; -+ // those two are here for event emitter methods -+ addListener(eventName: string): void; -+ removeListeners(count: Int32): void; -+} -+ -+export default TurboModuleRegistry.get('RNLinksdk'); -diff --git a/node_modules/react-native-plaid-link-sdk/src/index.ts b/node_modules/react-native-plaid-link-sdk/src/index.ts -new file mode 100644 -index 0000000..23ef946 ---- /dev/null -+++ b/node_modules/react-native-plaid-link-sdk/src/index.ts -@@ -0,0 +1,21 @@ -+import { -+ openLink, -+ dismissLink, -+ usePlaidEmitter, -+ PlaidLink, -+} from './PlaidLink'; -+ -+export * from './Types'; -+ -+export default PlaidLink; -+ -+export { -+ PlaidLink, -+ openLink, -+ dismissLink, -+ usePlaidEmitter, -+}; -+ -+// Components -+ -+export { EmbeddedLinkView } from './EmbeddedLink/EmbeddedLinkView'; -\ No newline at end of file diff --git a/patches/react-native-reanimated+3.7.2.patch b/patches/react-native-reanimated+3.8.1+001+initial.patch similarity index 100% rename from patches/react-native-reanimated+3.7.2.patch rename to patches/react-native-reanimated+3.8.1+001+initial.patch diff --git a/patches/react-native-reanimated+3.7.2+001+fix-boost-dependency.patch b/patches/react-native-reanimated+3.8.1+002+fix-boost-dependency.patch similarity index 100% rename from patches/react-native-reanimated+3.7.2+001+fix-boost-dependency.patch rename to patches/react-native-reanimated+3.8.1+002+fix-boost-dependency.patch diff --git a/patches/react-native-reanimated+3.7.2+002+copy-state.patch b/patches/react-native-reanimated+3.8.1+003+copy-state.patch similarity index 100% rename from patches/react-native-reanimated+3.7.2+002+copy-state.patch rename to patches/react-native-reanimated+3.8.1+003+copy-state.patch diff --git a/patches/react-native-reanimated+3.8.1+003+fix-strict-mode.patch b/patches/react-native-reanimated+3.8.1+004+fix-strict-mode.patch similarity index 100% rename from patches/react-native-reanimated+3.8.1+003+fix-strict-mode.patch rename to patches/react-native-reanimated+3.8.1+004+fix-strict-mode.patch diff --git a/patches/react-native-web+0.19.12+003+image-header-support.patch b/patches/react-native-web+0.19.12+003+image-header-support.patch index 6652f0345cc4..d0a490a4ed70 100644 --- a/patches/react-native-web+0.19.12+003+image-header-support.patch +++ b/patches/react-native-web+0.19.12+003+image-header-support.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-native-web/dist/exports/Image/index.js b/node_modules/react-native-web/dist/exports/Image/index.js -index 9649d27..3281cc8 100644 +index 9649d27..66ef95c 100644 --- a/node_modules/react-native-web/dist/exports/Image/index.js +++ b/node_modules/react-native-web/dist/exports/Image/index.js @@ -135,7 +135,22 @@ function resolveAssetUri(source) { @@ -47,7 +47,7 @@ index 9649d27..3281cc8 100644 }); } function abortPendingRequest() { -@@ -279,10 +288,78 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { +@@ -279,10 +288,79 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { suppressHydrationWarning: true }), hiddenImage, createTintColorSVG(tintColor, filterRef.current)); }); @@ -64,24 +64,20 @@ index 9649d27..3281cc8 100644 + var _React$useState3 = React.useState(''), + blobUri = _React$useState3[0], + setBlobUri = _React$useState3[1]; -+ var request = React.useRef({ -+ cancel: () => {}, -+ source: { -+ uri: '', -+ headers: {} -+ }, -+ promise: Promise.resolve('') -+ }); ++ var request = React.useRef(null); + var onError = props.onError, + onLoadStart = props.onLoadStart, + onLoadEnd = props.onLoadEnd; + React.useEffect(() => { -+ if (!hasSourceDiff(nextSource, request.current.source)) { ++ if (request.current !== null && !hasSourceDiff(nextSource, request.current.source)) { + return; + } + + // When source changes we want to clean up any old/running requests -+ request.current.cancel(); ++ if (request.current !== null) { ++ request.current.cancel(); ++ } ++ + if (onLoadStart) { + onLoadStart(); + } @@ -96,7 +92,12 @@ index 9649d27..3281cc8 100644 + }, [nextSource, onLoadStart, onError, onLoadEnd]); + + // Cancel any request on unmount -+ React.useEffect(() => request.current.cancel, []); ++ React.useEffect(() => () => { ++ if (request.current !== null) { ++ request.current.cancel(); ++ request.current = null; ++ } ++ }, []); + var propsToPass = _objectSpread(_objectSpread({}, props), {}, { + // `onLoadStart` is called from the current component + // We skip passing it down to prevent BaseImage raising it a 2nd time diff --git a/scripts/applyPatches.sh b/scripts/applyPatches.sh new file mode 100755 index 000000000000..4ce023755258 --- /dev/null +++ b/scripts/applyPatches.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# This script is a simple wrapper around patch-package that fails if any errors or warnings are detected. +# This is useful because patch-package does not fail on errors or warnings by default, +# which means that broken patches are easy to miss, and leads to developer frustration and wasted time. + +SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}") +source "$SCRIPTS_DIR/shellUtils.sh" + +# Wrapper to run patch-package. +function patchPackage { + OS="$(uname)" + if [[ "$OS" == "Darwin" || "$OS" == "Linux" ]]; then + npx patch-package --error-on-fail --color=always + else + error "Unsupported OS: $OS" + exit 1 + fi +} + +# Run patch-package and capture its output and exit code, while still displaying the original output to the terminal +TEMP_OUTPUT="$(mktemp)" +patchPackage 2>&1 | tee "$TEMP_OUTPUT" +EXIT_CODE=${PIPESTATUS[0]} +OUTPUT="$(cat "$TEMP_OUTPUT")" +rm -f "$TEMP_OUTPUT" + +# Check if the output contains a warning message +echo "$OUTPUT" | grep -q "Warning:" +WARNING_FOUND=$? + +printf "\n" + +# Determine the final exit code +if [ "$EXIT_CODE" -eq 0 ]; then + if [ $WARNING_FOUND -eq 0 ]; then + # patch-package succeeded but warning was found + error "It looks like you upgraded a dependency without upgrading the patch. Please review the patch, determine if it's still needed, and port it to the new version of the dependency." + exit 1 + else + # patch-package succeeded and no warning was found + success "patch-package succeeded without errors or warnings" + exit 0 + fi +else + # patch-package failed + error "patch-package failed to apply a patch" + exit "$EXIT_CODE" +fi diff --git a/scripts/postInstall.sh b/scripts/postInstall.sh index 339fdf25cb10..782c8ef5822c 100755 --- a/scripts/postInstall.sh +++ b/scripts/postInstall.sh @@ -1,11 +1,14 @@ #!/bin/bash +# Exit immediately if any command exits with a non-zero status +set -e + # Go to project root ROOT_DIR=$(dirname "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)") cd "$ROOT_DIR" || exit 1 -# Run patch-package -npx patch-package +# Apply packages using patch-package +scripts/applyPatches.sh # Install node_modules in subpackages, unless we're in a CI/CD environment, # where the node_modules for subpackages are cached separately. diff --git a/scripts/symbolicate-profile.ts b/scripts/symbolicate-profile.ts index a100c05029dd..a58c2894edb6 100755 --- a/scripts/symbolicate-profile.ts +++ b/scripts/symbolicate-profile.ts @@ -32,7 +32,7 @@ if (Object.keys(argsMap).length === 0 || argsMap.help !== undefined) { Logger.log('Options:'); Logger.log(' --profile= The .cpuprofile file to symbolicate'); Logger.log(' --platform= The platform for which the source map was uploaded'); - Logger.log(' --gh-token Token to use for requests send to the GitHub API. By default tries to pick up from the environment variable GITHUB_TOKEN'); + Logger.log(' --ghToken Token to use for requests send to the GitHub API. By default tries to pick up from the environment variable GITHUB_TOKEN'); Logger.log(' --help Display this help message'); process.exit(0); } @@ -53,7 +53,7 @@ if (argsMap.platform === undefined) { const githubToken = argsMap.ghToken ?? process.env.GITHUB_TOKEN; if (githubToken === undefined) { - Logger.error('No GitHub token provided. Either set a GITHUB_TOKEN environment variable or pass it using --gh-token'); + Logger.error('No GitHub token provided. Either set a GITHUB_TOKEN environment variable or pass it using --ghToken'); process.exit(1); } diff --git a/src/App.tsx b/src/App.tsx index 21025d34a661..98b5d4afeb1d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,6 +20,7 @@ import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; import SafeArea from './components/SafeArea'; import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider'; +import {SearchContextProvider} from './components/Search/SearchContext'; import ThemeIllustrationsProvider from './components/ThemeIllustrationsProvider'; import ThemeProvider from './components/ThemeProvider'; import ThemeStylesProvider from './components/ThemeStylesProvider'; @@ -91,6 +92,7 @@ function App({url}: AppProps) { VolumeContextProvider, VideoPopoverMenuContextProvider, KeyboardProvider, + SearchContextProvider, ]} > diff --git a/src/CONST.ts b/src/CONST.ts index 2e2608d773ae..e03f42b282e4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -74,6 +74,7 @@ const onboardingChoices = { type OnboardingPurposeType = ValueOf; const CONST = { + RECENT_WAYPOINTS_NUMBER: 20, DEFAULT_DB_NAME: 'OnyxDB', DEFAULT_TABLE_NAME: 'keyvaluepairs', DEFAULT_ONYX_DUMP_FILE_NAME: 'onyx-state.txt', @@ -181,6 +182,8 @@ const CONST = { MERCHANT_NAME_MAX_LENGTH: 255, + MASKED_PAN_PREFIX: 'XXXXXXXXXXXX', + REQUEST_PREVIEW: { MAX_LENGTH: 83, }, @@ -334,6 +337,8 @@ const CONST = { VERIFICATION_MAX_ATTEMPTS: 7, STATE: { VERIFYING: 'VERIFYING', + VALIDATING: 'VALIDATING', + SETUP: 'SETUP', PENDING: 'PENDING', OPEN: 'OPEN', }, @@ -360,11 +365,9 @@ const CONST = { DEFAULT_ROOMS: 'defaultRooms', VIOLATIONS: 'violations', DUPE_DETECTION: 'dupeDetection', - REPORT_FIELDS: 'reportFields', P2P_DISTANCE_REQUESTS: 'p2pDistanceRequests', WORKFLOWS_DELAYED_SUBMISSION: 'workflowsDelayedSubmission', SPOTNANA_TRAVEL: 'spotnanaTravel', - NETSUITE_ON_NEW_EXPENSIFY: 'netsuiteOnNewExpensify', REPORT_FIELDS_FEATURE: 'reportFieldsFeature', WORKSPACE_FEEDS: 'workspaceFeeds', NETSUITE_USA_TAX: 'netsuiteUsaTax', @@ -608,6 +611,7 @@ const CONST = { 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', + SAGE_INTACCT_INSTRUCTIONS: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct', 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`, @@ -661,11 +665,13 @@ const CONST = { MEMBER: 'member', }, MAX_COUNT_BEFORE_FOCUS_UPDATE: 30, + MIN_INITIAL_REPORT_ACTION_COUNT: 15, SPLIT_REPORTID: '-2', ACTIONS: { LIMIT: 50, // OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMessageOfOldDotReportAction in ReportActionsUtils.ts TYPE: { + ACTIONABLE_ADD_PAYMENT_CARD: 'ACTIONABLEADDPAYMENTCARD', ACTIONABLE_JOIN_REQUEST: 'ACTIONABLEJOINREQUEST', ACTIONABLE_MENTION_WHISPER: 'ACTIONABLEMENTIONWHISPER', ACTIONABLE_REPORT_MENTION_WHISPER: 'ACTIONABLEREPORTMENTIONWHISPER', @@ -839,6 +845,10 @@ const CONST = { TASK: 'task', INVOICE: 'invoice', }, + UNSUPPORTED_TYPE: { + PAYCHECK: 'paycheck', + BILL: 'bill', + }, CHAT_TYPE: chatTypes, WORKSPACE_CHAT_ROOMS: { ANNOUNCE: '#announce', @@ -891,9 +901,17 @@ const CONST = { INDIVIDUAL: 'individual', BUSINESS: 'policy', }, + EXPORT_OPTIONS: { + EXPORT_TO_INTEGRATION: 'exportToIntegration', + MARK_AS_EXPORTED: 'markAsExported', + }, }, NEXT_STEP: { - FINISHED: 'Finished!', + ICONS: { + HOURGLASS: 'hourglass', + CHECKMARK: 'checkmark', + STOPWATCH: 'stopwatch', + }, }, COMPOSER: { MAX_LINES: 16, @@ -1121,8 +1139,6 @@ const CONST = { // around each header. EMOJI_NUM_PER_ROW: 8, - EMOJI_FREQUENT_ROW_COUNT: 3, - EMOJI_DEFAULT_SKIN_TONE: -1, // Amount of emojis to render ahead at the end of the update cycle @@ -1201,6 +1217,8 @@ const CONST = { NOTE: 'n', }, + IMAGE_HIGH_RESOLUTION_THRESHOLD: 7000, + IMAGE_OBJECT_POSITION: { TOP: 'top', INITIAL: 'initial', @@ -1243,7 +1261,7 @@ const CONST = { MAX_AMOUNT_OF_SUGGESTIONS: 20, MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER: 5, HERE_TEXT: '@here', - SUGGESTION_BOX_MAX_SAFE_DISTANCE: 38, + SUGGESTION_BOX_MAX_SAFE_DISTANCE: 10, BIG_SCREEN_SUGGESTION_WIDTH: 300, }, COMPOSER_MAX_HEIGHT: 125, @@ -1344,18 +1362,76 @@ const CONST = { }, }, + SAGE_INTACCT_MAPPING_VALUE: { + NONE: 'NONE', + DEFAULT: 'DEFAULT', + TAG: 'TAG', + REPORT_FIELD: 'REPORT_FIELD', + }, + + SAGE_INTACCT_CONFIG: { + MAPPINGS: { + DEPARTMENTS: 'departments', + CLASSES: 'classes', + LOCATIONS: 'locations', + CUSTOMERS: 'customers', + PROJECTS: 'projects', + }, + SYNC_ITEMS: 'syncItems', + TAX: 'tax', + EXPORT: 'export', + EXPORT_DATE: 'exportDate', + NON_REIMBURSABLE_CREDIT_CARD_VENDOR: 'nonReimbursableCreditCardChargeDefaultVendor', + NON_REIMBURSABLE_VENDOR: 'nonReimbursableVendor', + REIMBURSABLE_VENDOR: 'reimbursableExpenseReportDefaultVendor', + NON_REIMBURSABLE_ACCOUNT: 'nonReimbursableAccount', + NON_REIMBURSABLE: 'nonReimbursable', + EXPORTER: 'exporter', + REIMBURSABLE: 'reimbursable', + AUTO_SYNC: 'autoSync', + AUTO_SYNC_ENABLED: 'enabled', + IMPORT_EMPLOYEES: 'importEmployees', + APPROVAL_MODE: 'approvalMode', + SYNC: 'sync', + SYNC_REIMBURSED_REPORTS: 'syncReimbursedReports', + REIMBURSEMENT_ACCOUNT_ID: 'reimbursementAccountID', + ENTITY: 'entity', + }, + + SAGE_INTACCT: { + APPROVAL_MODE: { + APPROVAL_MANUAL: 'APPROVAL_MANUAL', + }, + }, + QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE: { VENDOR_BILL: 'bill', CHECK: 'check', JOURNAL_ENTRY: 'journal_entry', }, + SAGE_INTACCT_REIMBURSABLE_EXPENSE_TYPE: { + EXPENSE_REPORT: 'EXPENSE_REPORT', + VENDOR_BILL: 'VENDOR_BILL', + }, + + SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE: { + CREDIT_CARD_CHARGE: 'CREDIT_CARD_CHARGE', + VENDOR_BILL: 'VENDOR_BILL', + }, + XERO_EXPORT_DATE: { LAST_EXPENSE: 'LAST_EXPENSE', REPORT_EXPORTED: 'REPORT_EXPORTED', REPORT_SUBMITTED: 'REPORT_SUBMITTED', }, + SAGE_INTACCT_EXPORT_DATE: { + LAST_EXPENSE: 'LAST_EXPENSE', + EXPORTED: 'EXPORTED', + SUBMITTED: 'SUBMITTED', + }, + NETSUITE_CONFIG: { SUBSIDIARY: 'subsidiary', EXPORTER: 'exporter', @@ -1388,7 +1464,16 @@ const CONST = { 3: 'createAccessToken', 4: 'enterCredentials', }, - IMPORT_CUSTOM_FIELDS: ['customSegments', 'customLists'], + IMPORT_CUSTOM_FIELDS: { + CUSTOM_SEGMENTS: 'customSegments', + CUSTOM_LISTS: 'customLists', + }, + CUSTOM_SEGMENT_FIELDS: ['segmentName', 'internalID', 'scriptID', 'mapping'], + CUSTOM_LIST_FIELDS: ['listName', 'internalID', 'transactionFieldID', 'mapping'], + CUSTOM_FORM_ID_TYPE: { + REIMBURSABLE: 'reimbursable', + NON_REIMBURSABLE: 'nonReimbursable', + }, SYNC_OPTIONS: { SYNC_REIMBURSED_REPORTS: 'syncReimbursedReports', SYNC_PEOPLE: 'syncPeople', @@ -1403,6 +1488,40 @@ const CONST = { JOBS: 'jobs', }, }, + NETSUITE_CUSTOM_LIST_LIMIT: 8, + NETSUITE_ADD_CUSTOM_LIST_STEP_NAMES: ['1', '2,', '3', '4'], + NETSUITE_ADD_CUSTOM_SEGMENT_STEP_NAMES: ['1', '2,', '3', '4', '5', '6,'], + }, + + NETSUITE_CUSTOM_FIELD_SUBSTEP_INDEXES: { + CUSTOM_LISTS: { + CUSTOM_LIST_PICKER: 0, + TRANSACTION_FIELD_ID: 1, + MAPPING: 2, + CONFIRM: 3, + }, + CUSTOM_SEGMENTS: { + SEGMENT_TYPE: 0, + SEGMENT_NAME: 1, + INTERNAL_ID: 2, + SCRIPT_ID: 3, + MAPPING: 4, + CONFIRM: 5, + }, + }, + + NETSUITE_CUSTOM_RECORD_TYPES: { + CUSTOM_SEGMENT: 'customSegment', + CUSTOM_RECORD: 'customRecord', + }, + + NETSUITE_FORM_STEPS_HEADER_HEIGHT: 40, + + NETSUITE_IMPORT: { + HELP_LINKS: { + CUSTOM_SEGMENTS: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/NetSuite#custom-segments', + CUSTOM_LISTS: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/NetSuite#custom-lists', + }, }, NETSUITE_EXPORT_DATE: { @@ -1457,6 +1576,17 @@ const CONST = { JOURNALS_APPROVED: 'JOURNALS_APPROVED', }, + NETSUITE_ACCOUNT_TYPE: { + ACCOUNTS_PAYABLE: '_accountsPayable', + ACCOUNTS_RECEIVABLE: '_accountsReceivable', + OTHER_CURRENT_LIABILITY: '_otherCurrentLiability', + CREDIT_CARD: '_creditCard', + BANK: '_bank', + OTHER_CURRENT_ASSET: '_otherCurrentAsset', + LONG_TERM_LIABILITY: '_longTermLiability', + EXPENSE: '_expense', + }, + NETSUITE_APPROVAL_ACCOUNT_DEFAULT: 'APPROVAL_ACCOUNT_DEFAULT', /** @@ -1971,8 +2101,12 @@ const CONST = { NAME_USER_FRIENDLY: { netsuite: 'NetSuite', quickbooksOnline: 'Quickbooks Online', + quickbooksDesktop: 'Quickbooks Desktop', xero: 'Xero', intacct: 'Sage Intacct', + financialForce: 'FinancialForce', + billCom: 'Bill.com', + zenefits: 'Zenefits', }, SYNC_STAGE_NAME: { STARTING_IMPORT_QBO: 'startingImportQBO', @@ -2032,6 +2166,7 @@ const CONST = { ACCESS_VARIANTS: { PAID: 'paid', ADMIN: 'admin', + CONTROL: 'control', }, DEFAULT_MAX_EXPENSE_AGE: 90, DEFAULT_MAX_EXPENSE_AMOUNT: 200000, @@ -2114,6 +2249,14 @@ const CONST = { CARD_NAME: 'CardName', CONFIRMATION: 'Confirmation', }, + CARD_TYPE: { + PHYSICAL: 'physical', + VIRTUAL: 'virtual', + }, + FREQUENCY_SETTING: { + DAILY: 'daily', + MONTHLY: 'monthly', + }, }, AVATAR_ROW_SIZE: { DEFAULT: 4, @@ -2215,6 +2358,8 @@ const CONST = { POLICY_ID_FROM_PATH: /\/w\/([a-zA-Z0-9]+)(\/|$)/, SHORT_MENTION: new RegExp(`@[\\w\\-\\+\\'#@]+(?:\\.[\\w\\-\\'\\+]+)*(?![^\`]*\`)`, 'gim'), + + REPORT_ID_FROM_PATH: /\/r\/(\d+)/, }, PRONOUNS: { @@ -2235,6 +2380,7 @@ const CONST = { WORKSPACE_WORKFLOWS: 'WorkspaceWorkflows', WORKSPACE_BANK_ACCOUNT: 'WorkspaceBankAccount', WORKSPACE_SETTINGS: 'WorkspaceSettings', + WORKSPACE_FEATURES: 'WorkspaceFeatures', }, get EXPENSIFY_EMAILS() { return [ @@ -2274,7 +2420,7 @@ const CONST = { this.ACCOUNT_ID.REWARDS, this.ACCOUNT_ID.STUDENT_AMBASSADOR, this.ACCOUNT_ID.SVFG, - ]; + ].filter((id) => id !== -1); }, // Emails that profile view is prohibited @@ -2333,8 +2479,10 @@ const CONST = { SETTINGS: 'settings', LEAVE_ROOM: 'leaveRoom', PRIVATE_NOTES: 'privateNotes', + EXPORT: 'export', DELETE: 'delete', MARK_AS_INCOMPLETE: 'markAsIncomplete', + CANCEL_PAYMENT: 'cancelPayment', UNAPPROVE: 'unapprove', }, EDIT_REQUEST_FIELD: { @@ -3722,6 +3870,9 @@ const CONST = { ENABLED: 'ENABLED', DISABLED: 'DISABLED', }, + STRIPE_GBP_AUTH_STATUSES: { + SUCCEEDED: 'succeeded', + }, TAB: { NEW_CHAT_TAB_ID: 'NewChatTab', NEW_CHAT: 'chat', @@ -3780,6 +3931,7 @@ const CONST = { }, EVENTS: { SCROLLING: 'scrolling', + ON_RETURN_TO_OLD_DOT: 'onReturnToOldDot', }, CHAT_HEADER_LOADER_HEIGHT: 36, @@ -3890,6 +4042,15 @@ const CONST = { WARNING: 'warning', }, + /** + * Constants with different types for the modifiedAmount violation + */ + MODIFIED_AMOUNT_VIOLATION_DATA: { + DISTANCE: 'distance', + CARD: 'card', + SMARTSCAN: 'smartscan', + }, + /** * Constants for types of violation names. * Defined here because they need to be referenced by the type system to generate the @@ -3933,6 +4094,14 @@ const CONST = { }, REVIEW_DUPLICATES_ORDER: ['merchant', 'category', 'tag', 'description', 'taxCode', 'billable', 'reimbursable'], + REPORT_VIOLATIONS: { + FIELD_REQUIRED: 'fieldRequired', + }, + + REPORT_VIOLATIONS_EXCLUDED_FIELDS: { + TEXT_TITLE: 'text_title', + }, + /** Context menu types */ CONTEXT_MENU_TYPES: { LINK: 'LINK', @@ -5043,9 +5212,16 @@ const CONST = { REPORT: 'report', }, ACTION_TYPES: { + VIEW: 'view', + REVIEW: 'review', DONE: 'done', PAID: 'paid', - VIEW: 'view', + }, + BULK_ACTION_TYPES: { + EXPORT: 'export', + HOLD: 'hold', + UNHOLD: 'unhold', + DELETE: 'delete', }, TRANSACTION_TYPE: { CASH: 'cash', @@ -5076,13 +5252,38 @@ const CONST = { ACTION: 'action', TAX_AMOUNT: 'taxAmount', }, - BULK_ACTION_TYPES: { - DELETE: 'delete', - HOLD: 'hold', - UNHOLD: 'unhold', - SUBMIT: 'submit', - APPROVE: 'approve', - PAY: 'pay', + SYNTAX_OPERATORS: { + AND: 'and', + OR: 'or', + EQUAL_TO: 'eq', + NOT_EQUAL_TO: 'neq', + GREATER_THAN: 'gt', + GREATER_THAN_OR_EQUAL_TO: 'gte', + LOWER_THAN: 'lt', + LOWER_THAN_OR_EQUAL_TO: 'lte', + }, + SYNTAX_ROOT_KEYS: { + TYPE: 'type', + STATUS: 'status', + SORT_BY: 'sortBy', + SORT_ORDER: 'sortOrder', + OFFSET: 'offset', + }, + SYNTAX_FILTER_KEYS: { + DATE: 'date', + AMOUNT: 'amount', + EXPENSE_TYPE: 'expenseType', + CURRENCY: 'currency', + MERCHANT: 'merchant', + DESCRIPTION: 'description', + FROM: 'from', + TO: 'to', + CATEGORY: 'category', + TAG: 'tag', + TAX_RATE: 'taxRate', + CARD_ID: 'cardID', + REPORT_ID: 'reportID', + KEYWORD: 'keyword', }, }, @@ -5098,8 +5299,10 @@ const CONST = { PAYMENT_CARD_CURRENCY: { USD: 'USD', AUD: 'AUD', + GBP: 'GBP', NZD: 'NZD', }, + GBP_AUTHENTICATION_COMPLETE: '3DS-authentication-complete', SUBSCRIPTION_PRICE_FACTOR: 2, FEEDBACK_SURVEY_OPTIONS: { @@ -5121,6 +5324,7 @@ const CONST = { }, }, + MAX_LENGTH_256: 256, WORKSPACE_CARDS_LIST_LABEL_TYPE: { CURRENT_BALANCE: 'currentBalance', REMAINING_LIMIT: 'remainingLimit', @@ -5128,21 +5332,64 @@ const CONST = { }, EXCLUDE_FROM_LAST_VISITED_PATH: [SCREENS.NOT_FOUND, SCREENS.SAML_SIGN_IN, SCREENS.VALIDATE_LOGIN] as string[], - UPGRADE_FEATURE_INTRO_MAPPING: [ - { - id: 'reportFields', - alias: 'report-fields', - name: 'Report Fields', - title: 'workspace.upgrade.reportFields.title', - description: 'workspace.upgrade.reportFields.description', - icon: 'Pencil', - }, - ], + EMPTY_STATE_MEDIA: { + ANIMATION: 'animation', + ILLUSTRATION: 'illustration', + VIDEO: 'video', + }, + get UPGRADE_FEATURE_INTRO_MAPPING() { + return { + reportFields: { + id: 'reportFields' as const, + alias: 'report-fields', + name: 'Report Fields', + title: 'workspace.upgrade.reportFields.title' as const, + description: 'workspace.upgrade.reportFields.description' as const, + icon: 'Pencil', + }, + [this.POLICY.CONNECTIONS.NAME.NETSUITE]: { + id: this.POLICY.CONNECTIONS.NAME.NETSUITE, + alias: 'netsuite', + name: this.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.netsuite, + title: `workspace.upgrade.${this.POLICY.CONNECTIONS.NAME.NETSUITE}.title` as const, + description: `workspace.upgrade.${this.POLICY.CONNECTIONS.NAME.NETSUITE}.description` as const, + icon: 'NetSuiteSquare', + }, + [this.POLICY.CONNECTIONS.NAME.SAGE_INTACCT]: { + id: this.POLICY.CONNECTIONS.NAME.SAGE_INTACCT, + alias: 'sage-intacct', + name: this.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.intacct, + title: `workspace.upgrade.${this.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}.title` as const, + description: `workspace.upgrade.${this.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}.description` as const, + icon: 'IntacctSquare', + }, + glCodes: { + id: 'glCodes' as const, + alias: 'gl-codes', + name: 'GL codes', + title: 'workspace.upgrade.glCodes.title' as const, + description: 'workspace.upgrade.glCodes.description' as const, + icon: 'Tag', + }, + glAndPayrollCodes: { + id: 'glAndPayrollCodes' as const, + alias: 'gl-and-payroll-codes', + name: 'GL & Payroll codes', + title: 'workspace.upgrade.glAndPayrollCodes.title' as const, + description: 'workspace.upgrade.glAndPayrollCodes.description' as const, + icon: 'FolderOpen', + }, + }; + }, REPORT_FIELD_TYPES: { TEXT: 'text', DATE: 'date', LIST: 'dropdown', }, + + NAVIGATION_ACTIONS: { + RESET: 'RESET', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/Expensify.tsx b/src/Expensify.tsx index bfe4db13d9c4..f9fd379d94ce 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -3,12 +3,13 @@ import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useStat import type {NativeEventSubscription} 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 Onyx, {useOnyx, withOnyx} from 'react-native-onyx'; import ConfirmModal from './components/ConfirmModal'; import DeeplinkWrapper from './components/DeeplinkWrapper'; import EmojiPicker from './components/EmojiPicker/EmojiPicker'; import FocusModeNotification from './components/FocusModeNotification'; import GrowlNotification from './components/GrowlNotification'; +import RequireTwoFactorAuthenticationModal from './components/RequireTwoFactorAuthenticationModal'; import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper'; import SplashScreenHider from './components/SplashScreenHider'; import UpdateAppModal from './components/UpdateAppModal'; @@ -19,6 +20,7 @@ import * as Report from './libs/actions/Report'; import * as User from './libs/actions/User'; import * as ActiveClientManager from './libs/ActiveClientManager'; import BootSplash from './libs/BootSplash'; +import FS from './libs/Fullstory'; import * as Growl from './libs/Growl'; import Log from './libs/Log'; import migrateOnyx from './libs/migrateOnyx'; @@ -36,6 +38,7 @@ import ONYXKEYS from './ONYXKEYS'; import PopoverReportActionContextMenu from './pages/home/report/ContextMenu/PopoverReportActionContextMenu'; import * as ReportActionContextMenu from './pages/home/report/ContextMenu/ReportActionContextMenu'; import type {Route} from './ROUTES'; +import ROUTES from './ROUTES'; import type {ScreenShareRequest, Session} from './types/onyx'; Onyx.registerLogger(({level, message}) => { @@ -100,6 +103,16 @@ function Expensify({ const [isSplashHidden, setIsSplashHidden] = useState(false); const [hasAttemptedToOpenPublicRoom, setAttemptedToOpenPublicRoom] = useState(false); const {translate} = useLocalize(); + const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const [shouldShowRequire2FAModal, setShouldShowRequire2FAModal] = useState(false); + + useEffect(() => { + if (!account?.needsTwoFactorAuthSetup || account.requiresTwoFactorAuth) { + return; + } + setShouldShowRequire2FAModal(true); + }, [account?.needsTwoFactorAuthSetup, account?.requiresTwoFactorAuth]); + const [initialUrl, setInitialUrl] = useState(null); useEffect(() => { @@ -147,6 +160,9 @@ function Expensify({ // Initialize this client as being an active client ActiveClientManager.init(); + // Initialize Fullstory lib + FS.init(); + // Used for the offline indicator appearing when someone is offline const unsubscribeNetInfo = NetworkConnection.subscribeToNetInfo(); @@ -208,7 +224,7 @@ function Expensify({ } appStateChangeListener.current.remove(); }; - // eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want this effect to run again + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- we don't want this effect to run again }, []); // This is being done since we want to play sound even when iOS device is on silent mode, to align with other platforms. @@ -249,6 +265,16 @@ function Expensify({ /> ) : null} {focusModeNotification ? : null} + {shouldShowRequire2FAModal ? ( + { + setShouldShowRequire2FAModal(false); + Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.HOME)); + }} + isVisible + description={translate('twoFactorAuth.twoFactorAuthIsRequiredForAdminsDescription')} + /> + ) : null} )} diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 906f8ef7095e..62c32e15f3b6 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -134,7 +134,7 @@ const ONYXKEYS = { NVP_LAST_PAYMENT_METHOD: 'nvp_private_lastPaymentMethod', /** This NVP holds to most recent waypoints that a person has used when creating a distance expense */ - NVP_RECENT_WAYPOINTS: 'expensify_recentWaypoints', + NVP_RECENT_WAYPOINTS: 'nvp_expensify_recentWaypoints', /** This NVP contains the choice that the user made on the engagement modal */ NVP_INTRO_SELECTED: 'nvp_introSelected', @@ -320,6 +320,9 @@ const ONYXKEYS = { /** Onboarding Purpose selected by the user during Onboarding flow */ ONBOARDING_PURPOSE_SELECTED: 'onboardingPurposeSelected', + /** Onboarding error message to be displayed to the user */ + ONBOARDING_ERROR_MESSAGE: 'onboardingErrorMessage', + /** Onboarding policyID selected by the user during Onboarding flow */ ONBOARDING_POLICY_ID: 'onboardingPolicyID', @@ -347,6 +350,9 @@ const ONYXKEYS = { /** Indicates whether we should store logs or not */ SHOULD_STORE_LOGS: 'shouldStoreLogs', + /** Indicates whether we should mask fragile user data while exporting onyx state or not */ + SHOULD_MASK_ONYX_STATE: 'shouldMaskOnyxState', + /** Stores new group chat draft */ NEW_GROUP_CHAT_DRAFT: 'newGroupChatDraft', @@ -373,6 +379,9 @@ const ONYXKEYS = { /** Stores info during review duplicates flow */ REVIEW_DUPLICATES: 'reviewDuplicates', + /** Stores the last export method for policy */ + LAST_EXPORT_METHOD: 'lastExportMethod', + /** Stores the information about the state of issuing a new card */ ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard', @@ -411,6 +420,7 @@ const ONYXKEYS = { REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_', REPORT_USER_IS_TYPING: 'reportUserIsTyping_', REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_', + REPORT_VIOLATIONS: 'reportViolations_', SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', TRANSACTION_VIOLATIONS: 'transactionViolations_', @@ -442,6 +452,12 @@ const ONYXKEYS = { * So for example: card_12345_Expensify Card */ WORKSPACE_CARDS_LIST: 'card_', + + /** Stores which connection is set up to use Continuous Reconciliation */ + SHARED_NVP_EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION: 'sharedNVP_expensifyCard_continuousReconciliationConnection_', + + /** The value that indicates whether Continuous Reconciliation should be used on the domain */ + SHARED_NVP_EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION: 'sharedNVP_expensifyCard_useContinuousReconciliation_', }, /** List of Form ids */ @@ -460,8 +476,8 @@ const ONYXKEYS = { WORKSPACE_RATE_AND_UNIT_FORM_DRAFT: 'workspaceRateAndUnitFormDraft', WORKSPACE_TAX_CUSTOM_NAME: 'workspaceTaxCustomName', WORKSPACE_TAX_CUSTOM_NAME_DRAFT: 'workspaceTaxCustomNameDraft', - WORKSPACE_REPORT_FIELDS_FORM: 'workspaceReportFieldsForm', - WORKSPACE_REPORT_FIELDS_FORM_DRAFT: 'workspaceReportFieldsFormDraft', + WORKSPACE_REPORT_FIELDS_FORM: 'workspaceReportFieldForm', + WORKSPACE_REPORT_FIELDS_FORM_DRAFT: 'workspaceReportFieldFormDraft', POLICY_CREATE_DISTANCE_RATE_FORM: 'policyCreateDistanceRateForm', POLICY_CREATE_DISTANCE_RATE_FORM_DRAFT: 'policyCreateDistanceRateFormDraft', POLICY_DISTANCE_RATE_EDIT_FORM: 'policyDistanceRateEditForm', @@ -532,8 +548,8 @@ const ONYXKEYS = { REPORT_VIRTUAL_CARD_FRAUD_DRAFT: 'reportVirtualCardFraudFormDraft', GET_PHYSICAL_CARD_FORM: 'getPhysicalCardForm', GET_PHYSICAL_CARD_FORM_DRAFT: 'getPhysicalCardFormDraft', - REPORT_FIELD_EDIT_FORM: 'reportFieldEditForm', - REPORT_FIELD_EDIT_FORM_DRAFT: 'reportFieldEditFormDraft', + REPORT_FIELDS_EDIT_FORM: 'reportFieldsEditForm', + REPORT_FIELDS_EDIT_FORM_DRAFT: 'reportFieldsEditFormDraft', REIMBURSEMENT_ACCOUNT_FORM: 'reimbursementAccount', REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft', PERSONAL_BANK_ACCOUNT_FORM: 'personalBankAccount', @@ -549,6 +565,8 @@ const ONYXKEYS = { WORKSPACE_NEW_TAX_FORM: 'workspaceNewTaxForm', WORKSPACE_NEW_TAX_FORM_DRAFT: 'workspaceNewTaxFormDraft', WORKSPACE_TAX_NAME_FORM: 'workspaceTaxNameForm', + WORKSPACE_TAX_CODE_FORM: 'workspaceTaxCodeForm', + WORKSPACE_TAX_CODE_FORM_DRAFT: 'workspaceTaxCodeFormDraft', WORKSPACE_TAX_NAME_FORM_DRAFT: 'workspaceTaxNameFormDraft', WORKSPACE_TAX_VALUE_FORM: 'workspaceTaxValueForm', WORKSPACE_TAX_VALUE_FORM_DRAFT: 'workspaceTaxValueFormDraft', @@ -556,12 +574,26 @@ 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', + ISSUE_NEW_EXPENSIFY_CARD_FORM: 'issueNewExpensifyCard', + ISSUE_NEW_EXPENSIFY_CARD_FORM_DRAFT: 'issueNewExpensifyCardDraft', + EDIT_EXPENSIFY_CARD_NAME_FORM: 'editExpensifyCardName', + EDIT_EXPENSIFY_CARD_NAME_DRAFT_FORM: 'editExpensifyCardNameDraft', + EDIT_EXPENSIFY_CARD_LIMIT_FORM: 'editExpensifyCardLimit', + EDIT_EXPENSIFY_CARD_LIMIT_DRAFT_FORM: 'editExpensifyCardLimitDraft', SAGE_INTACCT_CREDENTIALS_FORM: 'sageIntacctCredentialsForm', SAGE_INTACCT_CREDENTIALS_FORM_DRAFT: 'sageIntacctCredentialsFormDraft', + NETSUITE_CUSTOM_FIELD_FORM: 'netSuiteCustomFieldForm', + NETSUITE_CUSTOM_FIELD_FORM_DRAFT: 'netSuiteCustomFieldFormDraft', + NETSUITE_CUSTOM_SEGMENT_ADD_FORM: 'netSuiteCustomSegmentAddForm', + NETSUITE_CUSTOM_SEGMENT_ADD_FORM_DRAFT: 'netSuiteCustomSegmentAddFormDraft', + NETSUITE_CUSTOM_LIST_ADD_FORM: 'netSuiteCustomListAddForm', + NETSUITE_CUSTOM_LIST_ADD_FORM_DRAFT: 'netSuiteCustomListAddFormDraft', NETSUITE_TOKEN_INPUT_FORM: 'netsuiteTokenInputForm', NETSUITE_TOKEN_INPUT_FORM_DRAFT: 'netsuiteTokenInputFormDraft', + NETSUITE_CUSTOM_FORM_ID_FORM: 'netsuiteCustomFormIDForm', + NETSUITE_CUSTOM_FORM_ID_FORM_DRAFT: 'netsuiteCustomFormIDFormDraft', + SAGE_INTACCT_DIMENSION_TYPE_FORM: 'sageIntacctDimensionTypeForm', + SAGE_INTACCT_DIMENSION_TYPE_FORM_DRAFT: 'sageIntacctDimensionTypeFormDraft', }, } as const; @@ -574,7 +606,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WORKSPACE_TAG_FORM]: FormTypes.WorkspaceTagForm; [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM]: FormTypes.WorkspaceRateAndUnitForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_CUSTOM_NAME]: FormTypes.WorkspaceTaxCustomName; - [ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM]: FormTypes.WorkspaceReportFieldsForm; + [ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM]: FormTypes.WorkspaceReportFieldForm; [ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM]: FormTypes.CloseAccountForm; [ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM]: FormTypes.ProfileSettingsForm; [ONYXKEYS.FORMS.DISPLAY_NAME_FORM]: FormTypes.DisplayNameForm; @@ -609,7 +641,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.REPORT_VIRTUAL_CARD_FRAUD]: FormTypes.ReportVirtualCardFraudForm; [ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM]: FormTypes.ReportPhysicalCardForm; [ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM]: FormTypes.GetPhysicalCardForm; - [ONYXKEYS.FORMS.REPORT_FIELD_EDIT_FORM]: FormTypes.ReportFieldEditForm; + [ONYXKEYS.FORMS.REPORT_FIELDS_EDIT_FORM]: FormTypes.ReportFieldsEditForm; [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; @@ -620,12 +652,20 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.POLICY_DISTANCE_RATE_EDIT_FORM]: FormTypes.PolicyDistanceRateEditForm; [ONYXKEYS.FORMS.POLICY_DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT_FORM]: FormTypes.PolicyDistanceRateTaxReclaimableOnEditForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM]: FormTypes.WorkspaceTaxNameForm; + [ONYXKEYS.FORMS.WORKSPACE_TAX_CODE_FORM]: FormTypes.WorkspaceTaxCodeForm; [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.EDIT_EXPENSIFY_CARD_NAME_FORM]: FormTypes.EditExpensifyCardNameForm; + [ONYXKEYS.FORMS.EDIT_EXPENSIFY_CARD_LIMIT_FORM]: FormTypes.EditExpensifyCardLimitForm; [ONYXKEYS.FORMS.SAGE_INTACCT_CREDENTIALS_FORM]: FormTypes.SageIntactCredentialsForm; + [ONYXKEYS.FORMS.NETSUITE_CUSTOM_FIELD_FORM]: FormTypes.NetSuiteCustomFieldForm; + [ONYXKEYS.FORMS.NETSUITE_CUSTOM_LIST_ADD_FORM]: FormTypes.NetSuiteCustomFieldForm; + [ONYXKEYS.FORMS.NETSUITE_CUSTOM_SEGMENT_ADD_FORM]: FormTypes.NetSuiteCustomFieldForm; [ONYXKEYS.FORMS.NETSUITE_TOKEN_INPUT_FORM]: FormTypes.NetSuiteTokenInputForm; + [ONYXKEYS.FORMS.NETSUITE_CUSTOM_FORM_ID_FORM]: FormTypes.NetSuiteCustomFormIDForm; + [ONYXKEYS.FORMS.SAGE_INTACCT_DIMENSION_TYPE_FORM]: FormTypes.SageIntacctDimensionForm; }; type OnyxFormDraftValuesMapping = { @@ -656,6 +696,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE]: boolean; [ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING]: OnyxTypes.ReportUserIsTyping; [ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM]: boolean; + [ONYXKEYS.COLLECTION.REPORT_VIOLATIONS]: OnyxTypes.ReportViolations; [ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup; [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.TRANSACTION_DRAFT]: OnyxTypes.Transaction; @@ -674,6 +715,8 @@ type OnyxCollectionValuesMapping = { [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; + [ONYXKEYS.COLLECTION.SHARED_NVP_EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName; + [ONYXKEYS.COLLECTION.SHARED_NVP_EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION]: boolean; }; type OnyxValuesMapping = { @@ -726,6 +769,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION]: boolean; [ONYXKEYS.FOCUS_MODE_NOTIFICATION]: boolean; [ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: OnyxTypes.LastPaymentMethod; + [ONYXKEYS.LAST_EXPORT_METHOD]: OnyxTypes.LastExportMethod; [ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[]; [ONYXKEYS.NVP_INTRO_SELECTED]: OnyxTypes.IntroSelected; [ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES]: OnyxTypes.LastSelectedDistanceRates; @@ -779,16 +823,19 @@ type OnyxValuesMapping = { [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; [ONYXKEYS.MAX_CANVAS_WIDTH]: number; [ONYXKEYS.ONBOARDING_PURPOSE_SELECTED]: string; + [ONYXKEYS.ONBOARDING_ERROR_MESSAGE]: string; [ONYXKEYS.ONBOARDING_POLICY_ID]: string; [ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID]: string; [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: boolean; [ONYXKEYS.LAST_VISITED_PATH]: string | undefined; + [ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string; [ONYXKEYS.RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields; [ONYXKEYS.UPDATE_REQUIRED]: boolean; [ONYXKEYS.RESET_REQUIRED]: boolean; [ONYXKEYS.PLAID_CURRENT_EVENT]: string; [ONYXKEYS.LOGS]: OnyxTypes.CapturedLogs; [ONYXKEYS.SHOULD_STORE_LOGS]: boolean; + [ONYXKEYS.SHOULD_MASK_ONYX_STATE]: boolean; [ONYXKEYS.CACHED_PDF_PATHS]: Record; [ONYXKEYS.POLICY_OWNERSHIP_CHANGE_CHECKS]: Record; [ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE]: OnyxTypes.QuickAction; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2347bd4f93f4..ae7781318c52 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -3,6 +3,7 @@ import type CONST from './CONST'; import type {IOUAction, IOUType} from './CONST'; import type {IOURequestType} from './libs/actions/IOU'; import type {AuthScreensParamList} from './libs/Navigation/types'; +import type {ConnectionName, SageIntacctMappingName} from './types/onyx/Policy'; import type {SearchQuery} from './types/onyx/SearchResults'; import type AssertTypesNotEqual from './types/utils/AssertTypesNotEqual'; @@ -35,7 +36,7 @@ const ROUTES = { ALL_SETTINGS: 'all-settings', - SEARCH: { + SEARCH_CENTRAL_PANE: { route: '/search/:query', getRoute: (searchQuery: SearchQuery, queryParams?: AuthScreensParamList['Search_Central_Pane']) => { const {sortBy, sortOrder} = queryParams ?? {}; @@ -48,11 +49,22 @@ const ROUTES = { }, }, + SEARCH_ADVANCED_FILTERS: 'search/filters', + + SEARCH_ADVANCED_FILTERS_DATE: 'search/filters/date', + + SEARCH_ADVANCED_FILTERS_TYPE: 'search/filters/type', + SEARCH_REPORT: { - route: '/search/:query/view/:reportID', + route: 'search/:query/view/:reportID', getRoute: (query: string, reportID: string) => `search/${query}/view/${reportID}` as const, }, + TRANSACTION_HOLD_REASON_RHP: { + route: 'search/:query/hold', + getRoute: (query: string) => `search/${query}/hold` 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: { @@ -192,7 +204,8 @@ const ROUTES = { }, SETTINGS_2FA: { route: 'settings/security/two-factor-auth', - getRoute: (backTo?: string) => getUrlWithBackToParam('settings/security/two-factor-auth', backTo), + getRoute: (backTo?: string, forwardTo?: string) => + getUrlWithBackToParam(forwardTo ? `settings/security/two-factor-auth?forwardTo=${encodeURIComponent(forwardTo)}` : 'settings/security/two-factor-auth', backTo), }, SETTINGS_STATUS: 'settings/profile/status', @@ -280,6 +293,10 @@ const ROUTES = { route: 'r/:reportID/details', getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/details`, backTo), }, + REPORT_WITH_ID_DETAILS_EXPORT: { + route: 'r/:reportID/details/export/:connectionName', + getRoute: (reportID: string, connectionName: ConnectionName) => `r/${reportID}/details/export/${connectionName}` as const, + }, REPORT_SETTINGS: { route: 'r/:reportID/settings', getRoute: (reportID: string) => `r/${reportID}/settings` as const, @@ -670,6 +687,15 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/invoice-account-selector', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/invoice-account-selector` as const, }, + WORKSPACE_ACCOUNTING_CARD_RECONCILIATION: { + route: 'settings/workspaces/:policyID/accounting/:connection/card-reconciliation', + getRoute: (policyID: string, connection: ValueOf) => `settings/workspaces/${policyID}/accounting/${connection}/card-reconciliation` as const, + }, + WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS: { + route: 'settings/workspaces/:policyID/accounting/:connection/card-reconciliation/account', + getRoute: (policyID: string, connection: ValueOf) => + `settings/workspaces/${policyID}/accounting/${connection}/card-reconciliation/account` as const, + }, WORKSPACE_CATEGORIES: { route: 'settings/workspaces/:policyID/categories', getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories` as const, @@ -680,7 +706,8 @@ const ROUTES = { }, WORKSPACE_UPGRADE: { route: 'settings/workspaces/:policyID/upgrade/:featureName', - getRoute: (policyID: string, featureName: string) => `settings/workspaces/${policyID}/upgrade/${encodeURIComponent(featureName)}` as const, + getRoute: (policyID: string, featureName: string, backTo?: string) => + getUrlWithBackToParam(`settings/workspaces/${policyID}/upgrade/${encodeURIComponent(featureName)}` as const, backTo), }, WORKSPACE_CATEGORIES_SETTINGS: { route: 'settings/workspaces/:policyID/categories/settings', @@ -694,6 +721,14 @@ const ROUTES = { route: 'settings/workspaces/:policyID/categories/:categoryName/edit', getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/edit` as const, }, + WORKSPACE_CATEGORY_PAYROLL_CODE: { + route: 'settings/workspaces/:policyID/categories/:categoryName/payroll-code', + getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/payroll-code` as const, + }, + WORKSPACE_CATEGORY_GL_CODE: { + route: 'settings/workspaces/:policyID/categories/:categoryName/gl-code', + getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/gl-code` as const, + }, WORKSPACE_MORE_FEATURES: { route: 'settings/workspaces/:policyID/more-features', getRoute: (policyID: string) => `settings/workspaces/${policyID}/more-features` as const, @@ -726,6 +761,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/tag-list/:orderWeight', getRoute: (policyID: string, orderWeight: number) => `settings/workspaces/${policyID}/tag-list/${orderWeight}` as const, }, + WORKSPACE_TAG_GL_CODE: { + route: 'settings/workspaces/:policyID/tag/:orderWeight/:tagName/gl-code', + getRoute: (policyID: string, orderWeight: number, tagName: string) => `settings/workspaces/${policyID}/tag/${orderWeight}/${encodeURIComponent(tagName)}/gl-code` as const, + }, WORKSPACE_TAXES: { route: 'settings/workspaces/:policyID/taxes', getRoute: (policyID: string) => `settings/workspaces/${policyID}/taxes` as const, @@ -783,45 +822,83 @@ const ROUTES = { route: 'settings/workspaces/:policyID/tax/:taxID/value', getRoute: (policyID: string, taxID: string) => `settings/workspaces/${policyID}/tax/${encodeURIComponent(taxID)}/value` as const, }, + WORKSPACE_TAX_CODE: { + route: 'settings/workspaces/:policyID/tax/:taxID/tax-code', + getRoute: (policyID: string, taxID: string) => `settings/workspaces/${policyID}/tax/${encodeURIComponent(taxID)}/tax-code` as const, + }, WORKSPACE_REPORT_FIELDS: { route: 'settings/workspaces/:policyID/reportFields', getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields` as const, }, - WORKSPACE_REPORT_FIELD_SETTINGS: { - route: 'settings/workspaces/:policyID/reportField/:reportFieldKey', - getRoute: (policyID: string, reportFieldKey: string) => `settings/workspaces/${policyID}/reportField/${encodeURIComponent(reportFieldKey)}` as const, - }, WORKSPACE_CREATE_REPORT_FIELD: { route: 'settings/workspaces/:policyID/reportFields/new', getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields/new` as const, }, - WORKSPACE_REPORT_FIELD_LIST_VALUES: { - route: 'settings/workspaces/:policyID/reportFields/new/listValues', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields/new/listValues` as const, + WORKSPACE_REPORT_FIELDS_SETTINGS: { + route: 'settings/workspaces/:policyID/reportFields/:reportFieldID/edit', + getRoute: (policyID: string, reportFieldID: string) => `settings/workspaces/${policyID}/reportFields/${encodeURIComponent(reportFieldID)}/edit` as const, + }, + WORKSPACE_REPORT_FIELDS_LIST_VALUES: { + route: 'settings/workspaces/:policyID/reportFields/listValues/:reportFieldID?', + getRoute: (policyID: string, reportFieldID?: string) => `settings/workspaces/${policyID}/reportFields/listValues/${encodeURIComponent(reportFieldID ?? '')}` as const, }, - WORKSPACE_REPORT_FIELD_ADD_VALUE: { - route: 'settings/workspaces/:policyID/reportFields/new/addValue', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields/new/addValue` as const, + WORKSPACE_REPORT_FIELDS_ADD_VALUE: { + route: 'settings/workspaces/:policyID/reportFields/addValue/:reportFieldID?', + getRoute: (policyID: string, reportFieldID?: string) => `settings/workspaces/${policyID}/reportFields/addValue/${encodeURIComponent(reportFieldID ?? '')}` as const, }, - WORKSPACE_REPORT_FIELD_VALUE_SETTINGS: { - route: 'settings/workspaces/:policyID/reportFields/new/:valueIndex', - getRoute: (policyID: string, valueIndex: number) => `settings/workspaces/${policyID}/reportFields/new/${valueIndex}` as const, + WORKSPACE_REPORT_FIELDS_VALUE_SETTINGS: { + route: 'settings/workspaces/:policyID/reportFields/:valueIndex/:reportFieldID?', + getRoute: (policyID: string, valueIndex: number, reportFieldID?: string) => + `settings/workspaces/${policyID}/reportFields/${valueIndex}/${encodeURIComponent(reportFieldID ?? '')}` as const, }, - WORKSPACE_REPORT_FIELD_EDIT_VALUE: { + WORKSPACE_REPORT_FIELDS_EDIT_VALUE: { route: 'settings/workspaces/:policyID/reportFields/new/:valueIndex/edit', getRoute: (policyID: string, valueIndex: number) => `settings/workspaces/${policyID}/reportFields/new/${valueIndex}/edit` as const, }, + WORKSPACE_EDIT_REPORT_FIELDS_INITIAL_VALUE: { + route: 'settings/workspaces/:policyID/reportFields/:reportFieldID/edit/initialValue', + getRoute: (policyID: string, reportFieldID: string) => `settings/workspaces/${policyID}/reportFields/${encodeURIComponent(reportFieldID)}/edit/initialValue` 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_EXPENSIFY_CARD_DETAILS: { + route: 'settings/workspaces/:policyID/expensify-card/:cardID', + getRoute: (policyID: string, cardID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/expensify-card/${cardID}`, backTo), + }, + WORKSPACE_EXPENSIFY_CARD_NAME: { + route: 'settings/workspaces/:policyID/expensify-card/:cardID/edit/name', + getRoute: (policyID: string, cardID: string) => `settings/workspaces/${policyID}/expensify-card/${cardID}/edit/name` as const, + }, + WORKSPACE_EXPENSIFY_CARD_LIMIT: { + route: 'settings/workspaces/:policyID/expensify-card/:cardID/edit/limit', + getRoute: (policyID: string, cardID: string) => `settings/workspaces/${policyID}/expensify-card/${cardID}/edit/limit` as const, + }, + WORKSPACE_EXPENSIFY_CARD_LIMIT_TYPE: { + route: 'settings/workspaces/:policyID/expensify-card/:cardID/edit/limit-type', + getRoute: (policyID: string, cardID: string) => `settings/workspaces/${policyID}/expensify-card/${cardID}/edit/limit-type` as const, + }, + WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW: { + route: 'settings/workspaces/:policyID/expensify-card/issue-new', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/issue-new` as const, + }, + WORKSPACE_EXPENSIFY_CARD_BANK_ACCOUNT: { + route: 'settings/workspaces/:policyID/expensify-card/choose-bank-account', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/choose-bank-account` as const, + }, + WORKSPACE_EXPENSIFY_CARD_SETTINGS: { + route: 'settings/workspaces/:policyID/expensify-card/settings', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/settings` as const, + }, + WORKSPACE_EXPENSIFY_CARD_SETTINGS_ACCOUNT: { + route: 'settings/workspaces/:policyID/expensify-card/settings/account', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/settings/account` as const, + }, + WORKSPACE_EXPENSIFY_CARD_SETTINGS_FREQUENCY: { + route: 'settings/workspaces/:policyID/expensify-card/settings/frequency', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/settings/frequency` as const, + }, WORKSPACE_DISTANCE_RATES: { route: 'settings/workspaces/:policyID/distance-rates', getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates` as const, @@ -891,8 +968,8 @@ const ROUTES = { getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/tax-code` as const, }, TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE: { - route: 'r/:threadReportID/duplicates/confirm', - getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/confirm` as const, + route: 'r/:threadReportID/duplicates/review/description', + getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/description` as const, }, TRANSACTION_DUPLICATE_REVIEW_REIMBURSABLE_PAGE: { route: 'r/:threadReportID/duplicates/review/reimbursable', @@ -902,7 +979,10 @@ const ROUTES = { route: 'r/:threadReportID/duplicates/review/billable', getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/billable` as const, }, - + TRANSACTION_DUPLICATE_CONFIRMATION_PAGE: { + route: 'r/:threadReportID/duplicates/confirm', + getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/confirm` as const, + }, POLICY_ACCOUNTING_XERO_IMPORT: { route: 'settings/workspaces/:policyID/accounting/xero/import', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import` as const, @@ -996,6 +1076,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/netsuite/subsidiary-selector', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/subsidiary-selector` as const, }, + POLICY_ACCOUNTING_NETSUITE_EXISTING_CONNECTIONS: { + route: 'settings/workspaces/:policyID/accounting/netsuite/existing-connections', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/existing-connections` as const, + }, POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT: { route: 'settings/workspaces/:policyID/accounting/netsuite/token-input', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/token-input` as const, @@ -1009,6 +1093,29 @@ const ROUTES = { getRoute: (policyID: string, importField: TupleToUnion) => `settings/workspaces/${policyID}/accounting/netsuite/import/mapping/${importField}` as const, }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_MAPPING: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom/:importCustomField', + getRoute: (policyID: string, importCustomField: ValueOf) => + `settings/workspaces/${policyID}/accounting/netsuite/import/custom/${importCustomField}` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_VIEW: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom/:importCustomField/view/:valueIndex', + getRoute: (policyID: string, importCustomField: ValueOf, valueIndex: number) => + `settings/workspaces/${policyID}/accounting/netsuite/import/custom/${importCustomField}/view/${valueIndex}` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_EDIT: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom/:importCustomField/edit/:valueIndex/:fieldName', + getRoute: (policyID: string, importCustomField: ValueOf, valueIndex: number, fieldName: string) => + `settings/workspaces/${policyID}/accounting/netsuite/import/custom/${importCustomField}/edit/${valueIndex}/${fieldName}` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_LIST_ADD: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom-list/new', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/import/custom-list/new` as const, + }, + POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD: { + route: 'settings/workspaces/:policyID/accounting/netsuite/import/custom-segment/new', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/import/custom-segment/new` as const, + }, POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS: { route: 'settings/workspaces/:policyID/accounting/netsuite/import/customer-projects', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/import/customer-projects` as const, @@ -1078,6 +1185,35 @@ const ROUTES = { route: 'settings/workspaces/:policyID/connections/netsuite/advanced/', getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/` as const, }, + POLICY_ACCOUNTING_NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/reimbursement-account/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/reimbursement-account/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_COLLECTION_ACCOUNT_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/collection-account/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/collection-account/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_EXPENSE_REPORT_APPROVAL_LEVEL_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/expense-report-approval-level/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/expense-report-approval-level/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_VENDOR_BILL_APPROVAL_LEVEL_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/vendor-bill-approval-level/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/vendor-bill-approval-level/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_JOURNAL_ENTRY_APPROVAL_LEVEL_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/journal-entry-approval-level/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/journal-entry-approval-level/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_APPROVAL_ACCOUNT_SELECT: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/approval-account/select', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/approval-account/select` as const, + }, + POLICY_ACCOUNTING_NETSUITE_CUSTOM_FORM_ID: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/custom-form-id/:expenseType', + getRoute: (policyID: string, expenseType: ValueOf) => + `settings/workspaces/${policyID}/connections/netsuite/advanced/custom-form-id/${expenseType}` 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, @@ -1090,6 +1226,70 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/sage-intacct/existing-connections', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/existing-connections` as const, }, + POLICY_ACCOUNTING_SAGE_INTACCT_ENTITY: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/entity', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/entity` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_IMPORT: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/import', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/import` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_TOGGLE_MAPPINGS: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/import/toggle-mapping/:mapping', + getRoute: (policyID: string, mapping: SageIntacctMappingName) => `settings/workspaces/${policyID}/accounting/sage-intacct/import/toggle-mapping/${mapping}` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_MAPPINGS_TYPE: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/import/mapping-type/:mapping', + getRoute: (policyID: string, mapping: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/import/mapping-type/${mapping}` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_USER_DIMENSIONS: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/import/user-dimensions', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/import/user-dimensions` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_ADD_USER_DIMENSION: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/import/add-user-dimension', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/import/add-user-dimension` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_EDIT_USER_DIMENSION: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/import/edit-user-dimension/:dimensionName', + getRoute: (policyID: string, dimensionName: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/import/edit-user-dimension/${dimensionName}` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/export', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_PREFERRED_EXPORTER: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/preferred-exporter', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/preferred-exporter` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT_DATE: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/date', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/date` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_REIMBURSABLE_EXPENSES: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/reimbursable', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/reimbursable` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_NON_REIMBURSABLE_EXPENSES: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/nonreimbursable', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/nonreimbursable` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_DEFAULT_VENDOR: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/:reimbursable/default-vendor', + getRoute: (policyID: string, reimbursable: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/${reimbursable}/default-vendor` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_NON_REIMBURSABLE_CREDIT_CARD_ACCOUNT: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/export/nonreimbursable/credit-card-account', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export/nonreimbursable/credit-card-account` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_ADVANCED: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/advanced', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/advanced` as const, + }, + POLICY_ACCOUNTING_SAGE_INTACCT_PAYMENT_ACCOUNT: { + route: 'settings/workspaces/:policyID/accounting/sage-intacct/advanced/payment-account', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/advanced/payment-account` as const, + }, } as const; /** diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 8d077d635fcc..d7d56089876d 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -30,6 +30,10 @@ const SCREENS = { SEARCH: { CENTRAL_PANE: 'Search_Central_Pane', REPORT_RHP: 'Search_Report_RHP', + ADVANCED_FILTERS_RHP: 'Search_Advanced_Filters_RHP', + ADVANCED_FILTERS_DATE_RHP: 'Search_Advanced_Filters_Date_RHP', + ADVANCED_FILTERS_TYPE_RHP: 'Search_Advanced_Filters_Type_RHP', + TRANSACTION_HOLD_REASON_RHP: 'Search_Transaction_Hold_Reason_RHP', BOTTOM_TAB: 'Search_Bottom_Tab', }, SETTINGS: { @@ -144,8 +148,10 @@ const SCREENS = { TRANSACTION_DUPLICATE: 'TransactionDuplicate', TRAVEL: 'Travel', SEARCH_REPORT: 'SearchReport', + SEARCH_ADVANCED_FILTERS: 'SearchAdvancedFilters', SETTINGS_CATEGORIES: 'SettingsCategories', RESTRICTED_ACTION: 'RestrictedAction', + REPORT_EXPORT: 'Report_Export', }, ONBOARDING_MODAL: { ONBOARDING: 'Onboarding', @@ -193,6 +199,7 @@ const SCREENS = { TAX_CODE: 'Transaction_Duplicate_Tax_Code', REIMBURSABLE: 'Transaction_Duplicate_Reimburable', BILLABLE: 'Transaction_Duplicate_Billable', + CONFIRMATION: 'Transaction_Duplicate_Confirmation', }, IOU_SEND: { @@ -239,6 +246,7 @@ const SCREENS = { REPORT_DETAILS: { ROOT: 'Report_Details_Root', SHARE_CODE: 'Report_Details_Share_Code', + EXPORT: 'Report_Details_Export', }, WORKSPACE: { @@ -280,8 +288,14 @@ const SCREENS = { XERO_BILL_PAYMENT_ACCOUNT_SELECTOR: 'Policy_Accounting_Xero_Bill_Payment_Account_Selector', XERO_EXPORT_BANK_ACCOUNT_SELECT: 'Policy_Accounting_Xero_Export_Bank_Account_Select', NETSUITE_IMPORT_MAPPING: 'Policy_Accounting_NetSuite_Import_Mapping', + NETSUITE_IMPORT_CUSTOM_FIELD: 'Policy_Accounting_NetSuite_Import_Custom_Field', + NETSUITE_IMPORT_CUSTOM_FIELD_VIEW: 'Policy_Accounting_NetSuite_Import_Custom_Field_View', + NETSUITE_IMPORT_CUSTOM_FIELD_EDIT: 'Policy_Accounting_NetSuite_Import_Custom_Field_Edit', + NETSUITE_IMPORT_CUSTOM_LIST_ADD: 'Policy_Accounting_NetSuite_Import_Custom_List_Add', + NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD: 'Policy_Accounting_NetSuite_Import_Custom_Segment_Add', NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS: 'Policy_Accounting_NetSuite_Import_CustomersOrProjects', NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT: 'Policy_Accounting_NetSuite_Import_CustomersOrProjects_Select', + NETSUITE_REUSE_EXISTING_CONNECTIONS: 'Policy_Accounting_NetSuite_Reuse_Existing_Connections', NETSUITE_TOKEN_INPUT: 'Policy_Accounting_NetSuite_Token_Input', NETSUITE_SUBSIDIARY_SELECTOR: 'Policy_Accounting_NetSuite_Subsidiary_Selector', NETSUITE_IMPORT: 'Policy_Accounting_NetSuite_Import', @@ -299,9 +313,34 @@ const SCREENS = { 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', NETSUITE_ADVANCED: 'Policy_Accounting_NetSuite_Advanced', + NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Reimbursement_Account_Select', + NETSUITE_COLLECTION_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Collection_Account_Select', + NETSUITE_EXPENSE_REPORT_APPROVAL_LEVEL_SELECT: 'Policy_Accounting_NetSuite_Expense_Report_Approval_Level_Select', + NETSUITE_VENDOR_BILL_APPROVAL_LEVEL_SELECT: 'Policy_Accounting_NetSuite_Vendor_Bill_Approval_Level_Select', + NETSUITE_JOURNAL_ENTRY_APPROVAL_LEVEL_SELECT: 'Policy_Accounting_NetSuite_Journal_Entry_Approval_Level_Select', + NETSUITE_APPROVAL_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Approval_Account_Select', + NETSUITE_CUSTOM_FORM_ID: 'Policy_Accounting_NetSuite_Custom_Form_ID', 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', + SAGE_INTACCT_ENTITY: 'Policy_Sage_Intacct_Entity', + SAGE_INTACCT_IMPORT: 'Policy_Accounting_Sage_Intacct_Import', + SAGE_INTACCT_TOGGLE_MAPPING: 'Policy_Accounting_Sage_Intacct_Toggle_Mapping', + SAGE_INTACCT_MAPPING_TYPE: 'Policy_Accounting_Sage_Intacct_Mapping_Type', + SAGE_INTACCT_USER_DIMENSIONS: 'Policy_Accounting_Sage_Intacct_User_Dimensions', + SAGE_INTACCT_ADD_USER_DIMENSION: 'Policy_Accounting_Sage_Intacct_Add_User_Dimension', + SAGE_INTACCT_EDIT_USER_DIMENSION: 'Policy_Accounting_Sage_Intacct_Edit_User_Dimension', + SAGE_INTACCT_EXPORT: 'Policy_Accounting_Sage_Intacct_Export', + SAGE_INTACCT_PREFERRED_EXPORTER: 'Policy_Accounting_Sage_Intacct_Preferred_Exporter', + SAGE_INTACCT_EXPORT_DATE: 'Policy_Accounting_Sage_Intacct_Export_Date', + SAGE_INTACCT_REIMBURSABLE_EXPENSES: 'Policy_Accounting_Sage_Intacct_Reimbursable_Expenses', + SAGE_INTACCT_NON_REIMBURSABLE_EXPENSES: 'Policy_Accounting_Sage_Intacct_Non_Reimbursable_Expenses', + SAGE_INTACCT_DEFAULT_VENDOR: 'Policy_Accounting_Sage_Intacct_Default_Vendor', + SAGE_INTACCT_NON_REIMBURSABLE_CREDIT_CARD_ACCOUNT: 'Policy_Accounting_Sage_Intacct_Non_Reimbursable_Credit_Card_Account', + SAGE_INTACCT_ADVANCED: 'Policy_Accounting_Sage_Intacct_Advanced', + SAGE_INTACCT_PAYMENT_ACCOUNT: 'Policy_Accounting_Sage_Intacct_Payment_Account', + CARD_RECONCILIATION: 'Policy_Accounting_Card_Reconciliation', + RECONCILIATION_ACCOUNT_SETTINGS: 'Policy_Accounting_Reconciliation_Account_Settings', }, INITIAL: 'Workspace_Initial', PROFILE: 'Workspace_Profile', @@ -311,7 +350,15 @@ const SCREENS = { RATE_AND_UNIT_RATE: 'Workspace_RateAndUnit_Rate', RATE_AND_UNIT_UNIT: 'Workspace_RateAndUnit_Unit', EXPENSIFY_CARD: 'Workspace_ExpensifyCard', + EXPENSIFY_CARD_DETAILS: 'Workspace_ExpensifyCard_Details', + EXPENSIFY_CARD_LIMIT: 'Workspace_ExpensifyCard_Limit', EXPENSIFY_CARD_ISSUE_NEW: 'Workspace_ExpensifyCard_New', + EXPENSIFY_CARD_NAME: 'Workspace_ExpensifyCard_Name', + EXPENSIFY_CARD_LIMIT_TYPE: 'Workspace_ExpensifyCard_LimitType', + EXPENSIFY_CARD_BANK_ACCOUNT: 'Workspace_ExpensifyCard_BankAccount', + EXPENSIFY_CARD_SETTINGS: 'Workspace_ExpensifyCard_Settings', + EXPENSIFY_CARD_SETTINGS_ACCOUNT: 'Workspace_ExpensifyCard_Settings_Account', + EXPENSIFY_CARD_SETTINGS_FREQUENCY: 'Workspace_ExpensifyCard_Settings_Frequency', BILLS: 'Workspace_Bills', INVOICES: 'Workspace_Invoices', TRAVEL: 'Workspace_Travel', @@ -325,15 +372,17 @@ const SCREENS = { TAG_EDIT: 'Tag_Edit', TAXES: 'Workspace_Taxes', REPORT_FIELDS: 'Workspace_ReportFields', - REPORT_FIELD_SETTINGS: 'Workspace_ReportField_Settings', + REPORT_FIELDS_SETTINGS: 'Workspace_ReportFields_Settings', REPORT_FIELDS_CREATE: 'Workspace_ReportFields_Create', REPORT_FIELDS_LIST_VALUES: 'Workspace_ReportFields_ListValues', REPORT_FIELDS_ADD_VALUE: 'Workspace_ReportFields_AddValue', REPORT_FIELDS_VALUE_SETTINGS: 'Workspace_ReportFields_ValueSettings', REPORT_FIELDS_EDIT_VALUE: 'Workspace_ReportFields_EditValue', + REPORT_FIELDS_EDIT_INITIAL_VALUE: 'Workspace_ReportFields_EditInitialValue', TAX_EDIT: 'Workspace_Tax_Edit', TAX_NAME: 'Workspace_Tax_Name', TAX_VALUE: 'Workspace_Tax_Value', + TAX_CODE: 'Workspace_Tax_Code', TAXES_SETTINGS: 'Workspace_Taxes_Settings', TAXES_SETTINGS_CUSTOM_TAX_NAME: 'Workspace_Taxes_Settings_CustomTaxName', TAXES_SETTINGS_WORKSPACE_CURRENCY_DEFAULT: 'Workspace_Taxes_Settings_WorkspaceCurrency', @@ -342,6 +391,7 @@ const SCREENS = { TAG_CREATE: 'Tag_Create', TAG_SETTINGS: 'Tag_Settings', TAG_LIST_VIEW: 'Tag_List_View', + TAG_GL_CODE: 'Tag_GL_Code', CURRENCY: 'Workspace_Profile_Currency', ADDRESS: 'Workspace_Profile_Address', WORKFLOWS: 'Workspace_Workflows', @@ -354,6 +404,8 @@ const SCREENS = { NAME: 'Workspace_Profile_Name', CATEGORY_CREATE: 'Category_Create', CATEGORY_EDIT: 'Category_Edit', + CATEGORY_PAYROLL_CODE: 'Category_Payroll_Code', + CATEGORY_GL_CODE: 'Category_GL_Code', CATEGORY_SETTINGS: 'Category_Settings', CATEGORIES_SETTINGS: 'Categories_Settings', MORE_FEATURES: 'Workspace_More_Features', diff --git a/src/components/AccountingListSkeletonView.tsx b/src/components/AccountingListSkeletonView.tsx index b977903d3adc..dbe8ada6c4b7 100644 --- a/src/components/AccountingListSkeletonView.tsx +++ b/src/components/AccountingListSkeletonView.tsx @@ -4,12 +4,14 @@ import ItemListSkeletonView from './Skeletons/ItemListSkeletonView'; type AccountingListSkeletonViewProps = { shouldAnimate?: boolean; + gradientOpacityEnabled?: boolean; }; -function AccountingListSkeletonView({shouldAnimate = true}: AccountingListSkeletonViewProps) { +function AccountingListSkeletonView({shouldAnimate = true, gradientOpacityEnabled = false}: AccountingListSkeletonViewProps) { return ( ( <> { diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx index 9bd6142b5604..2679a550f72f 100644 --- a/src/components/AddressSearch/index.tsx +++ b/src/components/AddressSearch/index.tsx @@ -24,7 +24,24 @@ import CONST from '@src/CONST'; import type {Address} from '@src/types/onyx/PrivatePersonalDetails'; import CurrentLocationButton from './CurrentLocationButton'; import isCurrentTargetInsideContainer from './isCurrentTargetInsideContainer'; -import type {AddressSearchProps} from './types'; +import type {AddressSearchProps, PredefinedPlace} from './types'; + +/** + * Check if the place matches the search by the place name or description. + * @param search The search string for a place + * @param place The place to check for a match on the search + * @returns true if search is related to place, otherwise it returns false. + */ +function isPlaceMatchForSearch(search: string, place: PredefinedPlace): boolean { + if (!search) { + return true; + } + if (!place) { + return false; + } + const fullSearchSentence = `${place.name ?? ''} ${place.description}`; + return search.split(' ').every((searchTerm) => !searchTerm || fullSearchSentence.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())); +} // The error that's being thrown below will be ignored until we fork the // react-native-google-places-autocomplete repo and replace the @@ -42,6 +59,7 @@ function AddressSearch( isLimitedToUSA = false, label, maxInputLength, + onFocus, onBlur, onInputChange, onPress, @@ -72,7 +90,7 @@ function AddressSearch( const [isTyping, setIsTyping] = useState(false); const [isFocused, setIsFocused] = useState(false); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const [searchValue, setSearchValue] = useState(value || defaultValue || ''); + const [searchValue, setSearchValue] = useState(''); const [locationErrorCode, setLocationErrorCode] = useState(null); const [isFetchingCurrentLocation, setIsFetchingCurrentLocation] = useState(false); const shouldTriggerGeolocationCallbacks = useRef(true); @@ -282,7 +300,7 @@ function AddressSearch( // eslint-disable-next-line react/jsx-no-useless-fragment <> {(predefinedPlaces?.length ?? 0) > 0 && ( - <> + {/* This will show current location button in list if there are some recent destinations */} {shouldShowCurrentLocationButton && ( )} {!value && {translate('common.recentDestinations')}} - + )} ); @@ -304,10 +322,16 @@ function AddressSearch( }; }, []); + const filteredPredefinedPlaces = useMemo(() => { + if (!searchValue) { + return predefinedPlaces ?? []; + } + return predefinedPlaces?.filter((predefinedPlace) => isPlaceMatchForSearch(searchValue, predefinedPlace)) ?? []; + }, [predefinedPlaces, searchValue]); + const listEmptyComponent = useCallback( - () => - !!isOffline || !isTyping ? null : {translate('common.noResultsFound')}, - [isOffline, isTyping, styles, translate], + () => (!isTyping ? null : {translate('common.noResultsFound')}), + [isTyping, styles, translate], ); const listLoader = useCallback( @@ -348,7 +372,7 @@ function AddressSearch( fetchDetails suppressDefaultStyles enablePoweredByContainer={false} - predefinedPlaces={predefinedPlaces ?? undefined} + predefinedPlaces={filteredPredefinedPlaces} listEmptyComponent={listEmptyComponent} listLoaderComponent={listLoader} renderHeaderComponent={renderHeaderComponent} @@ -357,7 +381,7 @@ function AddressSearch( const subtitle = data.isPredefinedPlace ? data.description : data.structured_formatting.secondary_text; return ( - {!!title && {title}} + {!!title && {title}} {subtitle} ); @@ -391,6 +415,7 @@ function AddressSearch( shouldSaveDraft, onFocus: () => { setIsFocused(true); + onFocus?.(); }, onBlur: (event) => { if (!isCurrentTargetInsideContainer(event, containerRef)) { @@ -420,10 +445,11 @@ function AddressSearch( }} styles={{ textInputContainer: [styles.flexColumn], - listView: [StyleUtils.getGoogleListViewStyle(displayListViewBorder), styles.overflowAuto, styles.borderLeft, styles.borderRight, !isFocused && {height: 0}], + listView: [StyleUtils.getGoogleListViewStyle(displayListViewBorder), styles.borderLeft, styles.borderRight, !isFocused && styles.h0], row: [styles.pv4, styles.ph3, styles.overflowAuto], description: [styles.googleSearchText], - separator: [styles.googleSearchSeparator], + separator: [styles.googleSearchSeparator, styles.overflowAuto], + container: [styles.mh100], }} numberOfLines={2} isRowScrollable={false} @@ -447,11 +473,13 @@ function AddressSearch( ) } placeholder="" - /> - setLocationErrorCode(null)} - locationErrorCode={locationErrorCode} - /> + listViewDisplayed + > + setLocationErrorCode(null)} + locationErrorCode={locationErrorCode} + /> + {isFetchingCurrentLocation && } diff --git a/src/components/AddressSearch/types.ts b/src/components/AddressSearch/types.ts index 82e4c3c3fc37..b654fcad99da 100644 --- a/src/components/AddressSearch/types.ts +++ b/src/components/AddressSearch/types.ts @@ -23,6 +23,10 @@ type StreetValue = { street: string; }; +type PredefinedPlace = Place & { + name?: string; +}; + type AddressSearchProps = { /** The ID used to uniquely identify the input in a Form */ inputID?: string; @@ -30,6 +34,9 @@ type AddressSearchProps = { /** Saves a draft of the input value when used in a form */ shouldSaveDraft?: boolean; + /** Callback that is called when the text input is focused */ + onFocus?: () => void; + /** Callback that is called when the text input is blurred */ onBlur?: () => void; @@ -64,7 +71,7 @@ type AddressSearchProps = { canUseCurrentLocation?: boolean; /** A list of predefined places that can be shown when the user isn't searching for something */ - predefinedPlaces?: Place[] | null; + predefinedPlaces?: PredefinedPlace[] | null; /** A map of inputID key names */ renamedInputKeys?: Address; @@ -84,4 +91,4 @@ type AddressSearchProps = { type IsCurrentTargetInsideContainerType = (event: FocusEvent | NativeSyntheticEvent, containerRef: RefObject) => boolean; -export type {CurrentLocationButtonProps, AddressSearchProps, IsCurrentTargetInsideContainerType, StreetValue}; +export type {CurrentLocationButtonProps, AddressSearchProps, IsCurrentTargetInsideContainerType, StreetValue, PredefinedPlace}; diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 3319a28c58b9..1eb272dce49a 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -135,7 +135,7 @@ function AmountForm( setNewAmount(MoneyRequestUtils.stripDecimalsFromAmount(currentAmount)); // we want to update only when decimals change (setNewAmount also changes when decimals change). - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [decimals]); /** diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index 48604ec364c7..b9aeceeb3621 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -41,7 +41,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow return ( - {({anchor, report, action, checkIfContextMenuActive}) => ( + {({anchor, report, reportNameValuePairs, action, checkIfContextMenuActive}) => ( { @@ -53,7 +53,9 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow }} onPressIn={onPressIn} onPressOut={onPressOut} - onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => + showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)) + } shouldUseHapticsOnLongPress accessibilityLabel={displayName} role={CONST.ROLE.BUTTON} diff --git a/src/components/AnimatedStep/index.tsx b/src/components/AnimatedStep/index.tsx index 2fb3e3167ff8..6de7d0c2b013 100644 --- a/src/components/AnimatedStep/index.tsx +++ b/src/components/AnimatedStep/index.tsx @@ -37,6 +37,7 @@ function AnimatedStep({onAnimationEnd, direction = CONST.ANIMATION_DIRECTION.IN, }} duration={CONST.ANIMATED_TRANSITION} animation={animationStyle} + // eslint-disable-next-line react-compiler/react-compiler useNativeDriver={useNativeDriver} style={style} > diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index 450a49403215..f0c5e29bc3ba 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -1,5 +1,5 @@ import {Str} from 'expensify-common'; -import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {Animated, Keyboard, View} from 'react-native'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; import {withOnyx} from 'react-native-onyx'; @@ -28,6 +28,7 @@ import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type ModalType from '@src/types/utils/ModalType'; +import viewRef from '@src/types/utils/viewRef'; import AttachmentCarousel from './Attachments/AttachmentCarousel'; import AttachmentCarouselPagerContext from './Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import AttachmentView from './Attachments/AttachmentView'; @@ -265,7 +266,7 @@ function AttachmentModal({ } setIsModalOpen(false); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [isModalOpen, isConfirmButtonDisabled, onConfirm, file, sourceState]); /** @@ -367,7 +368,7 @@ function AttachmentModal({ onModalClose(); } - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [onModalClose]); /** @@ -428,7 +429,7 @@ function AttachmentModal({ }); } return menuItems; - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [isReceiptAttachment, transaction, file, sourceState, iouType]); // There are a few things that shouldn't be set until we absolutely know if the file is a receipt or an attachment. @@ -455,6 +456,8 @@ function AttachmentModal({ [closeModal, nope, sourceForAttachmentView], ); + const submitRef = useRef(null); + return ( <> { + if (!submitRef.current) { + return false; + } + return submitRef.current; + }} > {shouldUseNarrowLayout && } @@ -529,6 +538,7 @@ function AttachmentModal({ fallbackSource={fallbackSource} isUsedInAttachmentModal transactionID={transaction?.transactionID} + isUploaded={!isEmptyObject(report)} /> ) @@ -540,6 +550,7 @@ function AttachmentModal({ {({safeAreaPaddingBottomStyle}) => ( + )} + + + + + ); +} + +EmptyStateComponent.displayName = 'EmptyStateComponent'; +export default EmptyStateComponent; diff --git a/src/components/EmptyStateComponent/types.ts b/src/components/EmptyStateComponent/types.ts new file mode 100644 index 000000000000..96a60fa98513 --- /dev/null +++ b/src/components/EmptyStateComponent/types.ts @@ -0,0 +1,42 @@ +import type {ImageStyle} from 'expo-image'; +import type {StyleProp, ViewStyle} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import type DotLottieAnimation from '@components/LottieAnimations/types'; +import type SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton'; +import type TableRowSkeleton from '@components/Skeletons/TableRowSkeleton'; +import type CONST from '@src/CONST'; +import type IconAsset from '@src/types/utils/IconAsset'; + +type ValidSkeletons = typeof SearchRowSkeleton | typeof TableRowSkeleton; +type MediaTypes = ValueOf; + +type SharedProps = { + SkeletonComponent: ValidSkeletons; + title: string; + subtitle: string; + buttonText?: string; + buttonAction?: () => void; + headerStyles?: StyleProp; + headerMediaType: T; + headerContentStyles?: StyleProp; + emptyStateContentStyles?: StyleProp; +}; + +type MediaType = SharedProps & { + headerMedia: HeaderMedia; +}; + +type VideoProps = MediaType; +type IllustrationProps = MediaType; +type AnimationProps = MediaType; + +type EmptyStateComponentProps = VideoProps | IllustrationProps | AnimationProps; + +type VideoLoadedEventType = { + srcElement: { + videoWidth: number; + videoHeight: number; + }; +}; + +export type {EmptyStateComponentProps, VideoLoadedEventType}; diff --git a/src/components/ErrorMessageRow.tsx b/src/components/ErrorMessageRow.tsx new file mode 100644 index 000000000000..2e6e41449274 --- /dev/null +++ b/src/components/ErrorMessageRow.tsx @@ -0,0 +1,43 @@ +import {mapValues} from 'lodash'; +import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; +import type {ReceiptError, ReceiptErrors} from '@src/types/onyx/Transaction'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import MessagesRow from './MessagesRow'; + +type ErrorMessageRowProps = { + /** The errors to display */ + errors?: OnyxCommon.Errors | ReceiptErrors | null; + + /** Additional style object for the error row */ + errorRowStyles?: StyleProp; + + /** A function to run when the X button next to the error is clicked */ + onClose?: () => void; + + /** Whether we can dismiss the error message */ + canDismissError?: boolean; +}; + +function ErrorMessageRow({errors, errorRowStyles, onClose, canDismissError = true}: ErrorMessageRowProps) { + // Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages. + const errorEntries = Object.entries(errors ?? {}); + const filteredErrorEntries = errorEntries.filter((errorEntry): errorEntry is [string, string | ReceiptError] => errorEntry[1] !== null); + const errorMessages = mapValues(Object.fromEntries(filteredErrorEntries), (error) => error); + const hasErrorMessages = !isEmptyObject(errorMessages); + + return hasErrorMessages ? ( + + ) : null; +} + +ErrorMessageRow.displayName = 'ErrorMessageRow'; + +export default ErrorMessageRow; diff --git a/src/components/FlatList/index.android.tsx b/src/components/FlatList/index.android.tsx index 1246367d29e8..c8ce7ee10d6b 100644 --- a/src/components/FlatList/index.android.tsx +++ b/src/components/FlatList/index.android.tsx @@ -22,7 +22,7 @@ function CustomFlatList(props: FlatListProps, ref: ForwardedRef) } }, [scrollPosition?.offset, ref]); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps const onMomentumScrollEnd = useCallback((event: NativeSyntheticEvent) => setScrollPosition({offset: event.nativeEvent.contentOffset.y}), []); useFocusEffect( diff --git a/src/components/FlatList/index.tsx b/src/components/FlatList/index.tsx index d3e0459a11bb..b45a8418d9a3 100644 --- a/src/components/FlatList/index.tsx +++ b/src/components/FlatList/index.tsx @@ -150,10 +150,13 @@ function MVCPFlatList({maintainVisibleContentPosition, horizontal = false if (!isListRenderedRef.current) { return; } - requestAnimationFrame(() => { + const animationFrame = requestAnimationFrame(() => { prepareForMaintainVisibleContentPosition(); setupMutationObserver(); }); + return () => { + cancelAnimationFrame(animationFrame); + }; }, [prepareForMaintainVisibleContentPosition, setupMutationObserver]); const setMergedRef = useMergeRefs(scrollRef, ref); @@ -176,6 +179,7 @@ function MVCPFlatList({maintainVisibleContentPosition, horizontal = false const mutationObserver = mutationObserverRef.current; return () => { mutationObserver?.disconnect(); + mutationObserverRef.current = null; }; }, []); @@ -199,6 +203,10 @@ function MVCPFlatList({maintainVisibleContentPosition, horizontal = false ref={onRef} onLayout={(e) => { isListRenderedRef.current = true; + if (!mutationObserverRef.current) { + prepareForMaintainVisibleContentPosition(); + setupMutationObserver(); + } props.onLayout?.(e); }} /> diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 93ffa52bc80b..7c2f5579332a 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -63,6 +63,7 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo const buttonRef = ref; useEffect(() => { + // eslint-disable-next-line react-compiler/react-compiler sharedValue.value = withTiming(isActive ? 1 : 0, { duration: 340, easing: Easing.inOut(Easing.ease), diff --git a/src/components/FocusTrap/BOTTOM_TAB_SCREENS.ts b/src/components/FocusTrap/BOTTOM_TAB_SCREENS.ts index e6af466b12c5..b472b80e5cbd 100644 --- a/src/components/FocusTrap/BOTTOM_TAB_SCREENS.ts +++ b/src/components/FocusTrap/BOTTOM_TAB_SCREENS.ts @@ -1,6 +1,6 @@ -import type {BottomTabName} from '@libs/Navigation/types'; +import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; -const BOTTOM_TAB_SCREENS: BottomTabName[] = [SCREENS.HOME, SCREENS.SETTINGS.ROOT]; +const BOTTOM_TAB_SCREENS = [SCREENS.HOME, SCREENS.SETTINGS.ROOT, NAVIGATORS.BOTTOM_TAB_NAVIGATOR]; export default BOTTOM_TAB_SCREENS; diff --git a/src/components/FocusTrap/FocusTrapForModal/FocusTrapForModalProps.ts b/src/components/FocusTrap/FocusTrapForModal/FocusTrapForModalProps.ts index 6bc2350a6c55..5793885dacd1 100644 --- a/src/components/FocusTrap/FocusTrapForModal/FocusTrapForModalProps.ts +++ b/src/components/FocusTrap/FocusTrapForModal/FocusTrapForModalProps.ts @@ -1,6 +1,11 @@ +import type FocusTrap from 'focus-trap-react'; + +type FocusTrapOptions = Exclude; + type FocusTrapForModalProps = { children: React.ReactNode; active: boolean; + initialFocus?: FocusTrapOptions['initialFocus']; }; export default FocusTrapForModalProps; diff --git a/src/components/FocusTrap/FocusTrapForModal/index.web.tsx b/src/components/FocusTrap/FocusTrapForModal/index.web.tsx index be5da8c49a78..2608c58a4d23 100644 --- a/src/components/FocusTrap/FocusTrapForModal/index.web.tsx +++ b/src/components/FocusTrap/FocusTrapForModal/index.web.tsx @@ -1,17 +1,24 @@ import FocusTrap from 'focus-trap-react'; import React from 'react'; import sharedTrapStack from '@components/FocusTrap/sharedTrapStack'; +import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import type FocusTrapForModalProps from './FocusTrapForModalProps'; -function FocusTrapForModal({children, active}: FocusTrapForModalProps) { +function FocusTrapForModal({children, active, initialFocus = false}: FocusTrapForModalProps) { return ( { + if (ReportActionComposeFocusManager.isFocused()) { + return false; + } + return element; + }, }} > {children} diff --git a/src/components/FocusTrap/FocusTrapForScreen/index.web.tsx b/src/components/FocusTrap/FocusTrapForScreen/index.web.tsx index d81293729b94..e7fe135c952c 100644 --- a/src/components/FocusTrap/FocusTrapForScreen/index.web.tsx +++ b/src/components/FocusTrap/FocusTrapForScreen/index.web.tsx @@ -1,23 +1,18 @@ -import {useFocusEffect, useIsFocused, useRoute} from '@react-navigation/native'; +import {useIsFocused, useRoute} from '@react-navigation/native'; import FocusTrap from 'focus-trap-react'; -import React, {useCallback, useMemo} from 'react'; -import {useOnyx} from 'react-native-onyx'; +import React, {useMemo} from 'react'; import BOTTOM_TAB_SCREENS from '@components/FocusTrap/BOTTOM_TAB_SCREENS'; -import getScreenWithAutofocus from '@components/FocusTrap/SCREENS_WITH_AUTOFOCUS'; import sharedTrapStack from '@components/FocusTrap/sharedTrapStack'; import TOP_TAB_SCREENS from '@components/FocusTrap/TOP_TAB_SCREENS'; import WIDE_LAYOUT_INACTIVE_SCREENS from '@components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import ONYXKEYS from '@src/ONYXKEYS'; +import CONST from '@src/CONST'; import type FocusTrapProps from './FocusTrapProps'; -let activeRouteName = ''; function FocusTrapForScreen({children}: FocusTrapProps) { const isFocused = useIsFocused(); const route = useRoute(); const {isSmallScreenWidth} = useWindowDimensions(); - const [isAuthenticated] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => !!session?.authToken}); - const screensWithAutofocus = useMemo(() => getScreenWithAutofocus(!!isAuthenticated), [isAuthenticated]); const isActive = useMemo(() => { // Focus trap can't be active on bottom tab screens because it would block access to the tab bar. @@ -37,12 +32,6 @@ function FocusTrapForScreen({children}: FocusTrapProps) { return true; }, [isFocused, isSmallScreenWidth, route.name]); - useFocusEffect( - useCallback(() => { - activeRouteName = route.name; - }, [route]), - ); - return ( { - if (screensWithAutofocus.includes(activeRouteName)) { + delayInitialFocus: CONST.ANIMATED_TRANSITION, + initialFocus: (focusTrapContainers) => { + const isFocusedElementInsideContainer = focusTrapContainers?.some((container) => container.contains(document.activeElement)); + if (isFocusedElementInsideContainer) { return false; } return undefined; }, setReturnFocus: (element) => { - if (screensWithAutofocus.includes(activeRouteName)) { + if (document.activeElement && document.activeElement !== document.body) { return false; } return element; diff --git a/src/components/FocusTrap/SCREENS_WITH_AUTOFOCUS.ts b/src/components/FocusTrap/SCREENS_WITH_AUTOFOCUS.ts deleted file mode 100644 index 8c016032e143..000000000000 --- a/src/components/FocusTrap/SCREENS_WITH_AUTOFOCUS.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {CENTRAL_PANE_WORKSPACE_SCREENS} from '@libs/Navigation/AppNavigator/Navigators/FullScreenNavigator'; -import NAVIGATORS from '@src/NAVIGATORS'; -import SCREENS from '@src/SCREENS'; - -const SCREENS_WITH_AUTOFOCUS: string[] = [ - ...Object.keys(CENTRAL_PANE_WORKSPACE_SCREENS), - SCREENS.REPORT, - SCREENS.REPORT_DESCRIPTION_ROOT, - SCREENS.PRIVATE_NOTES.EDIT, - SCREENS.SETTINGS.PROFILE.STATUS, - SCREENS.SETTINGS.PROFILE.PRONOUNS, - SCREENS.REPORT_SETTINGS.ROOT, - SCREENS.REPORT_SETTINGS.NOTIFICATION_PREFERENCES, - SCREENS.REPORT_PARTICIPANTS.ROOT, - SCREENS.ROOM_MEMBERS_ROOT, - SCREENS.NEW_TASK.DETAILS, - SCREENS.MONEY_REQUEST.CREATE, - SCREENS.SIGN_IN_ROOT, -]; - -function getScreenWithAutofocus(isAuthenticated: boolean) { - if (!isAuthenticated) { - return [...SCREENS_WITH_AUTOFOCUS, NAVIGATORS.BOTTOM_TAB_NAVIGATOR]; - } - return SCREENS_WITH_AUTOFOCUS; -} - -export default getScreenWithAutofocus; diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 9df94e4c6114..793154d95b02 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -73,6 +73,9 @@ type FormProviderProps = FormProvider /** Whether to apply flex to the submit button */ submitFlexEnabled?: boolean; + + /** Whether button is disabled */ + isSubmitDisabled?: boolean; }; function FormProvider( @@ -176,7 +179,7 @@ function FormProvider( onValidate(trimmedStringValues, !hasServerError); // Only run when locales change - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [preferredLocale]); /** @param inputID - The inputID of the input being touched */ @@ -239,6 +242,7 @@ function FormProvider( inputRefs.current[inputID] = newRef; } if (inputProps.value !== undefined) { + // eslint-disable-next-line react-compiler/react-compiler inputValues[inputID] = inputProps.value; } else if (inputProps.shouldSaveDraft && draftValues?.[inputID] !== undefined && inputValues[inputID] === undefined) { inputValues[inputID] = draftValues[inputID]; diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index 5c74fd466a15..77ef44343792 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -38,6 +38,9 @@ type FormWrapperProps = ChildrenProps & /** Assuming refs are React refs */ inputRefs: RefObject; + /** Whether the submit button is disabled */ + isSubmitDisabled?: boolean; + /** Callback to submit the form */ onSubmit: () => void; }; @@ -57,9 +60,11 @@ function FormWrapper({ enabledWhenOffline, isSubmitActionDangerous = false, formID, + shouldUseScrollView = true, scrollContextEnabled = false, shouldHideFixErrorsAlert = false, disablePressOnEnter = true, + isSubmitDisabled = false, }: FormWrapperProps) { const styles = useThemeStyles(); const formRef = useRef(null); @@ -108,6 +113,7 @@ function FormWrapper({ {isSubmitButtonVisible && ( {({safeAreaPaddingBottomStyle}) => diff --git a/src/components/Form/InputWrapper.tsx b/src/components/Form/InputWrapper.tsx index b5535a2fe6c1..c966dd4456e9 100644 --- a/src/components/Form/InputWrapper.tsx +++ b/src/components/Form/InputWrapper.tsx @@ -54,7 +54,7 @@ function computeComponentSpecificRegistrationParams({ shouldSetTouchedOnBlurOnly: false, // Forward the originally provided value blurOnSubmit, - shouldSubmitForm: false, + shouldSubmitForm: !!shouldSubmitForm, }; } diff --git a/src/components/Form/SafariFormWrapper.tsx b/src/components/Form/SafariFormWrapper.tsx new file mode 100644 index 000000000000..8ad411e547be --- /dev/null +++ b/src/components/Form/SafariFormWrapper.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import {isSafari} from '@libs/Browser'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; + +type SafariFormWrapperProps = ChildrenProps; + +/** + * If we used any without
      wrapper, Safari 11+ would show the auto-fill suggestion popup. + */ +function SafariFormWrapper({children}: SafariFormWrapperProps) { + if (isSafari()) { + return {children}
      ; + } + + return children; +} + +export default SafariFormWrapper; diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index afbe2bb124b5..5f56bbeceea6 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -20,6 +20,10 @@ import type TextInput from '@components/TextInput'; import type TextPicker from '@components/TextPicker'; import type ValuePicker from '@components/ValuePicker'; import type BusinessTypePicker from '@pages/ReimbursementAccount/BusinessInfo/substeps/TypeBusiness/BusinessTypePicker'; +import type DimensionTypeSelector from '@pages/workspace/accounting/intacct/import/DimensionTypeSelector'; +import type NetSuiteCustomFieldMappingPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomFieldMappingPicker'; +import type NetSuiteCustomListPicker from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListPicker'; +import type NetSuiteMenuWithTopDescriptionForm from '@pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteMenuWithTopDescriptionForm'; import type {Country} from '@src/CONST'; import type {OnyxFormKey, OnyxValues} from '@src/ONYXKEYS'; import type {BaseForm} from '@src/types/form/Form'; @@ -39,6 +43,7 @@ type ValidInputs = | typeof CurrencySelector | typeof AmountForm | typeof BusinessTypePicker + | typeof DimensionTypeSelector | typeof StateSelector | typeof RoomNameInput | typeof ValuePicker @@ -47,7 +52,10 @@ type ValidInputs = | typeof AmountPicker | typeof TextPicker | typeof AddPlaidBankAccount - | typeof EmojiPickerButtonDropdown; + | typeof EmojiPickerButtonDropdown + | typeof NetSuiteCustomListPicker + | typeof NetSuiteCustomFieldMappingPicker + | typeof NetSuiteMenuWithTopDescriptionForm; type ValueTypeKey = 'string' | 'boolean' | 'date' | 'country' | 'reportFields' | 'disabledListValues'; type ValueTypeMap = { @@ -126,6 +134,9 @@ type FormProps = { /** Whether ScrollWithContext should be used instead of regular ScrollView. Set to true when there's a nested Picker component in Form. */ scrollContextEnabled?: boolean; + /** Whether to use ScrollView */ + shouldUseScrollView?: boolean; + /** Container styles */ style?: StyleProp; diff --git a/src/components/FormHelpMessage.tsx b/src/components/FormHelpMessage.tsx index 01a5a1eaf3a8..92cdc658b2d7 100644 --- a/src/components/FormHelpMessage.tsx +++ b/src/components/FormHelpMessage.tsx @@ -10,7 +10,7 @@ import Text from './Text'; type FormHelpMessageProps = { /** Error or hint text. Ignored when children is not empty */ - message?: string; + message?: string | React.ReactNode; /** Children to render next to dot indicator */ children?: React.ReactNode; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 67bbc7986bc7..1e73cce1630f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -78,7 +78,7 @@ function ImageRenderer({tnode}: ImageRendererProps) { thumbnailImageComponent ) : ( - {({anchor, report, action, checkIfContextMenuActive}) => ( + {({anchor, report, reportNameValuePairs, action, checkIfContextMenuActive}) => ( {({reportID, accountID, type}) => ( showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => + showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)) + } shouldUseHapticsOnLongPress accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={translate('accessibilityHints.viewAttachment')} diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index e5481c5d9094..ffab2434c83c 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -83,10 +83,12 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona return ( - {({anchor, report, action, checkIfContextMenuActive}) => ( + {({anchor, report, reportNameValuePairs, action, checkIfContextMenuActive}) => ( showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => + showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)) + } onPress={(event) => { event.preventDefault(); Navigation.navigate(navigationRoute); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx index 4d1e58a42830..14666798e8c7 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx @@ -34,12 +34,14 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d return ( - {({anchor, report, action, checkIfContextMenuActive}) => ( + {({anchor, report, reportNameValuePairs, action, checkIfContextMenuActive}) => ( {})} onPressIn={onPressIn} onPressOut={onPressOut} - onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => + showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)) + } shouldUseHapticsOnLongPress role={CONST.ROLE.PRESENTATION} accessibilityLabel={translate('accessibilityHints.prestyledText')} diff --git a/src/components/HybridAppMiddleware.tsx b/src/components/HybridAppMiddleware.tsx deleted file mode 100644 index 5c6934f4fc3d..000000000000 --- a/src/components/HybridAppMiddleware.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import {useNavigation} from '@react-navigation/native'; -import type {StackNavigationProp} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import {NativeModules} from 'react-native'; -import useSplashScreen from '@hooks/useSplashScreen'; -import BootSplash from '@libs/BootSplash'; -import Log from '@libs/Log'; -import Navigation from '@libs/Navigation/Navigation'; -import type {RootStackParamList} from '@libs/Navigation/types'; -import * as Welcome from '@userActions/Welcome'; -import CONST from '@src/CONST'; -import type {Route} from '@src/ROUTES'; - -type HybridAppMiddlewareProps = { - children: React.ReactNode; -}; - -type HybridAppMiddlewareContextType = { - navigateToExitUrl: (exitUrl: Route) => void; - showSplashScreenOnNextStart: () => void; -}; -const HybridAppMiddlewareContext = React.createContext({ - navigateToExitUrl: () => {}, - showSplashScreenOnNextStart: () => {}, -}); - -/* - * HybridAppMiddleware is responsible for handling BootSplash visibility correctly. - * It is crucial to make transitions between OldDot and NewDot look smooth. - */ -function HybridAppMiddleware(props: HybridAppMiddlewareProps) { - const {isSplashHidden, setIsSplashHidden} = useSplashScreen(); - const [startedTransition, setStartedTransition] = useState(false); - const [finishedTransition, setFinishedTransition] = useState(false); - const navigation = useNavigation>(); - - /* - * Handles navigation during transition from OldDot. For ordinary NewDot app it is just pure navigation. - */ - const navigateToExitUrl = useCallback((exitUrl: Route) => { - if (NativeModules.HybridAppModule) { - setStartedTransition(true); - Log.info(`[HybridApp] Started transition to ${exitUrl}`, true); - } - - Navigation.navigate(exitUrl); - }, []); - - /** - * This function only affects iOS. If during a single app lifecycle we frequently transition from OldDot to NewDot, - * we need to artificially show the bootsplash because the app is only booted once. - */ - const showSplashScreenOnNextStart = useCallback(() => { - setIsSplashHidden(false); - setStartedTransition(false); - setFinishedTransition(false); - }, [setIsSplashHidden]); - - useEffect(() => { - if (!finishedTransition || isSplashHidden) { - return; - } - - Log.info('[HybridApp] Finished transtion', true); - BootSplash.hide().then(() => { - setIsSplashHidden(true); - Log.info('[HybridApp] Handling onboarding flow', true); - Welcome.handleHybridAppOnboarding(); - }); - }, [finishedTransition, isSplashHidden, setIsSplashHidden]); - - useEffect(() => { - if (!startedTransition) { - return; - } - - // On iOS, the transitionEnd event doesn't trigger some times. As such, we need to set a timeout. - const timeout = setTimeout(() => { - setFinishedTransition(true); - }, CONST.SCREEN_TRANSITION_END_TIMEOUT); - - const unsubscribeTransitionEnd = navigation.addListener('transitionEnd', () => { - clearTimeout(timeout); - setFinishedTransition(true); - }); - - return () => { - clearTimeout(timeout); - unsubscribeTransitionEnd(); - }; - }, [navigation, startedTransition]); - - const contextValue = useMemo( - () => ({ - navigateToExitUrl, - showSplashScreenOnNextStart, - }), - [navigateToExitUrl, showSplashScreenOnNextStart], - ); - - return {props.children}; -} - -HybridAppMiddleware.displayName = 'HybridAppMiddleware'; - -export default HybridAppMiddleware; -export type {HybridAppMiddlewareContextType}; -export {HybridAppMiddlewareContext}; diff --git a/src/components/HybridAppMiddleware/index.ios.tsx b/src/components/HybridAppMiddleware/index.ios.tsx new file mode 100644 index 000000000000..1ea8e62ab17e --- /dev/null +++ b/src/components/HybridAppMiddleware/index.ios.tsx @@ -0,0 +1,161 @@ +import type React from 'react'; +import {useContext, useEffect, useState} from 'react'; +import {NativeEventEmitter, NativeModules} from 'react-native'; +import type {NativeModule} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {InitialURLContext} from '@components/InitialURLContextProvider'; +import useExitTo from '@hooks/useExitTo'; +import useSplashScreen from '@hooks/useSplashScreen'; +import BootSplash from '@libs/BootSplash'; +import Log from '@libs/Log'; +import Navigation from '@libs/Navigation/Navigation'; +import * as SessionUtils from '@libs/SessionUtils'; +import * as Welcome from '@userActions/Welcome'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {HybridAppRoute, Route} from '@src/ROUTES'; +import type {TryNewDot} from '@src/types/onyx'; + +type HybridAppMiddlewareProps = { + authenticated: boolean; + children: React.ReactNode; +}; + +const onboardingStatusSelector = (tryNewDot: OnyxEntry) => { + let completedHybridAppOnboarding = tryNewDot?.classicRedirect?.completedHybridAppOnboarding; + + if (typeof completedHybridAppOnboarding === 'string') { + completedHybridAppOnboarding = completedHybridAppOnboarding === 'true'; + } + + return completedHybridAppOnboarding; +}; + +/* + * HybridAppMiddleware is responsible for handling BootSplash visibility correctly. + * It is crucial to make transitions between OldDot and NewDot look smooth. + * The middleware assumes that the entry point for HybridApp is the /transition route. + */ +function HybridAppMiddleware({children, authenticated}: HybridAppMiddlewareProps) { + const {isSplashHidden, setIsSplashHidden} = useSplashScreen(); + const [startedTransition, setStartedTransition] = useState(false); + const [finishedTransition, setFinishedTransition] = useState(false); + + const initialURL = useContext(InitialURLContext); + const exitToParam = useExitTo(); + const [exitTo, setExitTo] = useState(); + + const [isAccountLoading] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.isLoading ?? false}); + const [sessionEmail] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email}); + const [completedHybridAppOnboarding] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT, {selector: onboardingStatusSelector}); + + /** + * This useEffect tracks changes of `nvp_tryNewDot` value. + * We propagate it from OldDot to NewDot with native method due to limitations of old app. + */ + useEffect(() => { + if (completedHybridAppOnboarding === undefined) { + return; + } + + if (!NativeModules.HybridAppModule) { + Log.hmmm(`[HybridApp] Onboarding status has changed, but the HybridAppModule is not defined`); + return; + } + + Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding}); + NativeModules.HybridAppModule.completeOnboarding(completedHybridAppOnboarding); + }, [completedHybridAppOnboarding]); + + // In iOS, the HybridApp defines the `onReturnToOldDot` event. + // If we frequently transition from OldDot to NewDot during a single app lifecycle, + // we need to artificially display the bootsplash since the app is booted only once. + // Therefore, isSplashHidden needs to be updated at the appropriate time. + useEffect(() => { + if (!NativeModules.HybridAppModule) { + return; + } + const HybridAppEvents = new NativeEventEmitter(NativeModules.HybridAppModule as unknown as NativeModule); + const listener = HybridAppEvents.addListener(CONST.EVENTS.ON_RETURN_TO_OLD_DOT, () => { + Log.info('[HybridApp] `onReturnToOldDot` event received. Resetting state of HybridAppMiddleware', true); + setIsSplashHidden(false); + setStartedTransition(false); + setFinishedTransition(false); + setExitTo(undefined); + }); + + return () => { + listener.remove(); + }; + }, [setIsSplashHidden]); + + // Save `exitTo` when we reach /transition route. + // `exitTo` should always exist during OldDot -> NewDot transitions. + useEffect(() => { + if (!NativeModules.HybridAppModule || !exitToParam || exitTo) { + return; + } + + Log.info('[HybridApp] Saving `exitTo` for later', true, {exitTo: exitToParam}); + setExitTo(exitToParam); + + Log.info(`[HybridApp] Started transition`, true); + setStartedTransition(true); + }, [exitTo, exitToParam]); + + useEffect(() => { + if (!startedTransition || finishedTransition) { + return; + } + + const transitionURL = NativeModules.HybridAppModule ? `${CONST.DEEPLINK_BASE_URL}${initialURL ?? ''}` : initialURL; + const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); + + // We need to wait with navigating to exitTo until all login-related actions are complete. + if (!authenticated || isLoggingInAsNewUser || isAccountLoading) { + return; + } + + if (exitTo) { + Navigation.isNavigationReady().then(() => { + // We need to remove /transition from route history. + // `useExitTo` returns undefined for routes other than /transition. + if (exitToParam) { + Log.info('[HybridApp] Removing /transition route from history', true); + Navigation.goBack(); + } + + Log.info('[HybridApp] Navigating to `exitTo` route', true, {exitTo}); + Navigation.navigate(Navigation.parseHybridAppUrl(exitTo)); + setExitTo(undefined); + + setTimeout(() => { + Log.info('[HybridApp] Setting `finishedTransition` to true', true); + setFinishedTransition(true); + }, CONST.SCREEN_TRANSITION_END_TIMEOUT); + }); + } + }, [authenticated, exitTo, exitToParam, finishedTransition, initialURL, isAccountLoading, sessionEmail, startedTransition]); + + useEffect(() => { + if (!finishedTransition || isSplashHidden) { + return; + } + + Log.info('[HybridApp] Finished transition, hiding BootSplash', true); + BootSplash.hide().then(() => { + setIsSplashHidden(true); + if (authenticated) { + Log.info('[HybridApp] Handling onboarding flow', true); + Welcome.handleHybridAppOnboarding(); + } + }); + }, [authenticated, finishedTransition, isSplashHidden, setIsSplashHidden]); + + return children; +} + +HybridAppMiddleware.displayName = 'HybridAppMiddleware'; + +export default HybridAppMiddleware; diff --git a/src/components/HybridAppMiddleware/index.tsx b/src/components/HybridAppMiddleware/index.tsx new file mode 100644 index 000000000000..5a8d8d6dfebe --- /dev/null +++ b/src/components/HybridAppMiddleware/index.tsx @@ -0,0 +1,133 @@ +import type React from 'react'; +import {useContext, useEffect, useState} from 'react'; +import {NativeModules} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {InitialURLContext} from '@components/InitialURLContextProvider'; +import useExitTo from '@hooks/useExitTo'; +import useSplashScreen from '@hooks/useSplashScreen'; +import BootSplash from '@libs/BootSplash'; +import Log from '@libs/Log'; +import Navigation from '@libs/Navigation/Navigation'; +import * as SessionUtils from '@libs/SessionUtils'; +import * as Welcome from '@userActions/Welcome'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {HybridAppRoute, Route} from '@src/ROUTES'; +import type {TryNewDot} from '@src/types/onyx'; + +type HybridAppMiddlewareProps = { + authenticated: boolean; + children: React.ReactNode; +}; + +const onboardingStatusSelector = (tryNewDot: OnyxEntry) => { + let completedHybridAppOnboarding = tryNewDot?.classicRedirect?.completedHybridAppOnboarding; + + if (typeof completedHybridAppOnboarding === 'string') { + completedHybridAppOnboarding = completedHybridAppOnboarding === 'true'; + } + + return completedHybridAppOnboarding; +}; + +/* + * HybridAppMiddleware is responsible for handling BootSplash visibility correctly. + * It is crucial to make transitions between OldDot and NewDot look smooth. + * The middleware assumes that the entry point for HybridApp is the /transition route. + */ +function HybridAppMiddleware({children, authenticated}: HybridAppMiddlewareProps) { + const {isSplashHidden, setIsSplashHidden} = useSplashScreen(); + const [startedTransition, setStartedTransition] = useState(false); + const [finishedTransition, setFinishedTransition] = useState(false); + + const initialURL = useContext(InitialURLContext); + const exitToParam = useExitTo(); + const [exitTo, setExitTo] = useState(); + + const [isAccountLoading] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.isLoading ?? false}); + const [sessionEmail] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email}); + const [completedHybridAppOnboarding] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT, {selector: onboardingStatusSelector}); + + /** + * This useEffect tracks changes of `nvp_tryNewDot` value. + * We propagate it from OldDot to NewDot with native method due to limitations of old app. + */ + useEffect(() => { + if (completedHybridAppOnboarding === undefined || !NativeModules.HybridAppModule) { + return; + } + + Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding}); + NativeModules.HybridAppModule.completeOnboarding(completedHybridAppOnboarding); + }, [completedHybridAppOnboarding]); + + // Save `exitTo` when we reach /transition route. + // `exitTo` should always exist during OldDot -> NewDot transitions. + useEffect(() => { + if (!NativeModules.HybridAppModule || !exitToParam || exitTo) { + return; + } + + Log.info('[HybridApp] Saving `exitTo` for later', true, {exitTo: exitToParam}); + setExitTo(exitToParam); + + Log.info(`[HybridApp] Started transition`, true); + setStartedTransition(true); + }, [exitTo, exitToParam]); + + useEffect(() => { + if (!startedTransition || finishedTransition) { + return; + } + + const transitionURL = NativeModules.HybridAppModule ? `${CONST.DEEPLINK_BASE_URL}${initialURL ?? ''}` : initialURL; + const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); + + // We need to wait with navigating to exitTo until all login-related actions are complete. + if (!authenticated || isLoggingInAsNewUser || isAccountLoading) { + return; + } + + if (exitTo) { + Navigation.isNavigationReady().then(() => { + // We need to remove /transition from route history. + // `useExitTo` returns undefined for routes other than /transition. + if (exitToParam) { + Log.info('[HybridApp] Removing /transition route from history', true); + Navigation.goBack(); + } + + Log.info('[HybridApp] Navigating to `exitTo` route', true, {exitTo}); + Navigation.navigate(Navigation.parseHybridAppUrl(exitTo)); + setExitTo(undefined); + + setTimeout(() => { + Log.info('[HybridApp] Setting `finishedTransition` to true', true); + setFinishedTransition(true); + }, CONST.SCREEN_TRANSITION_END_TIMEOUT); + }); + } + }, [authenticated, exitTo, exitToParam, finishedTransition, initialURL, isAccountLoading, sessionEmail, startedTransition]); + + useEffect(() => { + if (!finishedTransition || isSplashHidden) { + return; + } + + Log.info('[HybridApp] Finished transition, hiding BootSplash', true); + BootSplash.hide().then(() => { + setIsSplashHidden(true); + if (authenticated) { + Log.info('[HybridApp] Handling onboarding flow', true); + Welcome.handleHybridAppOnboarding(); + } + }); + }, [authenticated, finishedTransition, isSplashHidden, setIsSplashHidden]); + + return children; +} + +HybridAppMiddleware.displayName = 'HybridAppMiddleware'; + +export default HybridAppMiddleware; diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index bd0824372799..ccbf4b3a5da9 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -1,4 +1,6 @@ +import EmptyCardState from '@assets/images/emptystate__expensifycard.svg'; import ExpensifyCardIllustration from '@assets/images/expensifyCard/cardIllustration.svg'; +import LaptopwithSecondScreenandHourglass from '@assets/images/LaptopwithSecondScreenandHourglass.svg'; import Abracadabra from '@assets/images/product-illustrations/abracadabra.svg'; import BankArrowPink from '@assets/images/product-illustrations/bank-arrow--pink.svg'; import BankMouseGreen from '@assets/images/product-illustrations/bank-mouse--green.svg'; @@ -8,6 +10,7 @@ import ConciergeExclamation from '@assets/images/product-illustrations/concierge import CreditCardsBlue from '@assets/images/product-illustrations/credit-cards--blue.svg'; import EmptyStateExpenses from '@assets/images/product-illustrations/emptystate__expenses.svg'; import EmptyStateTravel from '@assets/images/product-illustrations/emptystate__travel.svg'; +import FolderWithPapers from '@assets/images/product-illustrations/folder-with-papers.svg'; import GpsTrackOrange from '@assets/images/product-illustrations/gps-track--orange.svg'; import Hands from '@assets/images/product-illustrations/home-illustration-hands.svg'; import InvoiceOrange from '@assets/images/product-illustrations/invoice--orange.svg'; @@ -52,6 +55,7 @@ import ConciergeNew from '@assets/images/simple-illustrations/simple-illustratio import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg'; import CreditCardEyes from '@assets/images/simple-illustrations/simple-illustration__creditcardeyes.svg'; import EmailAddress from '@assets/images/simple-illustrations/simple-illustration__email-address.svg'; +import EmptyState from '@assets/images/simple-illustrations/simple-illustration__empty-state.svg'; import FolderOpen from '@assets/images/simple-illustrations/simple-illustration__folder-open.svg'; import Gears from '@assets/images/simple-illustrations/simple-illustration__gears.svg'; import HandCard from '@assets/images/simple-illustrations/simple-illustration__handcard.svg'; @@ -77,6 +81,7 @@ import PiggyBank from '@assets/images/simple-illustrations/simple-illustration__ import Profile from '@assets/images/simple-illustrations/simple-illustration__profile.svg'; import QRCode from '@assets/images/simple-illustrations/simple-illustration__qr-code.svg'; import ReceiptEnvelope from '@assets/images/simple-illustrations/simple-illustration__receipt-envelope.svg'; +import ReceiptLocationMarker from '@assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg'; import ReceiptWrangler from '@assets/images/simple-illustrations/simple-illustration__receipt-wrangler.svg'; import ReceiptUpload from '@assets/images/simple-illustrations/simple-illustration__receiptupload.svg'; import SanFrancisco from '@assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg'; @@ -89,9 +94,11 @@ import SubscriptionPPU from '@assets/images/simple-illustrations/simple-illustra import Tag from '@assets/images/simple-illustrations/simple-illustration__tag.svg'; import TeachersUnite from '@assets/images/simple-illustrations/simple-illustration__teachers-unite.svg'; import ThumbsUpStars from '@assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg'; +import Tire from '@assets/images/simple-illustrations/simple-illustration__tire.svg'; import TrackShoe from '@assets/images/simple-illustrations/simple-illustration__track-shoe.svg'; import TrashCan from '@assets/images/simple-illustrations/simple-illustration__trashcan.svg'; import TreasureChest from '@assets/images/simple-illustrations/simple-illustration__treasurechest.svg'; +import VirtualCard from '@assets/images/simple-illustrations/simple-illustration__virtualcard.svg'; import WalletAlt from '@assets/images/simple-illustrations/simple-illustration__wallet-alt.svg'; import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg'; import ExpensifyApprovedLogoLight from '@assets/images/subscription-details__approvedlogo--light.svg'; @@ -109,6 +116,7 @@ export { ConciergeExclamation, CreditCardsBlue, EmailAddress, + EmptyCardState, EmptyStateExpenses, FolderOpen, HandCard, @@ -141,6 +149,7 @@ export { PinkBill, CreditCardsNew, InvoiceBlue, + LaptopwithSecondScreenandHourglass, LockOpen, Luggage, MoneyIntoWallet, @@ -186,6 +195,7 @@ export { Pencil, Tag, CarIce, + ReceiptLocationMarker, Lightbulb, EmptyStateTravel, SubscriptionAnnual, @@ -196,4 +206,8 @@ export { CheckmarkCircle, CreditCardEyes, LockClosedOrange, + EmptyState, + FolderWithPapers, + VirtualCard, + Tire, }; diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index f3cbc332c995..5fe1ba306400 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -58,7 +58,7 @@ function Image({source: propsSource, isAuthTokenRequired = false, session, onLoa } return propsSource; // The session prop is not required, as it causes the image to reload whenever the session changes. For more information, please refer to issue #26034. - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [propsSource, isAuthTokenRequired]); /** diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 7703b804611a..431a12d00106 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -21,8 +21,8 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; -import {parseHtmlToText} from '@libs/OnyxAwareParser'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportUtils from '@libs/ReportUtils'; @@ -251,7 +251,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti numberOfLines={1} accessibilityLabel={translate('accessibilityHints.lastChatMessagePreview')} > - {parseHtmlToText(optionItem.alternateText)} + {Parser.htmlToText(optionItem.alternateText)} ) : null} diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 8d61058ed5be..2afd9e10b80c 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -36,6 +36,7 @@ function OptionRowLHNData({ const optionItemRef = useRef(); const shouldDisplayViolations = canUseViolations && ReportUtils.shouldDisplayTransactionThreadViolations(fullReport, transactionViolations, parentReportAction); + const shouldDisplayReportViolations = ReportUtils.isReportOwner(fullReport) && ReportUtils.hasReportViolations(reportID); const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! @@ -46,7 +47,8 @@ function OptionRowLHNData({ preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, policy, parentReportAction, - hasViolations: !!shouldDisplayViolations, + hasViolations: !!shouldDisplayViolations || shouldDisplayReportViolations, + transactionViolations, }); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; @@ -57,7 +59,7 @@ function OptionRowLHNData({ return item; // Listen parentReportAction to update title of thread report when parentReportAction changed // Listen to transaction to update title of transaction report when transaction changed - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ fullReport, lastReportActionTransaction, @@ -70,6 +72,7 @@ function OptionRowLHNData({ transactionViolations, canUseViolations, receiptTransactions, + shouldDisplayReportViolations, ]); return ( diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index ea10e104a59d..c5a77f9d5ec4 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -78,7 +78,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan }; } - const foundPage = attachmentCarouselPagerContext.pagerItems.findIndex((item) => item.source === uri); + const foundPage = attachmentCarouselPagerContext.pagerItems.findIndex((item) => item.source === uri || item.previewSource === uri); return { ...attachmentCarouselPagerContext, isUsedInCarousel: !!attachmentCarouselPagerContext.pagerRef, diff --git a/src/components/LocationPermissionModal/index.android.tsx b/src/components/LocationPermissionModal/index.android.tsx new file mode 100644 index 000000000000..811537e00e67 --- /dev/null +++ b/src/components/LocationPermissionModal/index.android.tsx @@ -0,0 +1,90 @@ +import React, {useEffect, useState} from 'react'; +import {Linking} from 'react-native'; +import {RESULTS} from 'react-native-permissions'; +import ConfirmModal from '@components/ConfirmModal'; +import * as Illustrations from '@components/Icon/Illustrations'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {getLocationPermission, requestLocationPermission} from '@pages/iou/request/step/IOURequestStepScan/LocationPermission'; +import type {LocationPermissionModalProps} from './types'; + +function LocationPermissionModal({startPermissionFlow, resetPermissionFlow, onDeny, onGrant}: LocationPermissionModalProps) { + const [hasError, setHasError] = useState(false); + const [showModal, setShowModal] = useState(false); + + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + useEffect(() => { + if (!startPermissionFlow) { + return; + } + + getLocationPermission().then((status) => { + if (status === RESULTS.GRANTED || status === RESULTS.LIMITED) { + return onGrant(); + } + + setShowModal(true); + setHasError(status === RESULTS.BLOCKED); + }); + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- We only want to run this effect when startPermissionFlow changes + }, [startPermissionFlow]); + + const handledBlockedPermission = (cb: () => void) => () => { + if (hasError && Linking.openSettings) { + Linking.openSettings(); + setShowModal(false); + setHasError(false); + resetPermissionFlow(); + return; + } + cb(); + }; + + const grantLocationPermission = handledBlockedPermission(() => { + requestLocationPermission().then((status) => { + if (status === RESULTS.GRANTED || status === RESULTS.LIMITED) { + onGrant(); + } else if (status === RESULTS.BLOCKED) { + setHasError(true); + return; + } else { + onDeny(status); + } + setShowModal(false); + setHasError(false); + }); + }); + + const skipLocationPermission = () => { + onDeny(RESULTS.DENIED); + setShowModal(false); + setHasError(false); + }; + + return ( + + ); +} + +LocationPermissionModal.displayName = 'LocationPermissionModal'; + +export default LocationPermissionModal; diff --git a/src/components/LocationPermissionModal/index.tsx b/src/components/LocationPermissionModal/index.tsx new file mode 100644 index 000000000000..2bc4a7393822 --- /dev/null +++ b/src/components/LocationPermissionModal/index.tsx @@ -0,0 +1,90 @@ +import React, {useEffect, useState} from 'react'; +import {Linking} from 'react-native'; +import {RESULTS} from 'react-native-permissions'; +import ConfirmModal from '@components/ConfirmModal'; +import * as Illustrations from '@components/Icon/Illustrations'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {getLocationPermission, requestLocationPermission} from '@pages/iou/request/step/IOURequestStepScan/LocationPermission'; +import type {LocationPermissionModalProps} from './types'; + +function LocationPermissionModal({startPermissionFlow, resetPermissionFlow, onDeny, onGrant}: LocationPermissionModalProps) { + const [hasError, setHasError] = useState(false); + const [showModal, setShowModal] = useState(false); + + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + useEffect(() => { + if (!startPermissionFlow) { + return; + } + + getLocationPermission().then((status) => { + if (status === RESULTS.GRANTED || status === RESULTS.LIMITED) { + return onGrant(); + } + + setShowModal(true); + setHasError(status === RESULTS.BLOCKED); + }); + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- We only want to run this effect when startPermissionFlow changes + }, [startPermissionFlow]); + + const handledBlockedPermission = (cb: () => void) => () => { + if (hasError && Linking.openSettings) { + Linking.openSettings(); + setShowModal(false); + setHasError(false); + resetPermissionFlow(); + return; + } + cb(); + }; + + const grantLocationPermission = handledBlockedPermission(() => { + requestLocationPermission() + .then((status) => { + if (status === RESULTS.GRANTED || status === RESULTS.LIMITED) { + onGrant(); + } else { + onDeny(status); + } + }) + .finally(() => { + setShowModal(false); + setHasError(false); + }); + }); + + const skipLocationPermission = () => { + onDeny(RESULTS.DENIED); + setShowModal(false); + setHasError(false); + }; + + return ( + + ); +} + +LocationPermissionModal.displayName = 'LocationPermissionModal'; + +export default LocationPermissionModal; diff --git a/src/components/LocationPermissionModal/types.ts b/src/components/LocationPermissionModal/types.ts new file mode 100644 index 000000000000..ec603bfdb8c1 --- /dev/null +++ b/src/components/LocationPermissionModal/types.ts @@ -0,0 +1,19 @@ +import type {PermissionStatus} from 'react-native-permissions'; + +type LocationPermissionModalProps = { + /** A callback to call when the permission has been granted */ + onGrant: () => void; + + /** A callback to call when the permission has been denied */ + onDeny: (permission: PermissionStatus) => void; + + /** Should start the permission flow? */ + startPermissionFlow: boolean; + + /** Reset the permission flow */ + resetPermissionFlow: () => void; +}; + +export default {}; + +export type {LocationPermissionModalProps}; diff --git a/src/components/Lottie/index.tsx b/src/components/Lottie/index.tsx index 6395a715f339..a9b223a87a54 100644 --- a/src/components/Lottie/index.tsx +++ b/src/components/Lottie/index.tsx @@ -1,7 +1,7 @@ -import type {LottieViewProps} from 'lottie-react-native'; +import type {AnimationObject, LottieViewProps} from 'lottie-react-native'; import LottieView from 'lottie-react-native'; import type {ForwardedRef} from 'react'; -import React, {forwardRef} from 'react'; +import React, {forwardRef, useEffect, useState} from 'react'; import {View} from 'react-native'; import type DotLottieAnimation from '@components/LottieAnimations/types'; import useAppState from '@hooks/useAppState'; @@ -19,6 +19,12 @@ function Lottie({source, webStyle, ...props}: Props, ref: ForwardedRef setIsError(false)}); + const [animationFile, setAnimationFile] = useState(); + + useEffect(() => { + setAnimationFile(source.file); + }, [setAnimationFile, source.file]); + const aspectRatioStyle = styles.aspectRatioLottie(source); // If the image fails to load or app is in background state, we'll just render an empty view @@ -28,17 +34,17 @@ function Lottie({source, webStyle, ...props}: Props, ref: ForwardedRef; } - return ( + return animationFile ? ( setIsError(true)} /> - ); + ) : null; } Lottie.displayName = 'Lottie'; diff --git a/src/components/MagicCodeInput.tsx b/src/components/MagicCodeInput.tsx index 956f3ffe5e02..2fae3cc89597 100644 --- a/src/components/MagicCodeInput.tsx +++ b/src/components/MagicCodeInput.tsx @@ -192,7 +192,7 @@ function MagicCodeInput( // We have not added: // + the editIndex as the dependency because we don't want to run this logic after focusing on an input to edit it after the user has completed the code. // + the onFulfill as the dependency because onFulfill is changed when the preferred locale changed => avoid auto submit form when preferred locale changed. - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [value, shouldSubmitOnComplete]); /** @@ -353,7 +353,7 @@ function MagicCodeInput( // We have not added: // + the onChangeText and onKeyPress as the dependencies because we only want to run this when lastPressedDigit changes. - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [lastPressedDigit, isDisableKeyboard]); return ( diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index 283f7c396edb..974f58636977 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -37,6 +37,7 @@ const MapView = forwardRef( const currentPosition = userLocation ?? initialLocation; const [userInteractedWithMap, setUserInteractedWithMap] = useState(false); const shouldInitializeCurrentPosition = useRef(true); + const [isAccessTokenSet, setIsAccessTokenSet] = useState(false); // Determines if map can be panned to user's detected // location without bothering the user. It will return @@ -138,7 +139,12 @@ const MapView = forwardRef( }, [navigation]); useEffect(() => { - setAccessToken(accessToken); + setAccessToken(accessToken).then((token) => { + if (!token) { + return; + } + setIsAccessTokenSet(true); + }); }, [accessToken]); const setMapIdle = (e: MapState) => { @@ -151,7 +157,8 @@ const MapView = forwardRef( } }; const centerMap = useCallback(() => { - if (directionCoordinates && directionCoordinates.length > 1) { + const waypointCoordinates = waypoints?.map((waypoint) => waypoint.coordinate) ?? []; + if (waypointCoordinates.length > 1 || (directionCoordinates ?? []).length > 1) { const {southWest, northEast} = utils.getBounds(waypoints?.map((waypoint) => waypoint.coordinate) ?? [], directionCoordinates); cameraRef.current?.fitBounds(southWest, northEast, mapPadding, CONST.MAPBOX.ANIMATION_DURATION_ON_CENTER_ME); return; @@ -198,7 +205,7 @@ const MapView = forwardRef( const initCenterCoordinate = useMemo(() => (interactive ? centerCoordinate : undefined), [interactive, centerCoordinate]); const initBounds = useMemo(() => (interactive ? undefined : waypointsBounds), [interactive, waypointsBounds]); - return !isOffline && !!accessToken && !!defaultSettings ? ( + return !isOffline && isAccessTokenSet && !!defaultSettings ? ( ( pitchEnabled={pitchEnabled} attributionPosition={{...styles.r2, ...styles.b2}} scaleBarEnabled={false} + // We use scaleBarPosition with top: -32 to hide the scale bar on iOS because scaleBarEnabled={false} not work on iOS + scaleBarPosition={{...styles.tn8, left: 0}} + compassEnabled + compassPosition={{...styles.l2, ...styles.t5}} logoPosition={{...styles.l2, ...styles.b2}} // eslint-disable-next-line react/jsx-props-no-spreading {...responder.panHandlers} diff --git a/src/components/MapView/MapView.website.tsx b/src/components/MapView/MapView.website.tsx index 01d7134a96da..618dd5b24cf5 100644 --- a/src/components/MapView/MapView.website.tsx +++ b/src/components/MapView/MapView.website.tsx @@ -157,7 +157,7 @@ const MapView = forwardRef( resetBoundaries(); setShouldResetBoundaries(false); - // eslint-disable-next-line react-hooks/exhaustive-deps -- this effect only needs to run when the boundaries reset is forced + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- this effect only needs to run when the boundaries reset is forced }, [shouldResetBoundaries]); useEffect(() => { @@ -194,7 +194,8 @@ const MapView = forwardRef( if (!mapRef) { return; } - if (directionCoordinates && directionCoordinates.length > 1) { + const waypointCoordinates = waypoints?.map((waypoint) => waypoint.coordinate) ?? []; + if (waypointCoordinates.length > 1 || (directionCoordinates ?? []).length > 1) { const {northEast, southWest} = utils.getBounds(waypoints?.map((waypoint) => waypoint.coordinate) ?? [], directionCoordinates); const map = mapRef?.getMap(); map?.fitBounds([southWest, northEast], {padding: mapPadding, animate: true, duration: CONST.MAPBOX.ANIMATION_DURATION_ON_CENTER_ME}); diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx index 1142a90c87d1..bdc19316d491 100644 --- a/src/components/MentionSuggestions.tsx +++ b/src/components/MentionSuggestions.tsx @@ -55,6 +55,9 @@ type MentionSuggestionsProps = { /** Measures the parent container's position and dimensions. Also add cursor coordinates */ measureParentContainerAndReportCursor: (callback: MeasureParentContainerAndCursorCallback) => void; + + /** Reset the emoji suggestions */ + resetSuggestions: () => void; }; /** @@ -62,7 +65,15 @@ type MentionSuggestionsProps = { */ const keyExtractor = (item: Mention) => item.alternateText; -function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSelect, isMentionPickerLarge, measureParentContainerAndReportCursor = () => {}}: MentionSuggestionsProps) { +function MentionSuggestions({ + prefix, + mentions, + highlightedMentionIndex = 0, + onSelect, + isMentionPickerLarge, + measureParentContainerAndReportCursor = () => {}, + resetSuggestions, +}: MentionSuggestionsProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -149,6 +160,7 @@ function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSe isSuggestionPickerLarge={isMentionPickerLarge} accessibilityLabelExtractor={keyExtractor} measureParentContainerAndReportCursor={measureParentContainerAndReportCursor} + resetSuggestions={resetSuggestions} /> ); } diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index bf12c95825dc..f7f8a77018ce 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -1,4 +1,3 @@ -import {ExpensiMark} from 'expensify-common'; import type {ImageContentFit} from 'expo-image'; import type {ReactElement, ReactNode} from 'react'; import React, {forwardRef, useContext, useMemo} from 'react'; @@ -14,6 +13,7 @@ import ControlSelection from '@libs/ControlSelection'; import convertToLTR from '@libs/convertToLTR'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import getButtonState from '@libs/getButtonState'; +import Parser from '@libs/Parser'; import type {AvatarSource} from '@libs/UserUtils'; import variables from '@styles/variables'; import * as Session from '@userActions/Session'; @@ -160,7 +160,7 @@ type MenuItemBaseProps = { errorTextStyle?: StyleProp; /** Hint to display at the bottom of the component */ - hintText?: string; + hintText?: string | ReactNode; /** Should the error text red dot indicator be shown */ shouldShowRedDotIndicator?: boolean; @@ -433,16 +433,14 @@ function MenuItem( if (!title || !shouldParseTitle) { return ''; } - const parser = new ExpensiMark(); - return parser.replace(title, {shouldEscapeText}); + return Parser.replace(title, {shouldEscapeText}); }, [title, shouldParseTitle, shouldEscapeText]); const helperHtml = useMemo(() => { if (!helperText || !shouldParseHelperText) { return ''; } - const parser = new ExpensiMark(); - return parser.replace(helperText, {shouldEscapeText}); + return Parser.replace(helperText, {shouldEscapeText}); }, [helperText, shouldParseHelperText, shouldEscapeText]); const processedTitle = useMemo(() => { diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 2eb073bb39be..88ad2f6d5e00 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -50,6 +50,7 @@ function BaseModal( shouldEnableNewFocusManagement = false, restoreFocusType, shouldUseModalPaddingStyle = true, + initialFocus = false, }: BaseModalProps, ref: React.ForwardedRef, ) { @@ -118,7 +119,7 @@ function BaseModal( } hideModal(true); }, - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps [], ); @@ -242,7 +243,9 @@ function BaseModal( deviceWidth={windowWidth} animationIn={animationIn ?? modalStyleAnimationIn} animationOut={animationOut ?? modalStyleAnimationOut} + // eslint-disable-next-line react-compiler/react-compiler useNativeDriver={useNativeDriverProp && useNativeDriver} + // eslint-disable-next-line react-compiler/react-compiler useNativeDriverForBackdrop={useNativeDriverForBackdrop && useNativeDriver} hideModalContentWhileAnimating={hideModalContentWhileAnimating} animationInTiming={animationInTiming} @@ -253,7 +256,10 @@ function BaseModal( customBackdrop={shouldUseCustomBackdrop ? : undefined} > - + {}}: ModalContentProps) { - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps React.useEffect(() => () => onDismiss?.(), []); return children; } diff --git a/src/components/Modal/types.ts b/src/components/Modal/types.ts index 4971932b69c8..6ced829e93d6 100644 --- a/src/components/Modal/types.ts +++ b/src/components/Modal/types.ts @@ -1,8 +1,11 @@ +import type FocusTrap from 'focus-trap-react'; import type {ViewStyle} from 'react-native'; import type {ModalProps} from 'react-native-modal'; import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +type FocusTrapOptions = Exclude; + type PopoverAnchorPosition = { top?: number; right?: number; @@ -87,6 +90,9 @@ type BaseModalProps = Partial & { /** Should we apply padding style in modal itself. If this value is false, we will handle it in ScreenWrapper */ shouldUseModalPaddingStyle?: boolean; + + /** Used to set the element that should receive the initial focus */ + initialFocus?: FocusTrapOptions['initialFocus']; }; export default BaseModalProps; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 80ad2890afaa..d1b22b28c342 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -9,6 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -34,6 +35,7 @@ import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusB import type {ActionHandledType} from './ProcessMoneyReportHoldMenu'; import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu'; +import ExportWithDropdownMenu from './ReportActionItem/ExportWithDropdownMenu'; import SettlementButton from './SettlementButton'; type MoneyReportHeaderProps = { @@ -99,7 +101,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy); const policyType = policy?.type; const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); - const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); + const connectedIntegration = PolicyUtils.getConnectedIntegration(policy); const navigateBackToAfterDelete = useRef(); const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t)); @@ -108,29 +110,25 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea // allTransactions in TransactionUtils might have stale data const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID, transactions); - const cancelPayment = useCallback(() => { - if (!chatReport) { - return; - } - IOU.cancelPayment(moneyRequestReport, chatReport); - setIsConfirmModalVisible(false); - }, [moneyRequestReport, chatReport]); - const shouldShowPayButton = useMemo(() => IOU.canIOUBePaid(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]); const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]); const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport); - const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation; - const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation; + + const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; + const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin; + + const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation && !shouldShowExportIntegrationButton; + const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); - const shouldShowMarkAsCashButton = isDraft && allHavePendingRTERViolation; const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; const shouldShowStatusBar = allHavePendingRTERViolation || hasOnlyHeldExpenses || hasScanningReceipt; const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !shouldShowStatusBar; - const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || allHavePendingRTERViolation; + const shouldShowAnyButton = + shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || allHavePendingRTERViolation || shouldShowExportIntegrationButton; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy); @@ -284,6 +282,15 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea /> )} + {shouldShowExportIntegrationButton && !shouldUseNarrowLayout && ( + + + + )} {shouldShowSubmitButton && !shouldUseNarrowLayout && (