diff --git a/.github/actions/composite/setupGitForOSBotifyApp/action.yml b/.github/actions/composite/setupGitForOSBotifyApp/action.yml new file mode 100644 index 000000000000..bd5b5139bc6b --- /dev/null +++ b/.github/actions/composite/setupGitForOSBotifyApp/action.yml @@ -0,0 +1,53 @@ +# This is a duplicate for setupGitForOSBotify except we are using a Github App now for Github Authentication. +# GitHub Apps have higher rate limits. The reason this is being duplicated is because the existing action is still in use +# in open PRs/branches that aren't up to date with main and it ends up breaking action workflows as a result. +name: "Setup Git for OSBotify" +description: "Setup Git for OSBotify" + +inputs: + GPG_PASSPHRASE: + description: "Passphrase used to decrypt GPG key" + required: true + OS_BOTIFY_APP_ID: + description: "Application ID for OS Botify" + required: true + OS_BOTIFY_PRIVATE_KEY: + description: "OS Botify's private key" + required: true + +outputs: + # Do not try to use this for committing code. Use `secrets.OS_BOTIFY_COMMIT_TOKEN` instead + OS_BOTIFY_API_TOKEN: + description: Token to use for GitHub API interactions. + value: ${{ steps.generateToken.outputs.token }} + +runs: + using: composite + steps: + - name: Decrypt OSBotify GPG key + run: cd .github/workflows && gpg --quiet --batch --yes --decrypt --passphrase=${{ inputs.GPG_PASSPHRASE }} --output OSBotify-private-key.asc OSBotify-private-key.asc.gpg + shell: bash + + - name: Import OSBotify GPG Key + shell: bash + run: cd .github/workflows && gpg --import OSBotify-private-key.asc + + - name: Set up git for OSBotify + shell: bash + run: | + git config user.signingkey 367811D53E34168C + git config commit.gpgsign true + git config user.name OSBotify + git config user.email infra+osbotify@expensify.com + + - name: Enable debug logs for git + shell: bash + if: runner.debug == '1' + run: echo "GIT_TRACE=true" >> "$GITHUB_ENV" + + - name: Generate a token + id: generateToken + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a + with: + app_id: ${{ inputs.OS_BOTIFY_APP_ID }} + private_key: ${{ inputs.OS_BOTIFY_PRIVATE_KEY }} diff --git a/.github/actions/javascript/bumpVersion/index.js b/.github/actions/javascript/bumpVersion/index.js index 830dbf626548..da08d1a060b6 100644 --- a/.github/actions/javascript/bumpVersion/index.js +++ b/.github/actions/javascript/bumpVersion/index.js @@ -298,6 +298,9 @@ function getPreviousVersion(currentVersion, level) { if (patch === 0) { return getPreviousVersion(currentVersion, SEMANTIC_VERSION_LEVELS.MINOR); } + if (major === 1 && minor === 3 && patch === 83) { + return getVersionStringFromNumber(major, minor, patch - 2, 0); + } return getVersionStringFromNumber(major, minor, patch - 1, 0); } diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js index 561b8e61bc21..22ad59ed9588 100644 --- a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js @@ -998,6 +998,9 @@ function getPreviousVersion(currentVersion, level) { if (patch === 0) { return getPreviousVersion(currentVersion, SEMANTIC_VERSION_LEVELS.MINOR); } + if (major === 1 && minor === 3 && patch === 83) { + return getVersionStringFromNumber(major, minor, patch - 2, 0); + } return getVersionStringFromNumber(major, minor, patch - 1, 0); } diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js index e42f97508bc5..3aafda798c54 100644 --- a/.github/actions/javascript/getDeployPullRequestList/index.js +++ b/.github/actions/javascript/getDeployPullRequestList/index.js @@ -961,6 +961,9 @@ function getPreviousVersion(currentVersion, level) { if (patch === 0) { return getPreviousVersion(currentVersion, SEMANTIC_VERSION_LEVELS.MINOR); } + if (major === 1 && minor === 3 && patch === 83) { + return getVersionStringFromNumber(major, minor, patch - 2, 0); + } return getVersionStringFromNumber(major, minor, patch - 1, 0); } diff --git a/.github/actions/javascript/getPreviousVersion/index.js b/.github/actions/javascript/getPreviousVersion/index.js index 37db08db93e9..6770ba99ba69 100644 --- a/.github/actions/javascript/getPreviousVersion/index.js +++ b/.github/actions/javascript/getPreviousVersion/index.js @@ -148,6 +148,9 @@ function getPreviousVersion(currentVersion, level) { if (patch === 0) { return getPreviousVersion(currentVersion, SEMANTIC_VERSION_LEVELS.MINOR); } + if (major === 1 && minor === 3 && patch === 83) { + return getVersionStringFromNumber(major, minor, patch - 2, 0); + } return getVersionStringFromNumber(major, minor, patch - 1, 0); } diff --git a/.github/libs/versionUpdater.js b/.github/libs/versionUpdater.js index 78e8085621bd..b78178f443e6 100644 --- a/.github/libs/versionUpdater.js +++ b/.github/libs/versionUpdater.js @@ -118,6 +118,9 @@ function getPreviousVersion(currentVersion, level) { if (patch === 0) { return getPreviousVersion(currentVersion, SEMANTIC_VERSION_LEVELS.MINOR); } + if (major === 1 && minor === 3 && patch === 83) { + return getVersionStringFromNumber(major, minor, patch - 2, 0); + } return getVersionStringFromNumber(major, minor, patch - 1, 0); } diff --git a/.github/scripts/findUnusedKeys.sh b/.github/scripts/findUnusedKeys.sh index 77c3ea25326b..1411fffc8389 100755 --- a/.github/scripts/findUnusedKeys.sh +++ b/.github/scripts/findUnusedKeys.sh @@ -6,7 +6,7 @@ LIB_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../../ && pwd)" readonly SRC_DIR="${LIB_PATH}/src" readonly STYLES_DIR="${LIB_PATH}/src/styles" -readonly STYLES_FILE="${LIB_PATH}/src/styles/styles.js" +readonly STYLES_FILE="${LIB_PATH}/src/styles/styles.ts" readonly UTILITIES_STYLES_FILE="${LIB_PATH}/src/styles/utilities" readonly STYLES_KEYS_FILE="${LIB_PATH}/scripts/style_keys_list_temp.txt" readonly UTILITY_STYLES_KEYS_FILE="${LIB_PATH}/scripts/utility_keys_list_temp.txt" @@ -210,7 +210,12 @@ find_theme_style_and_store_keys() { fi # Check if we are inside an arrow function - if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ || "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + if [[ "$line" =~ ^[[:space:]]*([a-zA-Zgv 0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ || "$line" =~ ^[[:space:]]*([a-zA-Zgv 0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + inside_arrow_function=true + continue + fi + + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then inside_arrow_function=true continue fi @@ -348,7 +353,7 @@ echo "🔍 Looking for styles." find_utility_styles_store_prefix find_utility_usage_as_styles -# Find and store keys from styles.js +# Find and store keys from styles.ts find_styles_object_and_store_keys "$STYLES_FILE" find_styles_functions_and_store_keys "$STYLES_FILE" collect_theme_keys_from_styles "$STYLES_FILE" diff --git a/.github/workflows/cherryPick.yml b/.github/workflows/cherryPick.yml index b6558b049647..e6da6fff1446 100644 --- a/.github/workflows/cherryPick.yml +++ b/.github/workflows/cherryPick.yml @@ -11,7 +11,7 @@ jobs: validateActor: runs-on: ubuntu-latest outputs: - IS_DEPLOYER: ${{ fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) || github.actor == 'OSBotify' }} + IS_DEPLOYER: ${{ fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) || github.actor == 'OSBotify' || github.actor == 'os-botify[bot]' }} steps: - name: Check if user is deployer id: isDeployer @@ -41,15 +41,17 @@ jobs: token: ${{ secrets.OS_BOTIFY_TOKEN }} - name: Set up git for OSBotify - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + uses: Expensify/App/.github/actions/composite/setupGitForOSBotifyApp@8c19d6da4a3d7ce3b15c9cd89a802187d208ecab with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} + OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} - name: Get previous app version id: getPreviousVersion uses: Expensify/App/.github/actions/javascript/getPreviousVersion@main with: - SEMVER_LEVEL: 'PATCH' + SEMVER_LEVEL: "PATCH" - name: Fetch history of relevant refs run: | @@ -119,7 +121,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - - name: 'Announces a CP failure in the #announce Slack room' + - name: "Announces a CP failure in the #announce Slack room" uses: 8398a7/action-slack@v3 if: ${{ failure() }} with: diff --git a/.github/workflows/createNewVersion.yml b/.github/workflows/createNewVersion.yml index ba907334c595..c9c97d5355fb 100644 --- a/.github/workflows/createNewVersion.yml +++ b/.github/workflows/createNewVersion.yml @@ -26,12 +26,18 @@ on: LARGE_SECRET_PASSPHRASE: description: Passphrase used to decrypt GPG key required: true - OS_BOTIFY_TOKEN: - description: Token for the OSBotify user - required: true SLACK_WEBHOOK: description: Webhook used to comment in slack required: true + OS_BOTIFY_COMMIT_TOKEN: + description: OSBotify personal access token, used to workaround committing to protected branch + required: true + OS_BOTIFY_APP_ID: + description: Application ID for OS Botify App + required: true + OS_BOTIFY_PRIVATE_KEY: + description: OSBotify private key + required: true jobs: validateActor: @@ -43,7 +49,7 @@ jobs: id: getUserPermissions run: echo "PERMISSION=$(gh api /repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission | jq -r '.permission')" >> "$GITHUB_OUTPUT" env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} createNewVersion: runs-on: macos-latest @@ -65,18 +71,23 @@ jobs: uses: actions/checkout@v3 with: ref: main - token: ${{ secrets.OS_BOTIFY_TOKEN }} + # The OS_BOTIFY_COMMIT_TOKEN is a personal access token tied to osbotify + # This is a workaround to allow pushes to a protected branch + token: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} - name: Setup git for OSBotify - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + uses: Expensify/App/.github/actions/composite/setupGitForOSBotifyApp@8c19d6da4a3d7ce3b15c9cd89a802187d208ecab + id: setupGitForOSBotify with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} + OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} - name: Generate version id: bumpVersion uses: Expensify/App/.github/actions/javascript/bumpVersion@main with: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} SEMVER_LEVEL: ${{ inputs.SEMVER_LEVEL }} - name: Commit new version diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f2ff67680940..78040f237689 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -14,11 +14,13 @@ jobs: with: ref: staging token: ${{ secrets.OS_BOTIFY_TOKEN }} - - - name: Setup git for OSBotify - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + + - uses: Expensify/App/.github/actions/composite/setupGitForOSBotifyApp@8c19d6da4a3d7ce3b15c9cd89a802187d208ecab + id: setupGitForOSBotify with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} + OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} - name: Tag version run: git tag "$(npm run print-version --silent)" @@ -30,16 +32,18 @@ jobs: runs-on: ubuntu-latest if: github.ref == 'refs/heads/production' steps: - - name: Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v3 + name: Checkout with: ref: production token: ${{ secrets.OS_BOTIFY_TOKEN }} - - name: Setup git for OSBotify - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + - uses: Expensify/App/.github/actions/composite/setupGitForOSBotifyApp@8c19d6da4a3d7ce3b15c9cd89a802187d208ecab + id: setupGitForOSBotify with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} + OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} - name: Get current app version run: echo "PRODUCTION_VERSION=$(npm run print-version --silent)" >> "$GITHUB_ENV" @@ -49,7 +53,7 @@ jobs: uses: Expensify/App/.github/actions/javascript/getDeployPullRequestList@main with: TAG: ${{ env.PRODUCTION_VERSION }} - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} IS_PRODUCTION_DEPLOY: true - name: Generate Release Body @@ -64,4 +68,4 @@ jobs: tag_name: ${{ env.PRODUCTION_VERSION }} body: ${{ steps.getReleaseBody.outputs.RELEASE_BODY }} env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index 3666e8c7d343..308404b74bc0 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -191,14 +191,15 @@ jobs: if: ${{ always() && runner.debug != null && fromJSON(runner.debug) }} run: cat "./Host_Machine_Files/\$WORKING_DIRECTORY/debug.log" - - name: Check if test failed, if so post the results and add the DeployBlocker label - run: | - if grep -q '🔴' ./Host_Machine_Files/\$WORKING_DIRECTORY/output.md; then - gh pr edit ${{ inputs.PR_NUMBER }} --add-label DeployBlockerCash - 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 '✅ no performance regression detected' - fi - env: - GITHUB_TOKEN: ${{ github.token }} +# TODO: Once tests are more reliable we should uncomment this +# - name: Check if test failed, if so post the results and add the DeployBlocker label +# run: | +# if grep -q '🔴' ./Host_Machine_Files/\$WORKING_DIRECTORY/output.md; then +# gh pr edit ${{ inputs.PR_NUMBER }} --add-label DeployBlockerCash +# 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 '✅ no performance regression detected' +# fi +# env: +# GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/finishReleaseCycle.yml b/.github/workflows/finishReleaseCycle.yml index e2323af2486e..4fe6249edacc 100644 --- a/.github/workflows/finishReleaseCycle.yml +++ b/.github/workflows/finishReleaseCycle.yml @@ -12,6 +12,19 @@ jobs: outputs: isValid: ${{ fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) && !fromJSON(steps.checkDeployBlockers.outputs.HAS_DEPLOY_BLOCKERS) }} steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: main + token: ${{ secrets.OS_BOTIFY_TOKEN }} + + - uses: Expensify/App/.github/actions/composite/setupGitForOSBotifyApp@8c19d6da4a3d7ce3b15c9cd89a802187d208ecab + id: setupGitForOSBotify + with: + GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} + OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} + - name: Validate actor is deployer id: isDeployer run: | @@ -70,9 +83,12 @@ jobs: token: ${{ secrets.OS_BOTIFY_TOKEN }} - name: Setup Git for OSBotify - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + id: setupGitForOSBotify + uses: Expensify/App/.github/actions/composite/setupGitForOSBotifyApp@8c19d6da4a3d7ce3b15c9cd89a802187d208ecab with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} + OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} - name: Update production branch run: | @@ -109,9 +125,11 @@ jobs: token: ${{ secrets.OS_BOTIFY_TOKEN }} - name: Setup Git for OSBotify - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + uses: Expensify/App/.github/actions/composite/setupGitForOSBotifyApp@8c19d6da4a3d7ce3b15c9cd89a802187d208ecab with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} + OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} - name: Update staging branch to trigger staging deploy run: | diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index ad002e164837..1105f78da27a 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -16,7 +16,7 @@ jobs: validateActor: runs-on: ubuntu-latest outputs: - IS_DEPLOYER: ${{ fromJSON(steps.isUserDeployer.outputs.IS_DEPLOYER) || github.actor == 'OSBotify' }} + IS_DEPLOYER: ${{ fromJSON(steps.isUserDeployer.outputs.IS_DEPLOYER) || github.actor == 'OSBotify' || github.actor == 'os-botify[bot]' }} steps: - name: Check if user is deployer id: isUserDeployer @@ -104,6 +104,13 @@ jobs: name: android-sourcemap path: android/app/build/generated/sourcemaps/react/release/*.map + - name: Upload Android version to GitHub artifacts + if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} + uses: actions/upload-artifact@v3 + with: + name: app-production-release.aab + path: android/app/build/outputs/bundle/productionRelease/app-production-release.aab + - name: Upload Android version 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" @@ -238,6 +245,13 @@ jobs: name: ios-sourcemap path: main.jsbundle.map + - name: Upload iOS version to GitHub artifacts + if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} + uses: actions/upload-artifact@v3 + with: + name: New Expensify.ipa + path: /Users/runner/work/App/App/New Expensify.ipa + - name: Upload iOS version 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" diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index 186490c7baaf..d7d372aa7948 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -92,9 +92,11 @@ jobs: token: ${{ secrets.OS_BOTIFY_TOKEN }} - name: Setup Git for OSBotify - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + uses: Expensify/App/.github/actions/composite/setupGitForOSBotifyApp@8c19d6da4a3d7ce3b15c9cd89a802187d208ecab with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} + OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} - name: Update staging branch from main run: | diff --git a/.well-known/apple-app-site-association b/.well-known/apple-app-site-association index d6da0232f2fc..1e63fdcb2d52 100644 --- a/.well-known/apple-app-site-association +++ b/.well-known/apple-app-site-association @@ -79,6 +79,10 @@ { "/": "/search/*", "comment": "Search" + }, + { + "/": "/money2020/*", + "comment": "Money 2020" } ] } diff --git a/README.md b/README.md index daf9ddfae1ff..9aad797ebb51 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ New Expensify Icon

- + New Expensify

diff --git a/__mocks__/react-native.js b/__mocks__/react-native.js index 006d1aee38af..1eeea877ca0f 100644 --- a/__mocks__/react-native.js +++ b/__mocks__/react-native.js @@ -28,6 +28,7 @@ jest.doMock('react-native', () => { BootSplash: { getVisibilityStatus: jest.fn(), hide: jest.fn(), + logoSizeRatio: 1, navigationBarHeight: 0, }, StartupTimer: {stop: jest.fn()}, diff --git a/android/app/build.gradle b/android/app/build.gradle index db251e681e64..1b8eac0c5c20 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001038107 - versionName "1.3.81-7" + versionCode 1001038500 + versionName "1.3.85-0" } flavorDimensions "default" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d823324f50bf..7419d5b1e1a7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -70,6 +70,7 @@ + @@ -87,6 +88,7 @@ + diff --git a/android/app/src/main/java/com/expensify/chat/bootsplash/BootSplashDialog.java b/android/app/src/main/java/com/expensify/chat/bootsplash/BootSplashDialog.java index f5b1ceff60e2..b65cb7306a3d 100644 --- a/android/app/src/main/java/com/expensify/chat/bootsplash/BootSplashDialog.java +++ b/android/app/src/main/java/com/expensify/chat/bootsplash/BootSplashDialog.java @@ -6,6 +6,7 @@ import android.view.Window; import android.view.WindowManager.LayoutParams; import androidx.annotation.NonNull; +import com.expensify.chat.R; public class BootSplashDialog extends Dialog { @@ -26,6 +27,10 @@ protected void onCreate(Bundle savedInstanceState) { if (window != null) { window.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + + if (BootSplashModule.isSamsungOneUI4()) { + window.setBackgroundDrawableResource(R.drawable.bootsplash_samsung_oneui_4); + } } super.onCreate(savedInstanceState); diff --git a/android/app/src/main/java/com/expensify/chat/bootsplash/BootSplashModule.java b/android/app/src/main/java/com/expensify/chat/bootsplash/BootSplashModule.java index c286ebf7a935..7498fa6594fb 100644 --- a/android/app/src/main/java/com/expensify/chat/bootsplash/BootSplashModule.java +++ b/android/app/src/main/java/com/expensify/chat/bootsplash/BootSplashModule.java @@ -23,6 +23,7 @@ import com.facebook.react.common.ReactConstants; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.PixelUtil; +import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.Timer; @@ -47,6 +48,19 @@ public String getName() { return NAME; } + // From https://stackoverflow.com/a/61062773 + public static boolean isSamsungOneUI4() { + String name = "SEM_PLATFORM_INT"; + + try { + Field field = Build.VERSION.class.getDeclaredField(name); + int version = (field.getInt(null) - 90000) / 10000; + return version == 4; + } catch (Exception ignored) { + return false; + } + } + @Override public Map getConstants() { final HashMap constants = new HashMap<>(); @@ -61,6 +75,7 @@ public Map getConstants() { ? Math.round(PixelUtil.toDIPFromPixel(resources.getDimensionPixelSize(heightResId))) : 0; + constants.put("logoSizeRatio", isSamsungOneUI4() ? 0.5 : 1); constants.put("navigationBarHeight", height); return constants; } diff --git a/android/app/src/main/res/mipmap-hdpi/bootsplash_logo.png b/android/app/src/main/res/drawable-hdpi/bootsplash_logo.png similarity index 100% rename from android/app/src/main/res/mipmap-hdpi/bootsplash_logo.png rename to android/app/src/main/res/drawable-hdpi/bootsplash_logo.png diff --git a/android/app/src/main/res/mipmap-mdpi/bootsplash_logo.png b/android/app/src/main/res/drawable-mdpi/bootsplash_logo.png similarity index 100% rename from android/app/src/main/res/mipmap-mdpi/bootsplash_logo.png rename to android/app/src/main/res/drawable-mdpi/bootsplash_logo.png diff --git a/android/app/src/main/res/mipmap-xhdpi/bootsplash_logo.png b/android/app/src/main/res/drawable-xhdpi/bootsplash_logo.png similarity index 100% rename from android/app/src/main/res/mipmap-xhdpi/bootsplash_logo.png rename to android/app/src/main/res/drawable-xhdpi/bootsplash_logo.png diff --git a/android/app/src/main/res/mipmap-xxhdpi/bootsplash_logo.png b/android/app/src/main/res/drawable-xxhdpi/bootsplash_logo.png similarity index 100% rename from android/app/src/main/res/mipmap-xxhdpi/bootsplash_logo.png rename to android/app/src/main/res/drawable-xxhdpi/bootsplash_logo.png diff --git a/android/app/src/main/res/mipmap-xxxhdpi/bootsplash_logo.png b/android/app/src/main/res/drawable-xxxhdpi/bootsplash_logo.png similarity index 100% rename from android/app/src/main/res/mipmap-xxxhdpi/bootsplash_logo.png rename to android/app/src/main/res/drawable-xxxhdpi/bootsplash_logo.png diff --git a/android/app/src/main/res/drawable/bootsplash_samsung_oneui_4.xml b/android/app/src/main/res/drawable/bootsplash_samsung_oneui_4.xml new file mode 100644 index 000000000000..9861004d368f --- /dev/null +++ b/android/app/src/main/res/drawable/bootsplash_samsung_oneui_4.xml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 34d33d240458..aa0e8136957f 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -72,7 +72,7 @@ diff --git a/assets/animations/FastMoney.json b/assets/animations/FastMoney.json new file mode 100644 index 000000000000..95d560319141 --- /dev/null +++ b/assets/animations/FastMoney.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":24,"ip":0,"op":80,"w":375,"h":240,"nm":"C","assets":[{"id":"comp_0","nm":"C","fr":24,"layers":[{"ind":1,"ty":0,"nm":"E","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[174.5,291,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[42,42,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":0,"op":80,"st":0}]},{"id":"comp_1","nm":"E","fr":24,"layers":[{"ind":1,"ty":0,"nm":"t","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[966,966,0],"to":[0,-20,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":8,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":12,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":16,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":24,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":28,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":32,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":36,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":40,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":48,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":52,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":56,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":64,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":68,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":72,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":76,"s":[966,846,0],"to":[0,0,0],"ti":[0,-20,0]},{"t":80,"s":[966,966,0]}],"l":2},"a":{"a":0,"k":[966,543.5,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1087,"ip":0,"op":80,"st":0},{"ind":2,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[966,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":0,"op":16,"st":0},{"ind":3,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[966,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":16,"op":32,"st":16},{"ind":4,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[966,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":32,"op":48,"st":32},{"ind":5,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[966,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":48,"op":64,"st":48},{"ind":6,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[966,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":64,"op":80,"st":64},{"ind":7,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[1060,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":0,"op":8,"st":-8},{"ind":8,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[1060,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":8,"op":24,"st":8},{"ind":9,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[1060,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":24,"op":40,"st":24},{"ind":10,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[1060,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":40,"op":56,"st":40},{"ind":11,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[1060,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":56,"op":72,"st":56},{"ind":12,"ty":0,"nm":"l","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[1060,966,0],"l":2},"a":{"a":0,"k":[966,966,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1932,"ip":72,"op":80,"st":72},{"ind":13,"ty":0,"nm":"t","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[966,966,0],"to":[0,-20,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":8,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":12,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":16,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":24,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":28,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":32,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":36,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":40,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":48,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":52,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":56,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":64,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":68,"s":[966,846,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":72,"s":[966,966,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":76,"s":[966,846,0],"to":[0,0,0],"ti":[0,-20,0]},{"t":80,"s":[966,966,0]}],"l":2},"a":{"a":0,"k":[966,543.5,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1087,"ip":0,"op":80,"st":0},{"ind":14,"ty":0,"nm":"u","parent":1,"refId":"comp_4","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[966,543,0],"l":2},"a":{"a":0,"k":[966,543.5,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"w":1932,"h":1087,"ip":0,"op":80,"st":0},{"ind":15,"ty":3,"nm":"s","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[0]},{"t":54,"s":[9]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[1304.5,374.75,0],"to":[12.667,-19,0],"ti":[-13.167,5,0]},{"t":54,"s":[1362.5,335.75,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":50,"s":[100,100,100]},{"t":54,"s":[25,25,100]}],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":16,"ty":3,"nm":"s","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[0]},{"t":54,"s":[6]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[1341.898,405.813,0],"to":[13.5,0.917,0],"ti":[-11.5,-9.417,0]},{"t":54,"s":[1395.898,426.313,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":50,"s":[100,100,100]},{"t":54,"s":[25,25,100]}],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":17,"ty":4,"nm":"s","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-278.97,470.86,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-8.927,0],[1.705,-3.01],[7.797,-1.705]],"o":[[4.514,-11.535],[7.87,0],[-1.705,3.009],[-7.797,1.706]],"v":[[-14.694,12.137],[6.068,-12.137],[12.99,-2.507],[-0.378,5.015]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.553,0.784,1,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1285.643,129.839]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":50,"op":55,"st":0},{"ind":18,"ty":4,"nm":"s","parent":16,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-316.368,439.797,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[11.736,1.103],[-9.329,0],[0.702,-4.112],[5.019,0.247]],"o":[[5.818,-4.313],[9.328,0],[-0.702,3.01],[-10.432,-0.903]],"v":[[-16.349,-0.224],[4.012,-6.643],[15.647,1.582],[6.72,6.397]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.553,0.784,1,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1321,152.732]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":50,"op":55,"st":0}]},{"id":"comp_2","nm":"t","fr":24,"layers":[{"ind":1,"ty":3,"nm":"m","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":0},"p":{"a":0,"k":[1028,525.5,0],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[101,91,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":4,"s":[91,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":8,"s":[101,91,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":12,"s":[91,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":16,"s":[101,91,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[91,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":24,"s":[101,91,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":28,"s":[91,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":32,"s":[101,91,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":36,"s":[91,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":40,"s":[101,91,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44,"s":[91,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":48,"s":[101,91,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":52,"s":[91,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":56,"s":[101,91,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[91,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":64,"s":[101,91,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":68,"s":[91,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":72,"s":[101,91,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":76,"s":[91,103,100]},{"t":80,"s":[101,91,100]}],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":2,"ty":3,"nm":"b","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[909,531.5,0],"to":[1,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[915,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":8,"s":[909,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":12,"s":[915,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":16,"s":[909,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[915,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[909,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":28,"s":[915,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":32,"s":[909,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":36,"s":[915,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[909,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44,"s":[915,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[909,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":52,"s":[915,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":56,"s":[909,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[915,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":64,"s":[909,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":68,"s":[915,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":72,"s":[909,531.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":76,"s":[915,531.5,0],"to":[0,0,0],"ti":[1,0,0]},{"t":80,"s":[909,531.5,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[111,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":4,"s":[80,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":8,"s":[111,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":12,"s":[80,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":16,"s":[111,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[80,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":24,"s":[111,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":28,"s":[80,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":32,"s":[111,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":36,"s":[80,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":40,"s":[111,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44,"s":[80,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":48,"s":[111,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":52,"s":[80,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":56,"s":[111,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[80,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":64,"s":[111,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":68,"s":[80,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":72,"s":[111,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":76,"s":[80,103,100]},{"t":80,"s":[111,65,100]}],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":3,"ty":3,"nm":"h","parent":4,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":0},"p":{"a":0,"k":[10,137,0],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":4,"ty":3,"nm":"a","parent":5,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":8,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":12,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":32,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":36,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":44,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":48,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":52,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":56,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":64,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":68,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":76,"s":[0]},{"t":80,"s":[10]}]},"p":{"a":0,"k":[-36,63,0],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":5,"ty":3,"nm":"a","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[-112]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[-160]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":8,"s":[-112]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":12,"s":[-4]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[-112]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[-160]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[-112]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28,"s":[-4]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":32,"s":[-112]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":36,"s":[-160]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[-112]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":44,"s":[-4]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":48,"s":[-112]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":52,"s":[-160]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":56,"s":[-112]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[-4]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":64,"s":[-112]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":68,"s":[-160]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[-112]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":76,"s":[-4]},{"t":80,"s":[-112]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[890,395.5,0],"to":[2.5,-2,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[905,383.5,0],"to":[0,0,0],"ti":[-2.5,-1.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":8,"s":[890,395.5,0],"to":[2.5,1.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":12,"s":[920,393.5,0],"to":[0,0,0],"ti":[2.5,1.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":16,"s":[890,395.5,0],"to":[-2.5,-1.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[905,383.5,0],"to":[0,0,0],"ti":[-2.5,-1.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[890,395.5,0],"to":[2.5,1.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":28,"s":[920,393.5,0],"to":[0,0,0],"ti":[2.5,1.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":32,"s":[890,395.5,0],"to":[-2.5,-1.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":36,"s":[905,383.5,0],"to":[0,0,0],"ti":[-2.5,-1.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[890,395.5,0],"to":[2.5,1.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44,"s":[920,393.5,0],"to":[0,0,0],"ti":[2.5,1.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[890,395.5,0],"to":[-2.5,-1.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":52,"s":[905,383.5,0],"to":[0,0,0],"ti":[-2.5,-1.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":56,"s":[890,395.5,0],"to":[2.5,1.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[920,393.5,0],"to":[0,0,0],"ti":[2.5,1.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":64,"s":[890,395.5,0],"to":[-2.5,-1.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":68,"s":[905,383.5,0],"to":[0,0,0],"ti":[-2.5,-1.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":72,"s":[890,395.5,0],"to":[2.5,1.667,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":76,"s":[920,393.5,0],"to":[0,0,0],"ti":[5,-0.333,0]},{"t":80,"s":[890,395.5,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":6,"ty":3,"nm":"h","parent":7,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":44,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":47,"s":[0]},{"t":49,"s":[-20]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":44,"s":[-125,-9.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":47,"s":[-125,-9.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":49,"s":[-125,-9.5,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":7,"ty":3,"nm":"a","parent":8,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":44,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":47,"s":[0]},{"t":49,"s":[-19]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":44,"s":[75,-34,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":47,"s":[75,-34,0],"to":[0,0,0],"ti":[0,0,0]},{"t":49,"s":[75,-34,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":8,"ty":3,"nm":"a","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":44,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":47,"s":[0]},{"t":49,"s":[30]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":44,"s":[1122,363.5,0],"to":[5,1.333,0],"ti":[-3.333,-2.333,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":47,"s":[1152,371.5,0],"to":[3.333,2.333,0],"ti":[1.667,-1,0]},{"t":49,"s":[1142,377.5,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":9,"ty":3,"nm":"h","parent":10,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[-10]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":53,"s":[0]},{"t":59,"s":[-54]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":50,"s":[70,-59,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":53,"s":[70,-59,0],"to":[0,0,0],"ti":[0,0,0]},{"t":59,"s":[70,-59,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":10,"ty":3,"nm":"a","parent":11,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":53,"s":[0]},{"t":59,"s":[-11]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":50,"s":[121,-15,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":53,"s":[121,-15,0],"to":[0,0,0],"ti":[0,0,0]},{"t":59,"s":[121,-15,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":11,"ty":3,"nm":"a","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":53,"s":[0]},{"t":59,"s":[20]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":50,"s":[1150,365.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.659,"y":0.854},"o":{"x":0.297,"y":0},"t":53,"s":[1150,365.5,0],"to":[-1.038,0.389,0],"ti":[2.52,-0.945,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.425,"y":0.211},"t":56,"s":[1144.19,387.679,0],"to":[-3.951,1.482,0],"ti":[1.628,-0.611,0]},{"t":59,"s":[1134,371.5,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":12,"ty":4,"nm":"h","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[208.53,96.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0.2,6.65],[-11.98,5.54],[-6.96,-4.61],[0.1,-7.55],[0,0]],"o":[[0,0],[-5.9,0.34],[-0.21,-6.66],[11.99,-5.53],[6.97,4.61],[-0.1,7.56],[0,0]],"v":[[-16.11,18.555],[-16.12,18.555],[-26.52,9.685],[-11.78,-13.365],[18.64,-11.005],[26.63,5.195],[19.56,15.265]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[804.229,508.235]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.55,-2.56]],"o":[[-0.38,4.04],[0,0]],"v":[[0.06,-4.945],[0.32,4.945]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[787.789,521.845]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.59,-0.76],[-0.11,0.24],[0,0]],"o":[[0.76,3.53],[4.3,1.27],[0,0],[0,0]],"v":[[-5.495,-3.85],[-0.485,2.58],[5.495,0.2],[5.495,0.19]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[793.614,530.64]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.48,-2.41],[-2.62,0.23],[-0.21,0.37]],"o":[[-0.42,5.63],[1.84,3],[4.74,-0.43],[0,0]],"v":[[-6.755,-7.855],[-4.845,3.955],[2.435,7.625],[7.175,2.625]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[803.954,526.885]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.77,-2.08],[-2.7,0.54],[2.35,4.24],[0.06,4.63]],"o":[[0.46,4.14],[2.45,2.88],[4.64,-0.93],[-1.44,-2.85],[0,0]],"v":[[-9.3,-3.17],[-5.71,6.09],[2.68,9.45],[6.95,0.08],[4.48,-9.99]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[816.839,523.42]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.64,-0.93],[2.45,2.88],[4.74,-0.43],[1.84,3],[4.3,1.27],[0.76,3.53],[0,0],[0.2,6.65],[-11.98,5.54],[-6.96,-4.61],[0.1,-7.55]],"o":[[2.35,4.24],[-2.7,0.54],[-0.21,0.37],[-2.62,0.23],[-0.11,0.24],[-2.59,-0.76],[0,0],[-5.9,0.34],[-0.21,-6.66],[11.99,-5.53],[6.97,4.61],[-0.1,7.56]],"v":[[19.56,11.46],[15.29,20.83],[6.9,17.47],[2.16,22.47],[-5.12,18.8],[-11.1,21.18],[-16.11,14.75],[-16.12,14.75],[-26.52,5.88],[-11.78,-17.17],[18.64,-14.81],[26.63,1.39]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[804.229,512.04]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":80,"st":0},{"ind":13,"ty":4,"nm":"a","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[168.53,183.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-3.79,-4.94]],"o":[[6.76,-0.53],[0,0]],"v":[[-8.2,-2.515],[8.2,3.045]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[810.199,470.945]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[7.72,-0.59]],"o":[[-3.14,-5.15],[0,0]],"v":[[8.145,3.715],[-8.145,-3.125]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[813.384,459.555]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-4.28,-4.54]],"o":[[6.87,-2.29],[0,0]],"v":[[-8.365,-0.545],[8.365,2.835]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[808.274,482.105]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.37,9.86]],"o":[[-0.37,9.86],[0,0]],"v":[[-7.815,-5.235],[8.185,-4.625]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[806.914,502.335]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.54,4.98],[-0.91,4.23],[-1.27,3.87],[-16.55,15.32]],"o":[[0,-5.38],[0.48,-4.53],[0.88,-4.14],[6.98,-21.31],[0,0]],"v":[[-21.145,47.33],[-20.335,31.79],[-18.245,18.66],[-15.005,6.66],[21.145,-47.33]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[820.244,449.77]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.68,4.15],[-0.85,3.53],[-1.15,3.48],[-12.89,12.79]],"o":[[0.36,-4.37],[0.59,-3.7],[0.89,-3.66],[5.88,-17.68],[0,0]],"v":[[-17.46,40.015],[-15.91,27.245],[-13.75,16.395],[-10.69,5.685],[17.46,-40.015]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[832.559,457.695]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-16.55,15.32],[0,0],[5.88,-17.68],[0,0],[7.72,-0.59]],"o":[[0,0],[-12.89,12.79],[0,0],[-3.14,-5.15],[6.98,-21.31]],"v":[[13.76,-30.47],[22.39,-15.23],[-5.76,30.47],[-6.1,30.36],[-22.39,23.52]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[827.629,432.91]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.28,-4.54],[0,0],[0.36,-4.37],[-0.37,9.86],[-0.54,4.98]],"o":[[0,0],[-0.68,4.15],[-0.37,9.86],[0,-5.38],[6.87,-2.29]],"v":[[8.95,-8.48],[8.96,-8.48],[7.41,4.29],[-8.59,3.68],[-7.78,-11.86]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[807.689,493.42]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-3.14,-5.15],[0,0],[0.89,-3.66],[0,0],[6.76,-0.53],[-1.27,3.87]],"o":[[0,0],[-1.15,3.48],[0,0],[-3.79,-4.94],[0.88,-4.14],[7.72,-0.59]],"v":[[9.595,-1.695],[9.935,-1.585],[6.875,9.125],[6.465,9.025],[-9.935,3.465],[-6.695,-8.535]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[811.934,464.965]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-3.79,-4.94],[0,0],[0.59,-3.7],[0,0],[6.87,-2.29],[-0.91,4.23]],"o":[[0,0],[-0.85,3.53],[0,0],[-4.28,-4.54],[0.48,-4.53],[6.76,-0.53]],"v":[[9.04,-2.43],[9.45,-2.33],[7.29,8.52],[7.28,8.52],[-9.45,5.14],[-7.36,-7.99]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[809.359,476.42]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":80,"st":0},{"ind":14,"ty":4,"nm":"a","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.53,196.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33],[-1.52,-8.7]],"o":[[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33],[0,4.67]],"v":[[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22],[52.945,-11.41]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.52,-8.7],[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33]],"o":[[0,4.67],[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33]],"v":[[52.945,-11.41],[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":79,"op":80,"st":0},{"ind":15,"ty":4,"nm":"a","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.53,196.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33],[-1.52,-8.7]],"o":[[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33],[0,4.67]],"v":[[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22],[52.945,-11.41]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.52,-8.7],[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33]],"o":[[0,4.67],[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33]],"v":[[52.945,-11.41],[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":63,"op":74,"st":0},{"ind":16,"ty":4,"nm":"a","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.53,196.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33],[-1.52,-8.7]],"o":[[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33],[0,4.67]],"v":[[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22],[52.945,-11.41]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.52,-8.7],[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33]],"o":[[0,4.67],[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33]],"v":[[52.945,-11.41],[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":47,"op":58,"st":0},{"ind":17,"ty":4,"nm":"a","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.53,196.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33],[-1.52,-8.7]],"o":[[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33],[0,4.67]],"v":[[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22],[52.945,-11.41]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.52,-8.7],[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33]],"o":[[0,4.67],[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33]],"v":[[52.945,-11.41],[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":31,"op":42,"st":0},{"ind":18,"ty":4,"nm":"a","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.53,196.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33],[-1.52,-8.7]],"o":[[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33],[0,4.67]],"v":[[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22],[52.945,-11.41]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.52,-8.7],[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33]],"o":[[0,4.67],[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33]],"v":[[52.945,-11.41],[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":15,"op":26,"st":0},{"ind":19,"ty":4,"nm":"a","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.53,196.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33],[-1.52,-8.7]],"o":[[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33],[0,4.67]],"v":[[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22],[52.945,-11.41]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.52,-8.7],[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33]],"o":[[0,4.67],[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33]],"v":[[52.945,-11.41],[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":10,"st":0},{"ind":20,"ty":4,"nm":"h","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-187.47,521.11,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.16,-0.19],[3.84,0],[3.82,-0.13],[0.38,-2.68],[0,0]],"o":[[-1.74,0.1],[-1.91,0.31],[-6.18,0],[-3.82,0.13],[-0.38,2.67],[0,0]],"v":[[16.135,-3.325],[12.025,-2.855],[4.485,-2.215],[-9.325,-3.665],[-15.755,0.235],[-14.695,3.795]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1164.344,61.285]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[5.22,0.9],[1.21,-0.17],[0.01,-2.03],[-0.25,-0.13],[0,0]],"o":[[-8.92,1.02],[-1.75,-0.3],[-2.4,0.34],[-0.02,2.87],[0.02,0.01],[0,0]],"v":[[14.59,-2.46],[-6.43,-3.61],[-10.88,-3.79],[-14.57,-0.17],[-12.3,3.95],[-12.27,3.96]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1160.539,68.96]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[9.68,0.77],[0.84,-0.37],[-4.65,-0.93]],"o":[[-12.23,1.66],[-1.38,-0.02],[-4.17,1.76],[0,0]],"v":[[15.53,-4.375],[-8.04,-4.375],[-11.36,-3.825],[-8.94,4.395]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1159.599,76.735]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.98,-0.25],[1.1,-1.06],[0,-1],[-5.86,-0.63],[-3.95,0],[-8.15,2.81],[-0.51,6.88],[7.13,1.4],[4.87,3.95],[1.15,-1.91],[-7.42,-3.29]],"o":[[-6.4,0.97],[-2.55,-0.16],[-0.66,0.61],[0,2.68],[5.86,0.64],[3.95,0],[8.15,-2.8],[0.51,-6.88],[-7.13,-1.4],[-4.87,-3.95],[-1.15,1.91],[0,0]],"v":[[-3.255,11.595],[-22.745,12.485],[-28.345,13.875],[-29.365,16.305],[-22.105,22.165],[-5.675,21.025],[15.605,19.235],[28.855,5.095],[18.535,-10.705],[-0.295,-18.855],[-8.475,-20.005],[-2.645,-8.835]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1179.014,67.265]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.91,0.31],[-1.15,1.91],[-4.87,-3.95],[-7.13,-1.4],[0.51,-6.88],[8.15,-2.8],[3.95,0],[5.86,0.64],[0,2.68],[-0.66,0.61],[0,0],[-4.17,1.76],[-0.02,2.87],[-2.4,0.34],[0,0],[-0.38,2.67],[-3.82,0.13],[-6.18,0]],"o":[[-7.42,-3.29],[1.15,-1.91],[4.87,3.95],[7.13,1.4],[-0.51,6.88],[-8.15,2.81],[-3.95,0],[-5.86,-0.63],[0,-1],[0,0],[-4.65,-0.93],[-0.25,-0.13],[0.01,-2.03],[0,0],[0,0],[0.38,-2.68],[3.82,-0.13],[3.84,0]],"v":[[0.145,-8.835],[-5.685,-20.005],[2.495,-18.855],[21.325,-10.705],[31.645,5.095],[18.395,19.235],[-2.885,21.025],[-19.315,22.165],[-26.575,16.305],[-25.555,13.875],[-25.565,13.865],[-27.985,5.645],[-30.255,1.525],[-26.565,-2.095],[-26.575,-2.185],[-27.635,-5.745],[-21.205,-9.645],[-7.395,-8.195]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1176.224,67.265]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":44,"op":50,"st":0},{"ind":21,"ty":4,"nm":"a","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-362.47,461.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[2.48,-5]],"o":[[0,0],[2,5.67],[0,0]],"v":[[-0.94,-8.03],[-0.94,-8.02],[-1.24,8.03]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1233.839,72.2]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[2.58,5.79]],"o":[[2.18,-5.01],[0,0]],"v":[[-0.535,8.145],[-1.645,-8.145]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1219.294,72.085]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[2.74,-4.81]],"o":[[1.86,5.95],[0,0]],"v":[[-0.09,-8.025],[-1.77,8.025]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1247.969,72.955]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-10.36,1.72]],"o":[[-10.36,1.73],[0,0]],"v":[[3.795,-9.17],[6.565,7.45]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1201.674,73.11]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[89.82,6.06],[5.16,0.17],[5.24,0.02],[4.15,-0.07]],"o":[[-13.75,-27.12],[-4.83,-0.32],[-4.93,-0.18],[-3.97,-0.03],[0,0]],"v":[[89.84,32.59],[-47.43,-31.51],[-62.41,-32.26],[-77.66,-32.56],[-89.84,-32.5]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1295.309,96.44]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[60.81,4.09],[4.61,0.12],[4.68,-0.07],[3.54,-0.15]],"o":[[-15.72,-30],[-4.46,-0.3],[-4.54,-0.12],[-3.47,0.04],[0,0]],"v":[[80.59,27.725],[-42.63,-26.945],[-56.23,-27.575],[-70.07,-27.655],[-80.59,-27.365]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1288.829,107.925]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-13.75,-27.12],[0,0],[60.81,4.09],[1.86,5.95]],"o":[[0,0],[-15.72,-30],[2.74,-4.81],[89.82,6.06]],"v":[[69.475,28.74],[53.745,35.36],[-69.475,-19.31],[-67.795,-35.36]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1315.674,100.29]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[2.18,-5.01],[0,0],[3.54,-0.15],[-10.36,1.73],[-3.97,-0.03]],"o":[[2.58,5.79],[0,0],[-3.47,0.04],[-10.36,1.72],[4.15,-0.07],[0,0]],"v":[[9.625,-9.125],[10.735,7.165],[10.735,7.205],[0.215,7.495],[-2.555,-9.125],[9.625,-9.185]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1208.024,73.065]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.83,-0.32],[2.74,-4.81],[4.61,0.12],[0,0],[2,5.67]],"o":[[1.86,5.95],[-4.46,-0.3],[0,0],[2.48,-5],[5.16,0.17]],"v":[[6.71,-7.65],[5.03,8.4],[-8.57,7.77],[-8.57,7.65],[-8.27,-8.4]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1241.169,72.58]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.93,-0.18],[2.48,-5],[0,0],[4.68,-0.07],[0,0],[2.58,5.79],[0,0]],"o":[[2,5.67],[0,0],[-4.54,-0.12],[0,0],[2.18,-5.01],[0,0],[5.24,0.02]],"v":[[6.535,-7.935],[6.235,8.115],[6.235,8.235],[-7.605,8.155],[-7.605,8.115],[-8.715,-8.175],[-8.715,-8.235]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1226.364,72.115]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":44,"op":50,"st":0},{"ind":22,"ty":4,"nm":"m","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-12.47,67.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.462,-5.128],[-0.013,-0.349],[2.171,-0.112],[0.287,5.602],[0,0.45],[-1.984,0.087]],"o":[[0.037,0.337],[0.287,5.602],[-2.17,0.113],[-0.025,-0.474],[0,-4.965],[2.034,-0.1]],"v":[[3.73,-1.229],[3.805,-0.206],[0.386,10.136],[-4.055,0.193],[-4.092,-1.192],[-0.649,-10.149]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1053.325,418.603]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.287,-5.602],[2.171,-0.112],[0.287,5.601],[-2.184,0.112]],"o":[[0.287,5.602],[-2.171,0.112],[-0.287,-5.602],[2.17,-0.112]],"v":[[3.93,-0.206],[0.524,10.149],[-3.93,0.206],[-0.511,-10.149]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1027.587,422.096]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.137,8.271],[-2.358,5.477],[-1.547,0.449],[-0.4,0],[-1.273,-1.847],[-0.698,-8.01],[0.586,-5.365]],"o":[[-2.982,-6.749],[-0.137,-8.434],[0.686,-1.572],[0.399,-0.113],[2.071,0],[3.955,5.751],[0.537,5.938],[0,0]],"v":[[-5.801,24.515],[-10.367,0.911],[-6.911,-21.109],[-3.281,-24.352],[-2.083,-24.515],[3.319,-21.258],[9.968,0.325],[9.744,17.666]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1053.138,416.55]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.424,7.66],[-2.658,6.151],[-1.559,0.449],[-0.399,0],[-1.26,-1.847],[-0.524,-8.746],[1.148,-6.063]],"o":[[-2.982,-6.101],[-0.537,-9.332],[0.673,-1.572],[0.399,-0.113],[2.058,0],[4.33,6.262],[0.474,7.71],[0,0]],"v":[[-5.153,24.802],[-10.168,3.331],[-6.812,-21.396],[-3.182,-24.639],[-1.984,-24.802],[3.405,-21.545],[10.23,2.196],[8.907,23.704]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1027.637,419.332]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.036,8.982],[1.909,-1.559],[-0.112,0.499],[-1.971,2.92],[-0.274,-0.149],[-0.212,-14.971]],"o":[[0.2,-12.014],[-2.033,1.984],[-0.262,-3.007],[0.063,-0.312],[3.693,-0.536],[0.449,0.262],[0,0]],"v":[[-1.004,19.742],[-3.039,-9.762],[-10.161,-1.392],[-10.885,-11.496],[-4.573,-21.079],[7.492,-22.676],[10.997,22.824]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1001.095,419.988]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-17.404,1.036],[-7.723,0.449],[-4.204,0.287],[-0.199,-11.166]],"o":[[9.731,-1.21],[8.92,-0.536],[6.312,-0.349],[3.518,18.789],[0,0]],"v":[[-45.792,-14.684],[-1.054,-18.115],[24.447,-19.587],[40.515,-20.548],[45.792,20.548]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1026.296,406.046]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,-0.062],[-1.048,-8.583],[0.087,-2.27]],"o":[[0.013,0.062],[1.435,8.421],[0.687,5.664],[0,0]],"v":[[-2.651,-20.329],[-2.626,-20.142],[1.528,7.754],[2.564,20.329]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[981.982,411.691]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-10.542,-17.217]],"o":[[1.647,20.111],[0,0]],"v":[[-9.319,-28.514],[9.319,28.514]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1082.319,362.05]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-13.711,-19.325]],"o":[[1.335,23.667],[0,0]],"v":[[-11.59,-33.111],[11.59,33.111]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1077.004,366.934]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.886,-20.972]],"o":[[4.629,22.456],[0,0]],"v":[[-2.315,-31.196],[0.155,31.196]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[961.535,374.276]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.163,-15.108],[0,0]],"o":[[2.907,14.859],[0,0],[0,0]],"v":[[-1.516,-19.706],[1.353,19.606],[1.353,19.706]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[951.267,364.982]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.584,14.41]],"o":[[51.662,64.574],[0,0]],"v":[[-39.479,-28.607],[37.896,-35.967]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1004.426,369.192]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.345,21.433]],"o":[[40.222,51.961],[0,0]],"v":[[-32.73,-24.222],[32.73,-27.739]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1003.253,358.931]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.15,-0.786],[-6.25,0.2],[0.399,6.326],[0.2,0.761],[0.012,0.05],[-0.012,-0.05],[-0.25,-0.761],[-5.526,0.686],[0.461,5.714]],"o":[[0,0.798],[1.135,5.565],[6.276,-0.212],[-0.05,-0.785],[-0.012,-0.05],[0,0.05],[0.025,0.785],[1.672,5.465],[5.776,-0.711],[0,0]],"v":[[-22.961,-4.753],[-22.724,-2.383],[-10.336,7.872],[-0.231,-5.502],[-0.617,-7.834],[-0.655,-7.984],[-0.643,-7.834],[-0.231,-5.502],[13.443,4.055],[22.5,-8.072]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[837.613,133.101]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[-0.012,0.012],[0,0]],"v":[[0.006,-0.013],[0.006,0.013]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[860.107,125.017]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.137,-0.536],[0.125,0.549]],"o":[[-0.05,-0.549],[0.037,0.562]],"v":[[0.131,0.823],[-0.131,-0.823]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[859.982,124.181]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-5.751,0.436],[-0.499,5.664],[0.038,0.562],[-0.05,-0.537],[-5.365,0.387],[0.15,4.978],[0.163,0.649],[-0.15,-0.599],[-6.139,0.337],[-0.324,5.464],[0.063,0.636]],"o":[[1.31,5.564],[5.764,-0.425],[0.05,-0.537],[-0.062,0.562],[0.449,5.127],[5.264,-0.374],[-0.025,-0.624],[0.038,0.662],[1.073,4.74],[4.778,-0.275],[0.038,-0.574],[0,0]],"v":[[-32.63,-2.364],[-19.968,7.118],[-8.54,-3.861],[-8.528,-5.508],[-8.54,-3.861],[2.339,4.772],[11.708,-4.784],[11.433,-6.681],[11.708,-4.784],[23.186,2.427],[32.592,-5.745],[32.568,-7.554]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[892.743,127.394]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1.51,-2.408],[6.936,-2.396],[-0.088,-4.915],[4.977,0.062],[3.606,-3.318],[-5.827,1.073],[5.277,-2.283],[-8.596,-1.697],[-6.114,0.948],[-5.726,-2.694],[-8.571,-3.243],[3.58,-3.955],[-5.777,-3.219],[4.067,-1.148]],"o":[[-6.413,-3.581],[-4.754,1.646],[-4.804,-1.272],[-4.966,-0.05],[2.233,-5.339],[-4.966,-2.907],[6.5,-5.689],[1.372,-5.863],[6.262,-0.961],[6.238,-6.512],[-5.377,0.649],[6.574,-0.449],[-4.255,0.062],[-4.067,1.148]],"v":[[18.651,6.755],[-2.707,4.859],[-11.989,15.238],[-26.648,12.619],[-40.397,17.197],[-27.197,6.705],[-43.865,5.671],[-19.425,-0.793],[-5.539,-11.072],[13.024,-8.391],[38.288,-13.954],[24.228,-6.743],[43.865,-2.814],[30.578,-1.379]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[912.349,159.082]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.449,-0.936],[-22.631,1.46],[-7.485,-0.012],[0,0]],"o":[[0,0],[1.996,4.105],[8.558,7.136],[11.166,0.025],[0,0]],"v":[[-38.456,-12.208],[-37.846,-10.685],[-5.783,2.901],[19.081,12.182],[38.456,7.466]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[963.181,132.334]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-4.753,9.107],[6.912,13.836],[2.046,8.084],[0.137,0.411],[0,0]],"o":[[0,0],[4.579,-8.771],[19.313,-6.799],[-0.349,-1.385],[-0.05,-0.15],[0,0]],"v":[[-15.601,44.694],[-4.111,30.111],[-3.712,-4.622],[9.912,-41.712],[9.126,-44.47],[9.051,-44.694]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[804.864,172.456]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1.41,3.668],[19.05,32.337],[40.684,-11.253],[-18.377,-38.537],[-4.341,-8.795],[-5.177,-12.488],[-4.579,-11.477],[-2.657,-6.038],[-0.324,-0.711],[-9.681,-6.201],[-5.589,-1.934],[-4.117,-0.736],[-3.519,-0.174],[-4.715,0.612],[-3.593,1.01],[-1.56,0.562],[-3.406,2.183],[-2.881,2.844],[-3.655,10.355],[-0.723,3.269],[5.489,15.645]],"o":[[-12.625,-32.923],[-20.822,-35.307],[-47.046,12.999],[4.728,7.673],[5.951,12.077],[5.54,13.349],[3.144,7.872],[0.337,0.748],[4.529,10.018],[4.828,3.094],[3.88,1.372],[3.431,0.649],[4.703,0.25],[3.668,-0.474],[1.584,-0.449],[3.892,-1.397],[3.406,-2.171],[7.399,-7.298],[1.098,-3.106],[3.443,-15.37],[-1.372,-3.917]],"v":[[93.076,18.676],[47.365,-70.413],[-52.878,-120.628],[-85.04,-28.258],[-71.442,-3.369],[-54.737,33.959],[-39.541,71.81],[-30.833,92.857],[-29.86,95.028],[-7.853,119.63],[7.842,127.215],[19.868,130.396],[30.311,131.631],[44.495,131.107],[55.411,128.887],[60.127,127.377],[71.08,121.988],[80.525,114.44],[97.229,87.467],[99.974,77.898],[97.243,30.029]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[992.162,312.777]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-52.473,25.813],[9.956,24.44],[27.634,44.239],[18.502,-2.532],[7.648,-24.839],[-5.551,-18.152],[-9.581,-18.826]],"o":[[22.906,57.189],[53.346,-29.642],[-6.774,-18.801],[-42.28,-67.693],[-25.762,3.531],[-5.577,18.128],[7.548,24.664],[10.717,21.046]],"v":[[-57.956,82.414],[70.657,128.999],[103.455,5.926],[52.966,-87.118],[-66.839,-146.953],[-118.426,-104.285],[-116.081,-52.037],[-83.669,14.934]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1000.297,322.459]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[65.173,27.584]],"o":[[4.104,-129.161],[0,0]],"v":[[85.721,159.689],[-89.825,-159.689]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1072.088,304.481]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[11.952,33.335],[38.75,35.231]],"o":[[-3.531,-44.476],[-22.306,-62.229],[0,0]],"v":[[63.776,138.923],[38.375,16.949],[-63.776,-138.923]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[864.629,341.54]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[8.769,-0.587],[14.896,-0.986],[0.038,0],[5.963,-0.399],[34.994,-2.271],[7.647,-0.499],[9.943,-0.649],[2.907,-0.187],[-0.487,14.06],[0.062,4.965],[0.087,2.433],[23.729,48.481],[29.704,29.218],[3.069,2.845],[27.584,13.649],[-46.087,6.164]],"o":[[-0.587,20.548],[-14.846,0.985],[-0.037,0.012],[-5.277,0.349],[-30.229,2.008],[-8.109,0.536],[-11.915,0.773],[-23.404,1.509],[8.147,-2.844],[0.212,-6.2],[-0.025,-2.433],[-1.797,-53.708],[-19.375,-39.623],[-3.032,-2.981],[-28.894,-26.798],[0,0],[0,0]],"v":[[242.177,154.847],[224.602,184.789],[179.989,187.747],[179.864,187.758],[162.96,188.882],[59.673,195.643],[35.957,197.19],[2.908,199.336],[-39.921,202.106],[-29.154,170.641],[-29.104,153.375],[-29.279,146.089],[-69.576,-10.719],[-145.927,-114.879],[-155.072,-123.612],[-242.178,-185.08],[-136.499,-202.106]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[935.003,332.216]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-4.878,0.623],[-7.697,0.848],[0,0],[-7.56,0.686],[-7.96,0.511],[-6.475,0.237],[-6.724,-0.05],[-10.171,-1.561]],"o":[[4.778,-0.636],[7.585,-0.948],[0,0],[7.623,-0.848],[8.234,-0.761],[6.824,-0.449],[7.348,-0.262],[6.961,0.075],[0,0]],"v":[[-82.507,5.115],[-68.01,3.219],[-45.03,0.499],[-45.005,0.499],[-22.187,-1.809],[2.166,-3.73],[22.152,-4.753],[43.311,-5.065],[82.507,-2.441]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[882,124.768]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-5.191,-4.241],[-10.742,-20.323],[-0.612,-96.038],[0.063,-3.206],[0.35,-5.364]],"o":[[14.268,4.61],[52.46,32.985],[21.733,41.145],[0.025,3.132],[-0.112,5.178],[0,0]],"v":[[-106.576,-182.624],[-69.408,-165.199],[43.859,-44.509],[106.55,157.286],[106.487,166.805],[105.801,182.624]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1071.083,304.95]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.445,0.162],[13.062,-0.387],[33.46,4.841],[0.112,-1.735]],"o":[[2.358,-0.2],[-6.761,-3.967],[0,0],[-0.374,7.049],[0,0]],"v":[[28.882,20.728],[36.104,20.18],[6.886,13.967],[-35.256,-20.728],[-36.104,-5.819]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[942.078,508.678]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.684,5.839],[18.165,5.515],[12.9,-1.859],[0,0],[15.919,1.634],[-10.28,0.636],[3.643,0.037],[-12.937,9.419],[-14.272,2.059],[0,0],[-17.055,10.006],[3.132,2.358]],"o":[[0,0],[0,0],[-9.606,1.372],[0,0],[0,0],[0,0],[0,0],[0,0],[14.272,-2.046],[0,0],[0,0],[0,0]],"v":[[47.807,-16.038],[13.885,-16.212],[-1.597,-5.021],[-16.58,-11.833],[-49.978,-2.563],[-37.153,4.835],[-47.183,11.048],[-17.541,6.794],[3.281,13.318],[19.35,0.368],[49.978,-2.9],[38.25,-6.543]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1046.863,493.732]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0.013],[19.862,-2.433],[25.226,8.246],[7.772,-0.4]],"o":[[-0.012,-0.012],[-7.174,-8.546],[0,0],[-5.514,-1.809],[0,0]],"v":[[43.784,25.014],[43.771,24.989],[4.797,12.351],[-23.948,-22.269],[-43.784,-24.614]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[950.905,502.87]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[9.394,-3.668],[3.256,-24.402],[9.182,-18.489]],"o":[[0,0],[-10.729,4.179],[0,0],[0,0]],"v":[[38.906,-28.676],[21.302,-25.93],[-4.947,12.818],[-38.906,29.599]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1136.831,490.925]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[7.91,-0.761],[0.387,-0.025],[7.623,1.821],[2.021,0.849],[3.955,6.337],[2.669,10.591]],"o":[[-3.369,-0.287],[-0.374,0.037],[-7.173,0.637],[-2.133,-0.499],[-5.864,-2.445],[0,0],[0,0]],"v":[[34.645,13.492],[16.742,15.239],[15.594,15.338],[-7.324,14.303],[-13.561,12.294],[-28.657,-0.493],[-34.645,-16.124]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1049.807,318.273]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-3.718,0.512],[-2.969,1.048],[-1.697,1.123],[-1.435,2.396],[-0.487,2.757],[1.172,15.257]],"o":[[3.443,-0.299],[3.243,-0.449],[1.996,-0.686],[2.233,-1.435],[1.21,-1.958],[0,0],[0,0]],"v":[[-17.902,19.961],[-6.998,18.888],[2.445,16.742],[8.01,14.06],[13.586,8.383],[16.156,1.335],[16.731,-19.961]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[956.725,326.201]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-1.939,-3.751],[1.94,3.751]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[973.699,287.83]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.202,-3.787],[2.202,3.787]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[992.3,286.011]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,0.374],[0,0],[0,0],[0,0],[7.785,-5.065],[0.499,-0.262],[1.023,-0.386],[0.025,-0.012],[2.345,-0.387],[1.934,0.062],[1.31,0.224]],"o":[[-10.604,-3.156],[0,0],[0,0],[0,0],[-0.437,0.287],[-0.836,0.449],[-0.012,0],[-1.734,0.637],[-2.134,0.349],[-1.435,-0.025],[-1.335,-0.212]],"v":[[-7.785,7.117],[-24.328,-3.999],[7.66,-7.03],[22.743,-8.452],[16.543,4.335],[15.158,5.147],[12.388,6.393],[12.326,6.418],[6.238,7.978],[0.137,8.39],[-3.98,8.004]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[982.438,289.242]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.637,3.113],[-1.636,-3.113]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[992.466,300.133]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.884,5.732],[-1.884,-5.732]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[961.004,268.806]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[6.806,7.803],[-6.805,-7.803]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[999.473,264.328]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.087,-0.324]],"o":[[0,0],[0,0]],"v":[[-0.081,-0.25],[0.081,0.25]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[996.042,238.21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,-0.012],[9.108,-6.488]],"o":[[0.012,0.012],[0.749,2.358],[0,0]],"v":[[1.547,-9.133],[1.559,-9.095],[-4.554,9.133]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[994.589,247.604]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.467,-0.624],[4.529,6.524],[0.013,0.025],[0,0],[-1.771,1.822],[-15.856,-0.723],[-5.838,-2.171],[-0.436,-0.174],[-0.025,-0.012],[-0.149,-0.063]],"o":[[-3.816,2.789],[-18.651,2.595],[-0.025,-0.025],[-2.832,-4.092],[0,0],[4.529,-4.603],[4.804,0.225],[0.425,0.149],[0.013,0.013],[0.137,0.062],[0,0]],"v":[[26.189,10.884],[10.227,16.337],[-22.422,4.485],[-22.472,4.41],[-26.189,-3.2],[-23.52,-6.307],[7.083,-18.209],[23.052,-14.79],[24.337,-14.304],[24.4,-14.279],[24.836,-14.104]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[963.415,246.039]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[50.953,-4.398],[25.989,-2.264],[-17.289,1.429],[-50.953,4.398]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[980.327,274.832]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[13.605,-1.285],[-13.605,1.285]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[955.558,220.494]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.633,-4.554]],"o":[[1.984,0.936],[0,0]],"v":[[-4.154,-3.35],[4.154,3.35]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[991.969,235.11]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.087,-0.163]],"o":[[0.1,0.15],[0,0]],"v":[[-0.131,-0.231],[0.131,0.231]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[996.279,238.74]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.518,-17.179],[1.668,-17.042],[36.612,14.809],[70.157,42.318],[20.032,47.021],[17.075,47.595],[-21.724,50.34],[-70.157,54.045],[-65.755,24.016],[-59.152,-12.563],[-54.486,-33.635],[-49.957,-54.045],[-25.517,-36.541],[-8.488,-24.34]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[995.13,255.127]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.187,-4.117],[5.016,-0.15],[0.187,4.117],[-5.027,0.162]],"o":[[0.187,4.117],[-5.027,0.15],[-0.2,-4.117],[5.016,-0.15]],"v":[[9.095,-0.275],[0.349,7.461],[-9.082,0.287],[-0.337,-7.46]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[744.487,154.473]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.125,-0.125],[8.059,-1.197],[-0.574,0]],"o":[[0.137,0.112],[-8.596,1.16],[-1.809,-0.386],[1.759,0]],"v":[[13.593,-1.959],[13.992,-1.596],[-11.06,1.959],[-13.417,1.572]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[785.502,131.479]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-8.596,1.16],[17.503,-5.414],[9.17,-5.576],[0,0],[-1.734,16.655],[0,0],[1.185,11.153],[3.717,0.798]],"o":[[7.386,7.024],[9.606,16.493],[0,0],[0.399,0.187],[1.759,-17.017],[0,0],[-0.786,-7.51],[8.059,-1.198]],"v":[[14.708,-39.155],[4.029,-3.699],[-4.48,39.155],[-19.151,25.669],[-7.998,13.318],[-22.095,-11.334],[0.798,-24.833],[-10.343,-35.599]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[784.785,169.038]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.349,-1.385],[19.313,-6.799],[4.579,-8.771],[0,0],[0,0],[3.069,2.845],[0,0],[9.606,16.493],[7.386,7.024],[-4.878,0.624]],"o":[[2.046,8.084],[6.912,13.836],[-4.753,9.107],[0,0],[-3.032,-2.982],[0,0],[9.17,-5.576],[17.503,-5.414],[4.778,-0.636],[0.137,0.412]],"v":[[14.578,-41.919],[0.954,-4.829],[0.555,29.904],[-10.935,44.488],[-11.122,44.676],[-20.267,35.942],[-19.893,35.53],[-11.384,-7.324],[-0.705,-42.78],[13.792,-44.676]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[800.198,172.662]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[5.776,-0.711],[1.672,5.464],[0.2,0.761],[-7.561,0.686],[0,0],[-0.137,-0.536]],"o":[[0.462,5.714],[-5.527,0.687],[-0.05,-0.786],[7.623,-0.848],[0,0],[0.037,0.562],[-0.012,0.013]],"v":[[11.328,-5.371],[2.271,6.755],[-11.403,-2.801],[-11.79,-5.134],[11.029,-7.442],[11.066,-7.043],[11.328,-5.396]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[848.785,130.401]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-3.531,-44.476],[0,0],[7.773,-0.399],[0,0],[23.729,48.481],[29.705,29.218],[0,0],[-4.753,9.107],[0,0],[-22.306,-62.229]],"o":[[0,0],[-5.514,-1.809],[0,0],[-1.796,-53.708],[-19.374,-39.623],[0,0],[0,0],[0,0],[38.75,35.231],[11.952,33.335]],"v":[[69.664,138.88],[68.217,139.017],[48.38,136.672],[46.983,136.722],[6.686,-20.086],[-69.665,-124.246],[-69.478,-124.433],[-57.988,-139.017],[-57.888,-138.967],[44.263,16.905]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[858.741,341.584]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.776,-3.219],[4.067,-1.148],[1.51,-2.408],[6.937,-2.396],[-0.087,-4.915],[4.978,0.063],[3.605,-3.318],[-5.826,1.073],[5.277,-2.284],[-8.596,-1.697],[-6.113,0.948],[-5.726,-2.694],[-8.57,-3.244],[3.581,-3.955]],"o":[[-4.254,0.062],[-4.067,1.147],[-6.412,-3.581],[-4.753,1.646],[-4.803,-1.272],[-4.965,-0.049],[2.233,-5.34],[-4.965,-2.907],[6.499,-5.689],[1.372,-5.864],[6.263,-0.961],[6.238,-6.512],[-5.377,0.649],[6.575,-0.449]],"v":[[43.865,-2.813],[30.577,-1.378],[18.65,6.756],[-2.708,4.86],[-11.99,15.239],[-26.648,12.619],[-40.397,17.198],[-27.198,6.706],[-43.865,5.671],[-19.424,-0.792],[-5.54,-11.072],[13.024,-8.39],[38.287,-13.954],[24.227,-6.743]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[912.349,159.082]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.886,-20.972],[0,0],[3.144,7.872],[2.907,14.858],[0,0],[-2.969,1.048]],"o":[[4.629,22.456],[0,0],[-2.657,-6.038],[0.163,-15.108],[0,0],[3.244,-0.449],[0,0]],"v":[[2.433,-31.208],[4.903,31.183],[4.541,31.345],[-4.167,10.299],[-7.036,-29.012],[-7.061,-29.199],[2.383,-31.345]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[956.788,374.289]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-2.832,-4.092],[-0.025,-0.025],[-8.995,-1.697],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.013,0.025],[3.306,5.514],[0,0],[0,0]],"v":[[17.242,13.642],[17.392,15.364],[-17.391,18.333],[-16.272,18.246],[-9.668,-18.333],[-8.421,-18.059],[-4.703,-10.449],[-4.653,-10.374],[13.624,1.441],[13.474,2.177]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[945.647,260.897]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-2.919,-0.237],[4.529,-4.604],[-12.576,1.647]],"o":[[-15.857,-0.724],[2.558,-3.893],[3.443,-0.449]],"v":[[15.302,-5.589],[-15.302,6.313],[5.758,-5.327]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[1,0.878,0.839,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[955.197,233.419]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[14.485,7.154],[14.036,7.779],[-13.174,10.349],[-14.484,10.062],[-9.955,-10.349]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[955.128,211.431]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[7.987,-1.328],[4.044,0.58],[1.21,0.374],[0,0]],"o":[[-2.032,3.204],[-4.038,1.029],[-1.335,-0.212],[-4.467,-1.559],[0,0]],"v":[[18.357,-4.305],[5.813,3.276],[-6.312,3.664],[-10.117,2.778],[-18.357,-1.227]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[984.77,293.581]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.435,-0.025],[2.233,0.786],[-1.335,-0.212]],"o":[[-3.019,0.075],[1.21,0.375],[1.31,0.225]],"v":[[3.961,0.599],[-3.961,-0.674],[-0.156,0.212]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[978.614,297.033]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.564,-2.745],[7.822,6.113],[5.302,4.017],[-0.773,-0.013],[-6.849,-2.133]],"o":[[-7.96,0.961],[-6.799,1.909],[0.636,0.025],[10.068,0.785],[5.502,1.709]],"v":[[22.188,5.077],[-3.562,-1.348],[-22.188,-6.039],[-20.055,-5.976],[5.57,-1.66]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[964.953,126.489]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[4.804,0.225],[3.444,-0.449],[2.558,-3.892],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[-5.838,-2.171],[-2.919,-0.237],[-12.575,1.647],[-1.771,1.822],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[25.332,0.074],[25.157,0.537],[9.188,-2.882],[-0.356,-2.62],[-21.415,9.02],[-24.084,12.126],[-25.332,11.852],[-20.666,-9.219],[-19.356,-8.934],[7.853,-11.503],[8.303,-12.126]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[961.31,230.712]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-8.344,-4.205],[0.125,0],[5.502,1.709],[10.068,0.786],[0,0]],"o":[[0,0],[-0.125,0.012],[-5.564,-2.745],[-6.849,-2.133],[3.007,0.062],[0,0]],"v":[[-1.572,-3.419],[21.059,5.49],[21.059,5.526],[4.441,-1.211],[-21.184,-5.526],[-10.174,-4.391]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[966.082,126.04]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-2.57,-1.747],[3.494,-6.15],[1.447,-1.322],[2.109,-0.848],[-0.175,2.944],[0,0],[0,0],[-4.192,-1.085]],"o":[[3.418,3.481],[-1.197,2.371],[-1.472,1.048],[-7.273,1.634],[2.969,-2.233],[0,0],[4.716,-9.519],[4.179,1.772]],"v":[[10.087,-8.889],[10.648,5.695],[6.656,11.172],[1.315,14.029],[-13.13,6.693],[-4.036,0.992],[-14.141,-5.009],[0.007,-14.578]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[983.38,245.564]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-0.012],[9.107,-6.487],[-1.198,2.371],[3.418,3.481],[-0.773,-0.499],[0,0]],"o":[[0.749,2.358],[1.447,-1.322],[3.493,-6.15],[0.998,0.674],[0,0],[0.012,0.012]],"v":[[1.56,-8.197],[-4.554,10.031],[-0.562,4.553],[-1.122,-10.031],[1.534,-8.247],[1.547,-8.234]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[1,0.878,0.839,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[994.589,246.706]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0.749,2.358],[0.012,0.012],[-0.063,-0.037],[8.197,-7.224],[6.637,-1.247],[5.264,0.985],[3.306,5.514],[-18.651,2.595],[-2.956,1.198],[-1.697,1.547]],"o":[[0,-0.012],[0.062,0.05],[0,0],[-2.882,2.545],[-7.411,1.385],[-8.995,-1.697],[4.528,6.525],[4.466,-0.624],[1.822,-0.412],[9.107,-6.487]],"v":[[25.469,-13.212],[25.457,-13.249],[25.644,-13.125],[21.489,4.242],[7.554,10.217],[-11.409,10.617],[-29.686,-1.198],[2.963,10.654],[14.016,7.872],[19.356,5.015]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[1,0.878,0.839,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[970.679,251.721]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[1.023,-0.387],[0.025,-0.013],[4.18,-0.537],[0.761,-0.012],[1.31,0.225],[0,0],[0,0],[0,0]],"o":[[0,0],[-0.836,0.45],[-0.013,0],[-2.507,0.786],[-0.786,0.1],[-1.435,-0.025],[0,0],[0,0],[0,0],[0,0]],"v":[[9.943,-0.387],[10.979,0.674],[8.209,1.922],[8.146,1.946],[-1.722,3.755],[-4.042,3.917],[-8.159,3.531],[-8.06,2.894],[-10.979,-2.133],[7.885,-3.917]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[986.617,293.714]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.836,0.449],[-0.437,0.287],[1.635,-0.524]],"o":[[0.499,-0.262],[-1.16,0.886],[1.023,-0.387]],"v":[[0.693,-0.219],[2.077,-1.03],[-2.078,1.03]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[996.903,294.607]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.828,-3.044],[0,0],[11.165,0.025],[8.558,7.136],[1.996,4.104],[0.062,0.636],[0,0],[-6.063,-0.474],[0.637,0.025],[-6.799,1.909],[-7.96,0.96]],"o":[[0,0],[0,0],[-7.486,-0.013],[-22.631,1.459],[0.037,-0.574],[0,0],[6.961,0.075],[-0.774,-0.012],[5.303,4.017],[7.822,6.113],[4.853,2.395]],"v":[[38.182,7.629],[38.145,7.679],[18.77,12.395],[-6.094,3.113],[-38.157,-10.473],[-38.182,-12.282],[-38.182,-12.42],[-18.595,-11.609],[-20.729,-11.671],[-2.102,-6.98],[23.648,-0.555]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[963.493,132.122]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[1.472,-1.098],[0.499,-0.262],[0,0]],"o":[[0,0],[-1.061,1.996],[-0.437,0.287],[0,0],[0,0]],"v":[[-4.086,-1.909],[4.086,-2.682],[0.393,1.871],[-0.992,2.682],[-2.028,1.622]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[998.588,291.706]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-7.411,1.385],[-2.882,2.546],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[5.265,0.985],[6.637,-1.247],[0,0],[0,0]],"v":[[23.561,6.019],[23.598,6.456],[-19.68,10.149],[-19.831,8.427],[-23.597,-3.037],[-23.448,-3.773],[-4.484,-4.174],[9.451,-10.149],[9.95,-9.588]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[982.718,266.112]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.212,-14.971],[0,0],[3.88,1.373],[0,0],[1.035,8.983],[1.909,-1.56],[-0.112,0.499],[-1.971,2.919],[-0.274,-0.15]],"o":[[0,0],[-4.117,-0.736],[0,0],[0.2,-12.014],[-2.034,1.984],[-0.262,-3.007],[0.063,-0.312],[3.693,-0.537],[0.449,0.262]],"v":[[10.998,22.643],[10.936,23.006],[-1.091,19.824],[-1.004,19.561],[-3.037,-9.944],[-10.161,-1.572],[-10.885,-11.678],[-4.572,-21.259],[7.492,-22.855]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1001.095,420.168]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-17.404,1.036],[0,0],[0.399,-0.112],[0.673,-1.572],[-0.537,-9.331],[-2.982,-6.1],[0,0],[3.431,0.648],[0,0],[0.449,0.262],[3.693,-0.537],[0.062,-0.312],[-0.262,-3.006],[0,0],[1.435,8.421],[0,0]],"o":[[0,0],[-0.4,0],[-1.559,0.449],[-2.658,6.15],[0.424,7.661],[0,0],[-3.518,-0.175],[0,0],[-0.213,-14.971],[-0.275,-0.15],[-1.971,2.919],[-0.112,0.499],[0,0],[-1.048,-8.584],[0,0],[9.731,-1.21]],"v":[[22.737,-28.239],[23.149,-21.639],[21.951,-21.477],[18.321,-18.233],[14.965,6.493],[19.98,27.964],[19.967,28.239],[9.525,27.004],[9.588,26.642],[6.082,-18.857],[-5.982,-17.26],[-12.295,-7.679],[-11.571,2.426],[-18.994,3.275],[-23.149,-24.621],[-22.001,-24.808]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1002.505,416.17]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.287,-5.602],[2.171,-0.112],[0.287,5.601],[-2.184,0.112]],"o":[[0.287,5.602],[-2.171,0.112],[-0.287,-5.602],[2.17,-0.112]],"v":[[3.93,-0.206],[0.524,10.149],[-3.93,0.206],[-0.511,-10.149]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1027.587,422.096]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[8.196,-7.223],[0,0],[0,0]],"v":[[19.786,14.609],[19.324,15.108],[-5.64,17.241],[-5.677,16.806],[-19.288,1.199],[-19.787,0.637],[-15.633,-16.73],[-15.159,-17.241]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1011.955,255.326]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.864,-2.445],[0,0],[40.222,51.961],[0,0],[-0.487,2.758],[1.172,15.258],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[-2.345,21.433],[0,0],[1.21,-1.959],[0,0],[0,0],[0,0],[0,0],[2.67,10.592],[3.955,6.338]],"v":[[32.967,-13.842],[32.705,-13.217],[-32.755,-9.699],[-32.967,-9.824],[-30.397,-16.874],[-29.823,-38.169],[-29.873,-38.942],[8.926,-41.687],[11.883,-42.262],[17.871,-26.629]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1003.279,344.409]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.147,-6.063],[0,0],[4.703,0.25],[0,0],[0.424,7.66],[0,0],[-2.171,0.112],[0.287,5.601]],"o":[[0.474,7.71],[0,0],[-4.716,0.611],[0,0],[-2.981,-6.101],[0,0],[0.287,5.602],[2.171,-0.113],[0,0]],"v":[[9.962,-11.565],[8.64,9.943],[8.752,10.792],[-5.433,11.315],[-5.421,11.041],[-10.436,-10.43],[-4.248,-10.792],[0.206,-0.848],[3.612,-11.203]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1027.905,433.093]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-2.133,-0.499],[0,0],[51.662,64.575],[0,0],[-1.435,2.395],[0,0],[-2.345,21.434],[0,0]],"o":[[0,0],[1.584,14.409],[0,0],[2.233,-1.435],[0,0],[40.222,51.962],[0,0],[2.021,0.849]],"v":[[38.164,-35.288],[38.001,-34.638],[-39.374,-27.279],[-39.586,-27.602],[-34.008,-33.279],[-33.797,-33.154],[31.664,-36.672],[31.925,-37.297]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1004.32,367.864]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.012,-0.349],[2.17,-0.112],[0.287,5.602],[0,0.45],[-1.983,0.087],[-0.461,-5.128]],"o":[[0.287,5.602],[-2.171,0.113],[-0.025,-0.474],[0,-4.965],[2.034,-0.1],[0.038,0.337]],"v":[[3.805,-0.206],[0.387,10.136],[-4.055,0.193],[-4.092,-1.192],[-0.649,-10.149],[3.73,-1.229]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1053.325,418.603]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.16,0.886],[0,0],[0,0],[0,0],[-10.604,-3.156],[-3.019,0.074],[-2.133,0.349],[-1.734,0.637],[-0.013,0]],"o":[[7.785,-5.065],[0,0],[0,0],[0,0],[2.233,0.786],[1.934,0.062],[2.345,-0.387],[0.025,-0.012],[1.634,-0.524]],"v":[[3.851,4.023],[10.051,-8.764],[-5.032,-7.342],[-37.02,-4.311],[-20.477,6.806],[-12.555,8.079],[-6.454,7.666],[-0.366,6.106],[-0.303,6.082]],"c":true}},"nm":"P"},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[70.157,7.89],[20.032,12.595],[17.075,13.168],[-21.724,15.913],[-70.157,19.618],[-65.755,-10.41],[-64.533,-10.324],[-32.092,-13.293],[11.187,-16.986],[36.151,-19.119],[36.612,-19.618]],"c":true}},"nm":"P"},{"ty":"mm","mm":1,"nm":"M"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[995.13,289.554]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.699,-8.009],[0,0],[2.034,-0.1],[0,-4.965],[0,0],[-2.358,5.477],[-1.547,0.449],[-0.399,0],[-1.272,-1.846]],"o":[[0,0],[-0.461,-5.127],[-1.983,0.087],[0,0],[-0.138,-8.433],[0.687,-1.572],[0.399,-0.112],[2.071,0],[3.955,5.751]],"v":[[10.236,12.126],[4.185,12.625],[-0.194,3.706],[-3.637,12.662],[-10.099,12.713],[-6.644,-9.307],[-3.013,-12.55],[-1.816,-12.713],[3.586,-9.456]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1052.869,404.748]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.2,-11.166],[0,0],[3.406,-2.171],[0,0],[0.537,5.939],[3.955,5.752],[2.071,0],[0,0],[-4.205,0.287]],"o":[[0,0],[-2.882,2.844],[0,0],[0.586,-5.365],[-0.698,-8.009],[-1.273,-1.846],[0,0],[6.313,-0.35],[3.518,18.788]],"v":[[10.374,16.461],[10.973,17.085],[1.528,24.634],[1.167,24.085],[1.391,6.742],[-5.259,-14.84],[-10.661,-18.097],[-10.973,-23.673],[5.096,-24.634]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1061.715,410.132]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.984,0.936],[0.012,0.012],[0.424,0.15],[0,0],[0,0],[0,0],[0,0],[0,0],[1.937,0.43],[0,0],[0,0],[4.728,7.673],[-47.047,13],[-20.822,-35.306],[-12.625,-32.923],[0,0],[7.909,-0.761],[0.387,-0.025],[7.623,1.821],[2.021,0.849],[3.955,6.337],[2.67,10.592],[0,0],[0,0],[0,0],[0,0],[0.062,0.05]],"o":[[-2.632,-4.554],[-0.025,-0.012],[-0.437,-0.175],[0,0],[0,0],[0,0],[0,0],[0,0],[1.938,0.43],[0,0],[0,0],[-4.342,-8.796],[-18.377,-38.537],[40.683,-11.253],[19.051,32.337],[0,0],[-3.368,-0.287],[-0.375,0.037],[-7.174,0.636],[-2.133,-0.499],[-5.864,-2.445],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.063,-0.037],[0,0]],"v":[[9.133,-19.188],[0.824,-25.887],[0.761,-25.912],[-0.523,-26.399],[-0.35,-26.861],[-17.379,-39.061],[-41.819,-56.564],[-46.348,-36.155],[-51.014,-15.084],[-57.617,21.496],[-62.018,51.525],[-66.271,51.762],[-79.87,26.873],[-47.707,-65.498],[52.535,-15.283],[98.246,73.806],[97.46,74.118],[79.558,75.866],[78.41,75.965],[55.492,74.929],[49.254,72.921],[34.158,60.134],[28.17,44.501],[74.929,39.798],[44.75,12.288],[9.806,-19.561],[9.332,-19.051],[9.145,-19.176]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[986.991,257.647]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.048,-8.584],[0.087,-2.27],[0,0],[4.528,10.018],[0.336,0.749],[0,0],[4.629,22.456],[0,0],[-1.696,1.123],[0,0],[1.584,14.409],[0,0],[-7.173,0.636],[0,0],[-13.711,-19.325],[0,0],[7.398,-7.298],[0,0],[3.518,18.789],[6.313,-0.349],[8.92,-0.536],[9.731,-1.21]],"o":[[1.435,8.421],[0.687,5.664],[0,0],[-9.681,-6.2],[-0.325,-0.711],[0,0],[0.886,-20.972],[0,0],[1.996,-0.686],[0,0],[51.662,64.574],[0,0],[7.623,1.822],[0,0],[1.335,23.666],[0,0],[-3.655,10.354],[0,0],[-0.199,-11.165],[-4.204,0.287],[-7.722,0.449],[-17.404,1.036],[0,0]],"v":[[-44.925,9.057],[-40.771,36.954],[-39.735,49.528],[-39.972,49.915],[-61.979,25.314],[-62.952,23.142],[-62.591,22.98],[-65.061,-39.41],[-65.111,-39.548],[-59.547,-42.23],[-59.334,-41.905],[18.04,-49.267],[18.202,-49.915],[41.12,-48.879],[41.133,-48.668],[64.313,17.553],[65.111,17.754],[48.406,44.725],[47.807,44.101],[42.53,3.007],[26.461,3.967],[0.961,5.439],[-43.777,8.871]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1024.281,382.492]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-10.542,-17.217],[0,0],[1.098,-3.106],[0,0],[1.335,23.667],[0,0],[-0.374,0.038],[0,0]],"o":[[0,0],[-0.724,3.269],[0,0],[-13.711,-19.325],[0,0],[0.387,-0.025],[0,0],[1.647,20.111]],"v":[[12.869,23.685],[13.368,23.798],[10.623,33.366],[9.825,33.168],[-13.355,-33.055],[-13.368,-33.267],[-12.22,-33.366],[-5.77,-33.342]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1078.769,366.878]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.372,-3.917],[3.443,-15.37],[0,0],[1.647,20.111],[0,0],[-3.368,-0.287],[0,0]],"o":[[5.489,15.645],[0,0],[-10.542,-17.216],[0,0],[7.91,-0.761],[0,0],[1.41,3.668]],"v":[[8.341,-18.258],[11.073,29.611],[10.573,29.499],[-8.065,-27.528],[-14.516,-27.552],[3.388,-29.299],[4.173,-29.611]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1081.064,361.064]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[14.272,-2.046],[0,0],[0,0],[0,0],[0,0],[0,0],[-9.606,1.372],[0,0],[0,0]],"o":[[3.131,2.358],[-17.054,10.005],[0,0],[-14.273,2.059],[-12.937,9.419],[3.643,0.037],[-10.28,0.636],[15.919,1.634],[0,0],[12.9,-1.859],[18.165,5.515],[-1.685,5.839]],"v":[[38.25,-6.544],[49.977,-2.901],[19.349,0.368],[3.281,13.317],[-17.542,6.793],[-47.184,11.047],[-37.153,4.835],[-49.978,-2.564],[-16.581,-11.834],[-1.598,-5.022],[13.885,-16.212],[47.807,-16.038]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1046.863,493.732]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-14.846,0.986],[0.012,-0.3],[26.436,-1.747],[-0.225,0.174]],"o":[[-0.013,0.287],[-0.05,1.197],[0.212,-0.187],[14.896,-0.986]],"v":[[22.631,-1.753],[22.594,-0.867],[-22.631,1.753],[-21.982,1.204]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1136.975,518.758]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.391,2.757],[4.624,-10.238],[4.778,-0.312]],"o":[[-0.599,9.22],[-4.073,3.633],[1.048,-17.615]],"v":[[8.64,-14.715],[2.661,8.661],[-8.64,14.715]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1168.245,502.29]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-32.661,6.088],[0.349,-5.364],[1.048,-17.616],[14.896,-0.985],[-13.699,1.385]],"o":[[-0.112,5.177],[-4.392,2.757],[-14.846,0.985],[6.2,-5.177],[0,0]],"v":[[31.289,-24.103],[30.603,-8.284],[13.324,21.147],[-31.289,24.103],[-2.158,13.374]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1146.282,495.859]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.162,0.025],[0,0],[1.011,-0.649],[-0.112,5.177]],"o":[[0,0],[0,0],[0.35,-5.365],[0.163,-0.038]],"v":[[0.362,-7.96],[0.811,7.012],[-0.811,7.96],[-0.125,-7.859]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1177.695,479.615]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.475,7.71],[4.329,6.263],[2.058,0],[0,0],[-7.722,0.45],[0,0],[0.399,-0.112],[0.686,-1.572],[-0.137,-8.434],[-2.982,-6.75],[0,0],[3.668,-0.474]],"o":[[1.148,-6.063],[-0.524,-8.746],[-1.26,-1.846],[0,0],[8.92,-0.537],[0,0],[-0.399,0],[-1.547,0.449],[-2.358,5.477],[0.138,8.271],[0,0],[-3.593,1.01],[0,0]],"v":[[-1.603,27.865],[-0.281,6.357],[-7.105,-17.385],[-12.494,-20.641],[-12.906,-27.24],[12.594,-28.713],[12.906,-23.136],[11.709,-22.974],[8.078,-19.73],[4.622,2.29],[9.189,25.894],[9.426,26.493],[-1.491,28.713]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1038.148,415.172]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[9.394,-3.668],[0,0],[65.173,27.584],[0,0],[0,0],[0,0],[-10.742,-20.323],[-0.611,-96.039]],"o":[[0,0],[0,0],[4.105,-129.161],[0,0],[11.166,0.025],[0,0],[52.461,32.986],[21.732,41.145],[0,0]],"v":[[95.789,159.876],[78.186,162.622],[77.861,161.797],[-97.685,-157.582],[-97.685,-157.856],[-78.31,-162.572],[-78.273,-162.622],[34.995,-41.932],[97.685,159.865]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1079.948,302.373]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-6.725,-0.05],[0,0],[0.037,-0.574],[4.778,-0.275],[1.073,4.741],[0.162,0.648]],"o":[[7.348,-0.262],[0,0],[0.062,0.637],[-0.325,5.465],[-6.138,0.337],[-0.025,-0.624],[0,0]],"v":[[-10.61,-4.891],[10.549,-5.202],[10.549,-5.065],[10.573,-3.256],[1.167,4.916],[-10.312,-2.295],[-10.586,-4.191]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[914.763,124.905]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[1.21,-1.959],[2.234,-1.434],[1.996,-0.686],[3.244,-0.449],[3.444,-0.299],[0,0],[5.951,12.076],[0,0],[0,0]],"o":[[1.173,15.258],[-0.486,2.757],[-1.435,2.395],[-1.696,1.123],[-2.969,1.048],[-3.718,0.512],[0,0],[-5.177,-12.488],[0,0],[0,0],[0,0]],"v":[[25.781,-19.862],[25.207,1.434],[22.637,8.483],[17.06,14.16],[11.496,16.842],[2.052,18.987],[-8.852,20.061],[-10.249,20.634],[-26.954,-16.693],[-26.467,-16.93],[25.731,-20.634]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[947.674,326.101]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-15.857,-0.724],[-3.618,-1.534],[4.716,-9.519],[0,0],[2.97,-2.234],[-7.273,1.635],[4.466,-0.623],[4.529,6.525],[0.012,0.024],[0.462,2.52],[-1.285,1.959]],"o":[[5.003,0.387],[-4.191,-1.085],[0,0],[0,0],[-0.174,2.944],[-2.956,1.198],[-18.651,2.595],[-0.025,-0.025],[-1.385,-2.333],[0,0],[4.529,-4.604]],"v":[[9.064,-18.208],[21.951,-15.052],[7.804,-5.483],[17.909,0.518],[8.814,6.22],[23.261,13.555],[12.208,16.337],[-20.441,4.485],[-20.491,4.411],[-23.261,-2.95],[-21.539,-6.306]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[1,0.878,0.839,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[961.434,246.038]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-3.718,0.512],[0,0],[0.162,-15.108],[5.539,13.349]],"o":[[3.443,-0.299],[0,0],[2.907,14.859],[-4.578,-11.477],[0,0]],"v":[[-6.281,-18.676],[4.622,-19.749],[4.647,-19.562],[7.516,19.749],[-7.679,-18.102]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[945.104,364.838]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-6.762,-3.967],[2.357,-0.2],[9.943,-0.649],[0,0],[-0.012,0.324],[-0.375,7.049],[0,0]],"o":[[-2.445,0.162],[-11.915,0.773],[-5.077,-20.548],[0,0],[0.113,-1.735],[33.46,4.841],[13.062,-0.387]],"v":[[36.117,19.106],[28.894,19.655],[-4.155,21.801],[-36.118,-6.394],[-36.093,-6.892],[-35.244,-21.801],[6.899,12.894]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[942.066,509.751]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[3.892,-1.397],[1.585,-0.45],[0,0],[0.137,8.271],[0,0],[-0.025,-0.474],[-2.171,0.112],[0.286,5.602],[0.037,0.337],[0,0],[0.586,-5.365],[0,0]],"o":[[-1.56,0.561],[0,0],[-2.982,-6.75],[0,0],[0,0.449],[0.287,5.602],[2.171,-0.113],[-0.013,-0.349],[0,0],[0.537,5.939],[0,0],[-3.406,2.184]],"v":[[-0.917,10.885],[-5.633,12.395],[-5.87,11.796],[-10.436,-11.808],[-3.974,-11.858],[-3.936,-10.474],[0.505,-0.53],[3.924,-10.873],[3.849,-11.896],[9.899,-12.395],[9.675,4.947],[10.037,5.495]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1053.206,429.27]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-11.914,0.773],[0.324,1.36],[0.037,0.112]],"o":[[-17.74,1.497],[-0.025,-0.113],[9.943,-0.649]],"v":[[16.524,-1.922],[-16.437,0.562],[-16.524,0.225]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[954.436,531.328]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.15,-0.599],[5.265,-0.374],[0.449,5.128],[0.038,0.562],[0,0],[-6.475,0.237],[0,0]],"o":[[0.15,4.978],[-5.365,0.387],[0.05,-0.536],[0,0],[6.825,-0.449],[0,0],[0.037,0.661]],"v":[[10.068,-3.674],[0.699,5.882],[-10.18,-2.751],[-10.168,-4.398],[-10.218,-5.246],[9.769,-6.269],[9.794,-5.57]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[894.384,126.283]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.067,1.148],[-4.255,0.063],[6.575,-0.449],[-5.377,0.649],[6.238,-6.513],[6.263,-0.96],[1.373,-5.863],[6.5,-5.689],[-4.966,-2.907],[2.234,-5.339],[-4.966,-0.05],[-4.803,-1.273],[-4.754,1.647],[-6.412,-3.58]],"o":[[4.067,-1.148],[-5.777,-3.218],[3.58,-3.954],[-8.571,-3.243],[-5.727,-2.695],[-6.113,0.949],[-8.596,-1.696],[5.277,-2.283],[-5.826,1.073],[3.606,-3.318],[4.978,0.062],[-0.088,-4.916],[6.936,-2.395],[1.51,-2.408]],"v":[[-38.406,-167.051],[-25.12,-168.486],[-44.757,-172.415],[-30.697,-179.626],[-55.96,-174.061],[-74.523,-176.744],[-88.41,-166.464],[-112.85,-160.001],[-96.182,-158.966],[-109.382,-148.474],[-95.633,-153.052],[-80.974,-150.432],[-71.692,-160.812],[-50.333,-158.916]],"c":true}},"nm":"P"},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[-17.054,10.006],[3.132,2.357],[-1.685,5.839],[18.165,5.514],[12.899,-1.859],[0,0],[15.919,1.635],[-10.28,0.637],[3.643,0.038],[-12.938,9.419],[-14.273,2.058],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[-9.607,1.372],[0,0],[0,0],[0,0],[0,0],[0,0],[14.272,-2.046],[0,0]],"v":[[115.506,166.077],[103.779,162.434],[113.336,152.939],[79.414,152.766],[63.932,163.957],[48.948,157.145],[15.551,166.413],[28.376,173.811],[18.345,180.025],[47.988,175.771],[68.81,182.296],[84.878,169.345]],"c":true}},"nm":"P"},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[-52.473,25.812],[9.956,24.44],[27.633,44.239],[18.501,-2.533],[7.647,-24.839],[-5.552,-18.152],[-9.581,-18.826],[0,0]],"o":[[53.346,-29.642],[-6.774,-18.801],[-42.281,-67.694],[-25.763,3.531],[-5.577,18.127],[7.548,24.665],[10.717,21.047],[22.905,57.188]],"v":[[89.62,126.704],[122.418,3.631],[71.929,-89.413],[-47.876,-149.247],[-99.463,-106.58],[-97.117,-54.332],[-64.706,12.638],[-38.993,80.12]],"c":true}},"nm":"P"},{"ind":3,"ty":"sh","ks":{"a":0,"k":{"i":[[9.182,-18.489],[0,0],[34.995,-2.271],[19.862,-2.433],[25.226,8.247],[0,0],[11.951,33.335],[38.75,35.231],[0,0],[6.911,13.836],[2.046,8.084],[0,0],[-6.251,0.2],[0.399,6.325],[-5.527,0.687],[0.462,5.714],[-5.751,0.436],[-0.499,5.664],[-5.365,0.387],[0.149,4.978],[-6.139,0.337],[-0.324,5.464],[-22.631,1.46],[-7.485,-0.012],[0,0],[4.105,-129.162],[0,0],[3.256,-24.403]],"o":[[0,0],[-30.229,2.008],[-7.173,-8.546],[0,0],[0,0],[-3.53,-44.476],[-22.307,-62.229],[0,0],[4.579,-8.771],[19.312,-6.799],[0,0],[1.136,5.564],[6.275,-0.212],[1.672,5.464],[5.776,-0.711],[1.31,5.564],[5.764,-0.425],[0.449,5.127],[5.264,-0.374],[1.073,4.74],[4.778,-0.275],[1.996,4.105],[8.558,7.136],[0,0],[65.173,27.584],[0,0],[-10.729,4.18],[0,0]],"v":[[116.592,195.77],[116.629,196.344],[13.342,203.105],[-25.632,190.467],[-54.376,155.847],[-52.929,155.71],[-78.329,33.735],[-180.481,-122.137],[-180.581,-122.187],[-180.181,-156.921],[-166.557,-194.01],[-166.445,-194.035],[-154.057,-183.78],[-143.952,-197.154],[-130.278,-187.598],[-121.221,-199.724],[-108.557,-190.242],[-97.13,-201.221],[-86.251,-192.588],[-76.882,-202.145],[-65.404,-194.934],[-55.997,-203.105],[-23.935,-189.52],[0.93,-180.237],[0.93,-179.962],[176.475,139.417],[176.8,140.24],[150.551,178.99]],"c":true}},"nm":"P"},{"ty":"mm","mm":1,"nm":"M"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[981.333,324.754]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.028,0.15],[0.187,4.117],[5.015,-0.15],[-0.199,-4.117]],"o":[[5.015,-0.15],[-0.188,-4.117],[-5.028,0.162],[0.187,4.117]],"v":[[5.04,-9.089],[13.786,-16.824],[4.354,-24.009],[-4.392,-16.262]],"c":true}},"nm":"P"},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[27.584,13.648],[-46.235,6.85],[-0.786,-7.51],[0,0],[1.76,-17.017],[0.399,0.187],[0,0]],"o":[[-28.893,-26.798],[0,0],[3.718,0.799],[1.185,11.153],[0,0],[-1.734,16.655],[0,0],[0,0]],"v":[[40.134,37.584],[-46.971,-23.884],[34.645,-37.584],[45.786,-26.816],[22.893,-13.317],[36.99,11.335],[25.837,23.686],[40.509,37.171]],"c":true}},"nm":"P"},{"ty":"mm","mm":1,"nm":"M"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[739.797,171.022]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-6.774,-18.801],[53.347,-29.642],[22.905,57.189],[10.717,21.046],[7.548,24.664],[-5.577,18.128],[-25.762,3.531],[-42.281,-67.694]],"o":[[9.956,24.44],[-52.473,25.813],[0,0],[-9.581,-18.826],[-5.551,-18.153],[7.647,-24.839],[18.502,-2.532],[27.633,44.239]],"v":[[103.455,5.926],[70.656,128.999],[-57.956,82.415],[-83.669,14.934],[-116.081,-52.036],[-118.426,-104.285],[-66.839,-146.952],[52.966,-87.118]],"c":true}},"nm":"P"},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.579,-11.477],[-2.658,-6.038],[-0.324,-0.711],[-9.681,-6.201],[-5.589,-1.934],[-4.117,-0.736],[-3.519,-0.174],[-4.715,0.612],[-3.593,1.01],[-1.56,0.562],[-3.406,2.183],[-2.882,2.844],[-3.655,10.355],[-0.724,3.269],[5.489,15.645],[1.41,3.668],[19.05,32.337],[40.684,-11.253],[-18.377,-38.537],[-4.341,-8.795],[-5.177,-12.488]],"o":[[3.144,7.872],[0.337,0.748],[4.529,10.018],[4.828,3.094],[3.88,1.372],[3.431,0.649],[4.703,0.25],[3.668,-0.474],[1.584,-0.449],[3.892,-1.397],[3.406,-2.171],[7.398,-7.298],[1.098,-3.106],[3.443,-15.37],[-1.372,-3.917],[-12.625,-32.923],[-20.822,-35.307],[-47.046,13],[4.728,7.673],[5.951,12.077],[5.54,13.349]],"v":[[-47.676,62.129],[-38.968,83.176],[-37.995,85.347],[-15.988,109.949],[-0.293,117.534],[11.733,120.715],[22.176,121.95],[36.36,121.426],[47.277,119.206],[51.993,117.696],[62.946,112.307],[72.39,104.759],[89.095,77.786],[91.84,68.217],[89.108,20.348],[84.941,8.995],[39.23,-80.094],[-61.013,-130.309],[-93.175,-37.939],[-79.577,-13.05],[-62.872,24.278]],"c":true}},"nm":"P"},{"ty":"mm","mm":1,"nm":"M"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1000.297,322.458]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1.135,5.564],[0,0],[0.137,0.412],[-7.697,0.848],[-0.25,-0.761],[6.276,-0.212]],"o":[[0,0],[-0.35,-1.385],[7.585,-0.948],[0.025,0.786],[0.399,6.325],[-6.25,0.2]],"v":[[-10.997,-2.502],[-11.109,-2.477],[-11.895,-5.234],[11.085,-7.953],[11.497,-5.62],[1.391,7.753]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[825.886,133.22]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.335,-0.848],[-1.31,-1.36],[4.179,1.772]],"o":[[2.059,0.898],[-2.57,-1.747],[1.933,0.487]],"v":[[-0.05,-0.537],[5.04,2.845],[-5.04,-2.845]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[988.426,233.831]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[6.2,-5.177],[0.038,0],[5.963,-0.399],[0,0],[0,0],[-10.729,4.179],[0,0],[0,0],[0.063,-3.207],[0,0]],"o":[[-0.037,0.012],[-5.277,0.35],[0,0],[9.182,-18.489],[3.256,-24.402],[9.394,-3.668],[0,0],[0.025,3.131],[-32.661,6.088],[-13.699,1.385]],"v":[[-22.799,28.75],[-22.924,28.763],[-39.828,29.885],[-39.866,29.312],[-5.907,12.531],[20.342,-26.217],[37.944,-28.962],[39.842,-28.974],[39.778,-19.456],[6.332,18.022]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1137.792,491.211]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.05,-0.537],[5.764,-0.424],[1.31,5.564],[-0.013,0.012],[0.125,0.549],[0,0],[-7.959,0.511]],"o":[[-0.062,0.561],[-0.499,5.664],[-5.752,0.437],[0,0],[-0.05,-0.549],[0,0],[8.234,-0.761],[0,0]],"v":[[12.201,-6.107],[12.189,-4.46],[0.761,6.518],[-11.902,-2.963],[-11.902,-2.988],[-12.164,-4.635],[-12.202,-5.034],[12.151,-6.955]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[872.015,127.993]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.171,-0.112],[-0.287,-5.602],[0,0],[-2.658,6.15],[-1.559,0.449],[-0.4,0],[-1.26,-1.846],[-0.525,-8.745],[0,0]],"o":[[-2.183,0.112],[0,0],[-0.537,-9.332],[0.673,-1.572],[0.399,-0.112],[2.058,0],[4.329,6.263],[0,0],[-0.287,-5.601]],"v":[[-0.324,3.349],[-3.742,13.704],[-9.93,14.066],[-6.574,-10.66],[-2.945,-13.905],[-1.747,-14.066],[3.642,-10.811],[10.468,12.931],[4.116,13.293]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1027.4,408.597]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.829,3.094],[0,0],[0.686,5.664],[0,0],[-2.034,1.983],[0.2,-12.014]],"o":[[-5.589,-1.934],[0,0],[0.087,-2.271],[0,0],[1.909,-1.559],[1.035,8.982],[0,0]],"v":[[8.103,14.883],[-7.592,7.299],[-7.354,6.911],[-8.39,-5.664],[-0.967,-6.513],[6.157,-14.883],[8.19,14.622]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[991.9,425.109]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.907,-0.187],[-0.487,14.06],[0,0],[0,0],[-5.077,-20.547]],"o":[[8.147,-2.844],[0,0],[-0.012,0.325],[0,0],[-23.404,1.51]],"v":[[-21.414,15.732],[-10.648,-15.732],[-10.523,-15.732],[-10.549,-15.233],[21.414,12.962]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[916.497,518.59]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[33.46,4.841],[0.112,-1.735],[0,0],[0.062,4.965],[0.087,2.433],[0,0],[-5.514,-1.809],[0,0],[-7.173,-8.546],[7.647,-0.499],[-2.445,0.162],[13.062,-0.387]],"o":[[-0.374,7.049],[0,0],[0.212,-6.201],[-0.025,-2.433],[0,0],[7.773,-0.399],[25.226,8.246],[19.861,-2.433],[-8.109,0.536],[2.358,-0.2],[-6.762,-3.967],[0,0]],"v":[[-43.378,-15.682],[-44.226,-0.773],[-44.351,-0.773],[-44.301,-18.04],[-44.476,-25.326],[-43.079,-25.376],[-23.242,-23.03],[5.502,11.59],[44.476,24.228],[20.76,25.775],[27.983,25.226],[-1.235,19.013]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0,0.698,0.408,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[950.2,503.631]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-4.466,-1.56]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-10.605,-3.156]],"v":[[-18.196,-4.042],[13.792,-7.074],[13.792,-7.061],[18.196,0.512],[-0.668,2.296],[-8.777,3.069],[-1.653,7.074]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[976.306,289.285]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.06,1.996],[0,0],[0,0],[0,0],[0,0],[7.785,-5.066]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[1.472,-1.098]],"v":[[4.242,1.84],[-3.93,2.613],[-8.334,-4.959],[-8.334,-4.972],[6.75,-6.394],[0.549,6.394]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[998.431,287.183]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":80,"st":0},{"ind":23,"ty":4,"nm":"a","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.53,196.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33],[-1.52,-8.7]],"o":[[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33],[0,4.67]],"v":[[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22],[52.945,-11.41]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.52,-8.7],[2.2,0.33],[18.19,-7.67],[10.85,6.16],[-10.17,6.5],[-18.01,1.33]],"o":[[0,4.67],[-18.61,-2.32],[-24.82,9.99],[-11.12,-7.86],[10.18,-6.5],[18.02,-1.33]],"v":[[52.945,-11.41],[48.855,-5.73],[-7.545,1.83],[-41.825,17.17],[-29.705,-6.04],[18.515,-22]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[882.434,400.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":80,"st":0},{"ind":24,"ty":4,"nm":"b","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[106.53,61.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.88,24.951],[0,0],[0,0],[25.866,-7.144]],"o":[[0,0],[24.365,5.284],[0,0],[0,0],[-30.822,8.513]],"v":[[-33.188,8.953],[-9.998,-5.137],[12.312,-28.621],[28.163,-27.74],[7.321,20.108]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":1,"ml":10,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[894.218,513.63]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0.189,4.119],[-5.021,0.155],[-0.188,-4.118],[5.022,-0.155]],"o":[[-0.189,-4.119],[5.022,-0.155],[0.189,4.119],[-5.021,0.155]],"v":[[-9.092,0.28],[-0.342,-7.458],[9.092,-0.281],[0.342,7.457]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":1,"ml":10,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.043,0.106,0.204,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[873.947,500.296]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-26.316,0.733],[-22.016,0.44],[7.682,30.609],[0,0],[0,0]],"o":[[1.018,18.715],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-42.772,-24.016],[-10.703,24.048],[27.606,22.728],[35.091,-24.387],[35.229,-24.662],[-42.766,-24.782]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"fl","c":{"a":0,"k":[0.031,0.322,0.224,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[894.189,510.13]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":80,"st":0},{"ind":26,"ty":4,"nm":"a","parent":8,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-337.47,377.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-6.92,6.26],[-0.44,18.57],[2.07,4.77],[-6.4,3.28],[-3.12,-7.87],[0,-14],[10.37,-9.68],[4.19,4.89]],"o":[[6.93,-6.25],[0.6,-15.94],[-2.07,-4.77],[6.4,-3.28],[3.12,7.88],[-0.35,27.66],[-5.48,4.86],[-4.19,-4.89]],"v":[[-14.19,32.465],[7.93,-11.985],[3.24,-36.115],[4.43,-49.915],[20.26,-40.685],[24.46,-13.995],[-4.37,43.805],[-20.27,48.305]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1367.229,174.165]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-3.12,-7.87],[0,-14],[10.37,-9.68],[4.19,4.89],[-6.92,6.26],[-0.44,18.57],[2.07,4.77],[-6.4,3.28]],"o":[[3.12,7.88],[-0.35,27.66],[-5.48,4.86],[-4.19,-4.89],[6.93,-6.25],[0.6,-15.94],[-2.07,-4.77],[6.4,-3.28]],"v":[[20.26,-40.685],[24.46,-13.995],[-4.37,43.805],[-20.27,48.305],[-14.19,32.465],[7.93,-11.985],[3.24,-36.115],[4.43,-49.915]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1367.229,174.165]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":44,"op":50,"st":0},{"ind":27,"ty":4,"nm":"h","parent":9,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-327.47,181.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-6,3.3],[-3.14,1.95],[-1.46,-2.02],[0.11,-0.22],[0,0]],"o":[[1.73,-10.14],[6,-3.3],[3.15,-1.95],[1.4,1.95],[0,0],[0,0]],"v":[[-19.38,13.575],[-2.52,-2.815],[10.09,-11.625],[17.98,-11.425],[17.99,-6.595],[17.98,-6.585]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1356.769,387.245]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.49,1.76]],"o":[[8.06,-2.61],[0,0]],"v":[[-7.06,3.495],[7.06,-3.495]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1367.689,384.155]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.09,0],[2.34,-3],[0,0]],"o":[[1.87,-1.32],[4.39,0],[0,0],[0,0]],"v":[[-5.37,-1.205],[-0.15,-3.365],[3.03,3.355],[3.03,3.365]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1380.129,381.855]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-3.76,2.29]],"o":[[7.52,-0.89],[0,0]],"v":[[-8.52,2.91],[8.52,-2.91]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1374.639,388.13]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.74,0.55],[0.62,-5.61]],"o":[[1.08,-0.65],[3.32,-2.45],[0,0]],"v":[[-5.23,0.79],[-2.5,-1.03],[4.61,3.48]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1388.389,384.42]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-13.18,0.39],[-8.88,-2.35],[-0.13,3],[5.22,-0.13],[0,1.96],[-8.35,-0.26],[-3.79,6.27],[4.31,-1.31],[14.36,-0.13]],"o":[[-1.43,9.4],[13.18,-0.39],[6.78,0.65],[0.13,-3],[-5.22,0.14],[0,-1.96],[8.35,0.26],[3.78,-6.26],[-4.31,1.3],[0,0]],"v":[[-31.135,-3.98],[-15.205,16.25],[6.205,17.56],[14.035,12.99],[6.205,7.5],[-1.895,3.33],[6.855,-1.11],[28.785,-7.9],[24.475,-16.9],[3.985,-10.64]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1368.524,404.8]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.34,-3],[-0.74,0.55],[0.62,-5.61],[3.78,-6.26],[8.35,0.26],[0,-1.96],[-5.22,0.14],[0.13,-3],[6.78,0.65],[13.18,-0.39],[-1.43,9.4],[-6,3.3],[-3.14,1.95],[-1.46,-2.02],[0.11,-0.22],[-2.09,0]],"o":[[1.08,-0.65],[3.32,-2.45],[4.31,-1.31],[-3.79,6.27],[-8.35,-0.26],[0,1.96],[5.22,-0.13],[-0.13,3],[-8.88,-2.35],[-13.18,0.39],[1.73,-10.14],[6,-3.3],[3.15,-1.95],[1.4,1.95],[1.87,-1.32],[4.39,0]],"v":[[14.635,-13.13],[17.365,-14.95],[24.475,-10.44],[28.785,-1.44],[6.855,5.35],[-1.895,9.79],[6.205,13.96],[14.035,19.45],[6.205,24.02],[-15.205,22.71],[-31.135,2.48],[-14.275,-13.91],[-1.665,-22.72],[6.225,-22.52],[6.235,-17.69],[11.455,-19.85]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1368.523,398.34]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":50,"op":60,"st":0},{"ind":28,"ty":4,"nm":"a","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-307.47,72.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[-5.08,2.86]],"o":[[0,0],[4.98,3.32],[0,0]],"v":[[-7.525,-2.78],[-7.515,-2.78],[7.525,-0.08]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1343.894,447.37]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-5.68,1.63]],"o":[[4.65,3.8],[0,0]],"v":[[-7.74,-2.515],[7.74,0.885]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1343.679,461.675]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-6.15,2.36]],"o":[[4.61,4.25],[0,0]],"v":[[-7.99,-2.95],[7.99,0.59]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1344.369,432.32]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.72,-16.11]],"o":[[-0.14,-13.51],[0,0]],"v":[[-7.92,5.085],[8.06,8.425]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1343.359,408.045]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.45,24.03],[-0.14,5.24],[0.14,4.94],[0.47,5.65]],"o":[[11.89,-18.09],[0.5,-4.92],[0.13,-4.67],[-0.16,-5.31],[0,0]],"v":[[-11.115,54.29],[10.025,-8.2],[10.985,-23.44],[10.955,-37.85],[10.015,-54.29]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1341.404,470.76]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.45,24.25],[-0.02,5.15],[0.17,5.37],[0.3,5.73]],"o":[[12.33,-18.83],[0.28,-4.59],[0.01,-4.79],[-0.16,-5.12],[0,0]],"v":[[-9.785,53.19],[9.335,-7.16],[9.775,-21.73],[9.535,-36.94],[8.835,-53.19]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1326.604,466.32]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.68,1.63],[0,0],[11.89,-18.09],[0,0],[-1.45,24.25]],"o":[[0,0],[-2.45,24.03],[0,0],[12.33,-18.83],[4.65,3.8]],"v":[[17.295,-29.545],[17.305,-29.545],[-3.835,32.945],[-17.305,27.405],[1.815,-32.945]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1334.124,492.105]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.08,2.86],[0,0],[0.5,-4.92],[0,0],[4.65,3.8],[-0.02,5.15]],"o":[[0,0],[-0.14,5.24],[0,0],[-5.68,1.63],[0.28,-4.59],[4.98,3.32]],"v":[[7.255,-7.1],[8.225,-7.07],[7.265,8.17],[7.255,8.17],[-8.225,4.77],[-7.785,-9.8]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1344.164,454.39]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.72,-16.11],[-0.16,-5.31],[4.61,4.25],[0,0],[0.3,5.73]],"o":[[0.47,5.65],[-6.15,2.36],[0,0],[-0.16,-5.12],[-0.14,-13.51]],"v":[[7.59,-0.975],[8.53,15.465],[-7.45,11.925],[-7.69,11.935],[-8.39,-4.315]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1343.828,417.445]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-6.15,2.36],[0.13,-4.67],[0,0],[4.98,3.32],[0.17,5.37],[0,0]],"o":[[0.14,4.94],[0,0],[-5.08,2.86],[0.01,-4.79],[0,0],[4.61,4.25]],"v":[[8.03,-6.85],[8.06,7.56],[7.09,7.53],[-7.95,4.83],[-8.19,-10.38],[-7.95,-10.39]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1344.329,439.76]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":50,"op":60,"st":0},{"ind":29,"ty":4,"nm":"a","parent":11,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-236.47,7.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-8.38,11.87],[-5.84,-2.17],[7.86,-10.82],[21.86,-3.46],[1.34,4.81]],"o":[[30.14,-14.58],[6.71,-8.68],[5.85,2.17],[-15.05,21.51],[-3.83,0.29],[-1.33,-4.81]],"v":[[-34.315,26.33],[25.055,-29.36],[39.485,-38.52],[35.535,-18.38],[-34.685,40.4],[-44.005,35.65]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1289.524,551.75]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.84,-2.17],[7.86,-10.82],[21.86,-3.46],[1.34,4.81],[0,0],[-8.38,11.87]],"o":[[5.85,2.17],[-15.05,21.51],[-3.83,0.29],[-1.33,-4.81],[30.14,-14.58],[6.71,-8.68]],"v":[[39.485,-38.52],[35.535,-18.38],[-34.685,40.4],[-44.005,35.65],[-34.315,26.33],[25.055,-29.36]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1289.524,551.75]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":50,"op":60,"st":0}]},{"id":"comp_3","nm":"l","fr":24,"layers":[{"ind":1,"ty":3,"nm":"l","parent":2,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[10]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":7,"s":[19]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":8,"s":[19]},{"i":{"x":[0.593],"y":[0.843]},"o":{"x":[0.175],"y":[0.072]},"t":9,"s":[19]},{"i":{"x":[0.698],"y":[1]},"o":{"x":[0.351],"y":[1.186]},"t":10,"s":[6.793]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":12,"s":[4]},{"t":15,"s":[-34]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":1,"s":[-79,97,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":4,"s":[-79,97,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":7,"s":[-79,97,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":8,"s":[-79,97,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.593,"y":0.593},"o":{"x":0.167,"y":0.167},"t":9,"s":[-79,97,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.698,"y":0.698},"o":{"x":0.351,"y":0.351},"t":10,"s":[-79,97,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":12,"s":[-79,97,0],"to":[0,0,0],"ti":[0,0,0]},{"t":15,"s":[-79,97,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":1,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":4,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":7,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.593,0.593,0.593],"y":[1,1,1]},"o":{"x":[0.175,0.175,0.175],"y":[0,0,0]},"t":9,"s":[100,100,100]},{"i":{"x":[0.698,0.698,0.698],"y":[1,1,1]},"o":{"x":[0.351,0.351,0.351],"y":[0,0,0]},"t":10,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":12,"s":[100,100,100]},{"t":15,"s":[100,100,100]}],"l":2}},"ao":0,"ip":0,"op":16,"st":0},{"ind":2,"ty":3,"nm":"l","parent":3,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[-6]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[6]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":7,"s":[11]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[39]},{"i":{"x":[0.593],"y":[1]},"o":{"x":[0.175],"y":[0]},"t":9,"s":[-5]},{"i":{"x":[0.698],"y":[1]},"o":{"x":[0.351],"y":[0]},"t":10,"s":[-5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":12,"s":[-5]},{"t":15,"s":[-14]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":1,"s":[10,135,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":4,"s":[10,135,0],"to":[-0.206,-0.114,0],"ti":[0.564,0.209,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":7,"s":[8.763,134.314,0],"to":[-0.564,-0.209,0],"ti":[-0.206,-0.114,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":8,"s":[6.619,133.749,0],"to":[0.206,0.114,0],"ti":[-0.564,-0.209,0]},{"i":{"x":0.593,"y":0.593},"o":{"x":0.167,"y":0.167},"t":9,"s":[10,135,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.698,"y":0.698},"o":{"x":0.351,"y":0.351},"t":10,"s":[10,135,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":12,"s":[10,135,0],"to":[0,0,0],"ti":[0,0,0]},{"t":15,"s":[10,135,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":1,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":4,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":7,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.593,0.593,0.593],"y":[1,1,1]},"o":{"x":[0.175,0.175,0.175],"y":[0,0,0]},"t":9,"s":[100,100,100]},{"i":{"x":[0.698,0.698,0.698],"y":[1,1,1]},"o":{"x":[0.351,0.351,0.351],"y":[0,0,0]},"t":10,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":12,"s":[100,100,100]},{"t":15,"s":[100,100,100]}],"l":2}},"ao":0,"ip":0,"op":16,"st":0},{"ind":3,"ty":3,"nm":"l","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[16]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":7,"s":[16]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[-54]},{"i":{"x":[0.593],"y":[0.843]},"o":{"x":[0.175],"y":[0.072]},"t":9,"s":[-65]},{"i":{"x":[0.698],"y":[1]},"o":{"x":[0.351],"y":[1.186]},"t":10,"s":[-93.482]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":12,"s":[-100]},{"t":15,"s":[-81]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":1,"s":[993,865,0],"to":[-4,-9.5,0],"ti":[-3.5,-10,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":4,"s":[969,808,0],"to":[3.5,10,0],"ti":[-3.833,-15.833,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":7,"s":[1014,925,0],"to":[3.833,15.833,0],"ti":[-1.333,7.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":8,"s":[992,903,0],"to":[1.333,-7.667,0],"ti":[-1.5,15,0]},{"i":{"x":0.593,"y":0.843},"o":{"x":0.167,"y":0.167},"t":9,"s":[1022,879,0],"to":[1.036,-10.364,0],"ti":[6.778,15.785,0]},{"i":{"x":0.698,"y":1},"o":{"x":0.296,"y":1},"t":10,"s":[1009.1,821.8,0],"to":[-3.032,-7.061,0],"ti":[2.112,-2.266,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":12,"s":[1001,813,0],"to":[-6.833,7.333,0],"ti":[3.333,-18.333,0]},{"t":15,"s":[981,923,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":1,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":4,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":7,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.593,0.593,0.593],"y":[1,1,1]},"o":{"x":[0.175,0.175,0.175],"y":[0,0,0]},"t":9,"s":[100,100,100]},{"i":{"x":[0.698,0.698,0.698],"y":[1,1,1]},"o":{"x":[0.351,0.351,0.351],"y":[0,0,0]},"t":10,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":12,"s":[100,100,100]},{"t":15,"s":[100,100,100]}],"l":2}},"ao":0,"ip":0,"op":16,"st":0},{"ind":4,"ty":3,"nm":"l","parent":5,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":0},"p":{"a":0,"k":[4,171,0],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":16,"st":0},{"ind":5,"ty":3,"nm":"l","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":0},"p":{"a":0,"k":[114,137,0],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":16,"st":0},{"ind":6,"ty":3,"nm":"l","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":2},"p":{"a":0,"k":[1005,948,0],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":16,"st":0},{"ind":7,"ty":4,"nm":"l","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[353.53,-209.39,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-3.87],[3.87,0],[0,3.88],[-3.87,0]],"o":[[0,3.88],[-3.87,0],[0,-3.87],[3.87,0]],"v":[[7.01,0],[0,7.01],[-7.01,0],[0,-7.01]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[653.369,810.09]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[3.57,-4.41],[-3.65,-27.85],[-5.48,0],[0,0]],"o":[[-4.08,-2.78],[-3.57,4.42],[4.7,24.38],[5.48,0],[0,0]],"v":[[9.305,-42.03],[-4.715,-37.92],[-12.695,11.43],[10.865,44.81],[16.345,40.13]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[638.504,835.55]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-3.67,6.05]],"o":[[5.61,2.49],[0,0]],"v":[[-8.92,1.38],[8.92,-3.87]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[660.379,853.62]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-5,3.48]],"o":[[5.97,3.15],[0,0]],"v":[[-9.235,-0.935],[9.235,-2.215]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[657.774,844.495]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-6.18,3.71],[-0.03,0.02]],"o":[[3.23,2.81],[0.03,-0.01],[0,0]],"v":[[-8.855,-2.215],[8.765,-1.495],[8.855,-1.545]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[657.064,833.335]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.13,-1.55],[3.22,-8.76],[-3.27,-12.43],[-8.88,-1.56],[-3.14,2.01],[2.23,7.26],[0.41,1.22],[0.21,4.39],[-1.83,3.66]],"o":[[-6.1,0.15],[-4.96,1.85],[-5.89,16.08],[2.85,11.54],[3.48,0.62],[8.64,-5.55],[-0.63,-2.07],[-0.94,-3.96],[-0.29,-6.33],[0,0]],"v":[[11.25,-42.18],[-3.93,-40.41],[-15.9,-25.83],[-17.53,19.4],[3.04,41.56],[13.15,39.62],[17.49,15.64],[15.8,10.61],[14.02,-2.27],[16.15,-18.31]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[651.809,834.11]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-8.88,-1.56],[0,0],[5.48,0],[4.7,24.38],[-3.57,4.42],[-4.08,-2.78],[0,0],[3.22,-8.76],[-3.27,-12.43]],"o":[[0,0],[0,0],[-5.48,0],[-3.65,-27.85],[3.57,-4.41],[0,0],[-4.96,1.85],[-5.89,16.08],[2.85,11.54]],"v":[[16.345,40.12],[16.345,40.13],[10.865,44.81],[-12.695,11.43],[-4.715,-37.92],[9.305,-42.03],[9.375,-41.85],[-2.595,-27.27],[-4.225,17.96]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[638.504,835.55]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-3.87,0],[0,-3.87],[3.87,0],[0,3.88]],"o":[[3.87,0],[0,3.88],[-3.87,0],[0,-3.87]],"v":[[0,-7.01],[7.01,0],[0,7.01],[-7.01,0]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[653.369,810.09]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,3.88],[3.87,0],[0,-3.87],[-3.87,0]],"o":[[0,-3.87],[-3.87,0],[0,3.88],[3.87,0]],"v":[[8.57,-24.02],[1.56,-31.03],[-5.45,-24.02],[1.56,-17.01]],"c":true}},"nm":"P"},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.29,-6.33],[-0.94,-3.96],[-0.63,-2.07],[8.64,-5.55],[3.48,0.62],[2.85,11.54],[-5.89,16.08],[-4.96,1.85],[-6.1,0.15],[0,0]],"o":[[0.21,4.39],[0.41,1.22],[2.23,7.26],[-3.14,2.01],[-8.88,-1.56],[-3.27,-12.43],[3.22,-8.76],[4.13,-1.55],[0,0],[-1.83,3.66]],"v":[[14.02,-2.27],[15.8,10.61],[17.49,15.64],[13.15,39.62],[3.04,41.56],[-17.53,19.4],[-15.9,-25.83],[-3.93,-40.41],[11.25,-42.18],[16.15,-18.31]],"c":true}},"nm":"P"},{"ty":"mm","mm":1,"nm":"M"},{"ty":"fl","c":{"a":0,"k":[0.996,0.839,0.027,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[651.809,834.11]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":1,"op":16,"st":0},{"ind":8,"ty":4,"nm":"l","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[224.53,-162.39,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.28,-2.57]],"o":[[-0.74,3.44],[0,0]],"v":[[-0.14,-4.455],[0.88,4.455]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[673.919,796.385]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.82,-11.57]],"o":[[11.77,-3.09],[0,0]],"v":[[-9.94,-3.275],[9.12,6.365]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[684.739,804.115]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-5.04,-4.79]],"o":[[1.34,6.88],[0,0]],"v":[[-4.355,-8.905],[4.355,8.905]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[746.554,785.915]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.55,7.69]],"o":[[-3.12,-3.73],[0,0]],"v":[[4.335,9.085],[-4.335,-9.085]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[738.144,789.425]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-4.42,-5.91]],"o":[[0.61,6.84],[0,0]],"v":[[-3.86,-9.71],[3.86,9.71]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[730.019,792.67]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-4.9,-5.76]],"o":[[0.83,5.76],[0,0]],"v":[[-4.185,-8.42],[4.185,8.42]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[754.804,781.74]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.04,-0.04],[14.57,-7.41],[2.97,-1.28],[0,0],[2.62,-1.01],[0.31,-0.11],[2.4,-0.81],[26.95,-1.17],[3.58,-0.11],[-2.74,-8.72],[-5.32,2.37],[-16.13,1.82],[-1.12,0.14],[-12.94,4.79],[-3.1,1.39],[-1.86,0.9],[-0.99,0.52],[-2.74,1.67],[-5.43,6.05]],"o":[[-0.04,0.04],[-11.85,10.76],[-2.59,1.32],[0,0],[-2.33,1.01],[-0.32,0.11],[-2.73,0.94],[-15.05,5.03],[-3.1,0.14],[-6.17,0.42],[2.73,8.72],[4.17,-4.04],[1.03,-0.12],[14.02,-1.72],[2.74,-1.01],[1.71,-0.76],[0.97,-0.47],[2.65,-1.38],[18.46,-11.15],[0,0]],"v":[[58.025,-35.74],[57.895,-35.62],[22.145,-9.36],[13.825,-5.46],[13.825,-5.45],[6.405,-2.42],[5.455,-2.09],[-2.195,0.53],[-54.595,9.14],[-64.605,9.5],[-71.285,25],[-60.395,33.37],[-34.495,28.05],[-31.265,27.66],[5.525,19.95],[14.255,16.37],[19.615,13.88],[22.565,12.4],[30.685,7.81],[74.025,-25.71]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[728.354,782.43]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.28,-2.57],[0.82,-11.57],[4.17,-4.04],[2.73,8.72],[-6.17,0.42],[-3.1,0.14],[0,0]],"o":[[11.77,-3.09],[-16.13,1.82],[-5.32,2.37],[-2.74,-8.72],[3.58,-0.11],[0,0],[-0.74,3.44]],"v":[[0.295,-4.03],[19.355,5.61],[-6.545,10.93],[-17.435,2.56],[-10.755,-12.94],[-0.745,-13.3],[-0.725,-12.94]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0.996,0.839,0.027,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[674.504,804.87]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-15.05,5.03],[-4.42,-5.91],[14.02,-1.72],[1.03,-0.12],[11.77,-3.09],[-0.74,3.44],[0,0]],"o":[[0.61,6.84],[-12.94,4.79],[-1.12,0.14],[0.82,-11.57],[-1.28,-2.57],[0,0],[26.95,-1.17]],"v":[[22.7,-13.76],[30.42,5.66],[-6.37,13.37],[-9.6,13.76],[-28.66,4.12],[-29.68,-4.79],[-29.7,-5.15]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[703.459,796.72]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-3.12,-3.73],[0,0],[2.74,-1.01],[0.61,6.84],[-2.73,0.94]],"o":[[0,0],[-3.1,1.39],[-4.42,-5.91],[2.4,-0.81],[1.55,7.69]],"v":[[8.095,7.15],[8.225,7.44],[-0.505,11.02],[-8.225,-8.4],[-0.575,-11.02]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[734.384,791.36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.04,-4.79],[0,0],[0.97,-0.47],[1.71,-0.76],[0,0],[1.55,7.69],[-0.32,0.11],[-2.33,1.01],[0,0]],"o":[[0,0],[-0.99,0.52],[-1.86,0.9],[0,0],[-3.12,-3.73],[0.31,-0.11],[2.62,-1.01],[0,0],[1.34,6.88]],"v":[[8.545,6.93],[8.555,6.94],[5.605,8.42],[0.245,10.91],[0.115,10.62],[-8.555,-7.55],[-7.605,-7.88],[-0.185,-10.91],[-0.165,-10.88]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[742.364,787.89]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.9,-5.76],[0,0],[2.65,-1.38],[0,0],[1.34,6.88],[0,0],[0,0],[-2.59,1.32],[0,0]],"o":[[0,0],[-2.74,1.67],[0,0],[-5.04,-4.79],[0,0],[0,0],[2.97,-1.28],[0,0],[0.83,5.76]],"v":[[8.38,6.21],[8.43,6.29],[0.31,10.88],[0.3,10.87],[-8.41,-6.94],[-8.43,-6.97],[-8.43,-6.98],[-0.11,-10.88],[0.01,-10.63]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[750.609,783.95]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[18.46,-11.15],[0,0],[0.83,5.76],[0,0],[-11.85,10.76]],"o":[[0,0],[0,0],[-5.43,6.05],[0,0],[-4.9,-5.76],[0,0],[14.57,-7.41],[0,0]],"v":[[9.9,-21.575],[25.98,-11.945],[25.9,-11.805],[-17.44,21.715],[-17.49,21.635],[-25.86,4.795],[-25.98,4.545],[9.77,-21.715]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[776.479,768.525]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":1,"op":16,"st":0},{"ind":9,"ty":4,"nm":"l","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[184.53,-77.39,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[4.88,0],[0.42,-9.78],[14.11,-21.77],[-6.21,-7.49],[-6.67,6.4],[-8.91,14.32],[0.72,10.97]],"o":[[-7.79,0],[-0.42,9.78],[-14.11,21.78],[4.01,5.64],[5.38,-4.95],[16.59,-28.29],[-0.72,-10.97]],"v":[[20.42,-52.47],[10.32,-36.16],[-8.19,13.2],[-25.76,46.83],[-8.19,45.01],[14.51,14.79],[31.25,-39.33]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[810.569,711.71]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.72,-10.97],[16.59,-28.29],[5.38,-4.95],[4.01,5.64],[-14.11,21.78],[-0.42,9.78],[-7.79,0]],"o":[[0.72,10.97],[-8.91,14.32],[-6.67,6.4],[-6.21,-7.49],[14.11,-21.77],[0.42,-9.78],[4.88,0]],"v":[[31.25,-39.33],[14.51,14.79],[-8.19,45.01],[-25.76,46.83],[-8.19,13.2],[10.32,-36.16],[20.42,-52.47]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[810.569,711.71]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":1,"op":16,"st":0},{"ind":10,"ty":4,"nm":"l","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-272.47,-255.39,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-4.09],[4.08,0],[0,4.09],[-4.09,0]],"o":[[0,4.09],[-4.09,0],[0,-4.09],[4.08,0]],"v":[[7.4,0],[0,7.4],[-7.4,0],[0,-7.4]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1292.479,858.84]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[2.71,4.93]],"o":[[1.52,-7.73],[0,0]],"v":[[0.135,9.5],[-1.655,-9.5]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1331.114,855.78]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.42,-7.86]],"o":[[1.88,4.97],[0,0]],"v":[[-1.335,-9.375],[-0.085,9.375]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1341.204,854.165]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.89,6.41]],"o":[[1.97,-6.4],[0,0]],"v":[[-1.005,9.605],[-0.885,-9.605]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1320.884,857.185]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-7.27,-4.65],[-25.22,0.66],[-4.16,4.3],[3.44,1.87]],"o":[[-3.96,1.59],[7.27,4.64],[24.66,0.06],[4.15,-4.3],[0,0]],"v":[[-44.125,-8.175],[-40.285,3.765],[1.895,10.145],[43.935,-1.525],[43.935,-10.805]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1315.384,874.345]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.79,-6.43],[-5.04,-2.26],[-12.46,5.79],[-1.81,3.86],[0.59,3.54],[11.85,-1.75],[2.06,-0.35],[3.07,-0.26],[5.42,0.88]],"o":[[-3.45,6.89],[1.28,2.99],[15.88,7.12],[6.96,-3.23],[1.45,-3.06],[-1.33,-8.01],[-2.43,0.36],[-3.38,0.56],[-4.14,0.35],[0,0]],"v":[[-38.64,-17.155],[-43.44,3.815],[-34.34,11.785],[31.99,11.785],[44.62,1.185],[45.64,-8.695],[21.45,-17.155],[14.76,-16.075],[5.3,-14.775],[-8.52,-15.375]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1314.698,862.355]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.09,0],[0,-4.09],[4.08,0],[0,4.09]],"o":[[4.08,0],[0,4.09],[-4.09,0],[0,-4.09]],"v":[[0,-7.4],[7.4,0],[0,7.4],[-7.4,0]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1292.479,858.84]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.81,3.86],[4.15,-4.3],[24.66,0.06],[7.27,4.64],[-3.96,1.59],[-5.04,-2.26],[-12.46,5.79]],"o":[[3.44,1.87],[-4.16,4.3],[-25.22,0.66],[-7.27,-4.65],[1.28,2.99],[15.88,7.12],[6.96,-3.23]],"v":[[43.935,-10.805],[43.935,-1.525],[1.895,10.145],[-40.285,3.765],[-44.125,-8.175],[-35.025,-0.205],[31.305,-0.205]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1315.384,874.345]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,4.09],[4.08,0],[0,-4.09],[-4.09,0]],"o":[[0,-4.09],[-4.09,0],[0,4.09],[4.08,0]],"v":[[-14.82,-3.515],[-22.22,-10.915],[-29.62,-3.515],[-22.22,3.885]],"c":true}},"nm":"P"},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[-3.38,0.56],[-2.43,0.36],[-1.33,-8.01],[1.45,-3.06],[6.96,-3.23],[15.88,7.12],[1.28,2.99],[-3.45,6.89],[0,0],[-4.14,0.35]],"o":[[2.06,-0.35],[11.85,-1.75],[0.59,3.54],[-1.81,3.86],[-12.46,5.79],[-5.04,-2.26],[-2.79,-6.43],[0,0],[5.42,0.88],[3.07,-0.26]],"v":[[14.76,-16.075],[21.45,-17.155],[45.64,-8.695],[44.62,1.185],[31.99,11.785],[-34.34,11.785],[-43.44,3.815],[-38.64,-17.155],[-8.52,-15.375],[5.3,-14.775]],"c":true}},"nm":"P"},{"ty":"mm","mm":1,"nm":"M"},{"ty":"fl","c":{"a":0,"k":[0.996,0.839,0.027,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1314.699,862.355]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":1,"st":0},{"ind":11,"ty":4,"nm":"l","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-318.47,-134.39,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-13.78,-0.14],[0,0]],"o":[[5.39,4.71],[0,0],[0,0]],"v":[[-11.665,-3.32],[11.595,3.32],[11.665,3.32]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1330.394,784.82]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-10.94,-0.02]],"o":[[4.88,4.66],[0,0]],"v":[[-11.595,-3.715],[11.595,3.715]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1327.094,792.145]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-10.49,0.76]],"o":[[4.87,6.32],[0,0]],"v":[[-11.525,-4.55],[11.525,3.79]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1323.494,799.4]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-9.69,1.43]],"o":[[6.2,3.77],[0,0]],"v":[[-11.62,-2.91],[11.62,1.48]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1333.009,778.23]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-5.89,-2.26]],"o":[[3.16,2.26],[0,0]],"v":[[-6.79,-3.385],[6.79,3.385]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1289.779,838.855]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-6.64,-7.83]],"o":[[7.52,-12.63],[0,0]],"v":[[-10.71,9.56],[10.71,-1.73]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1307.279,832.68]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.27,-12.72],[2.21,-6.83],[0.92,-2.19],[1.25,-2.43],[1.32,-2.24],[2.29,-3.36],[5.58,-7.23],[0.9,-1.82],[-8.27,-0.63],[-10.81,13.47],[-4.47,6.49],[-5.06,9.57],[-1.13,2.41],[-0.95,2.49],[-0.61,2.63],[7.6,26.4]],"o":[[3.32,13.67],[-0.59,5.85],[-0.65,2.01],[-0.91,2.2],[-1.04,2.05],[-1.74,2.97],[-9.12,13.39],[-3.56,4.62],[-2.32,4.7],[8.27,0.64],[3.16,-3.93],[5.75,-8.36],[1.31,-2.47],[1.24,-2.65],[1.13,-2.97],[4.77,-20.67],[0,0]],"v":[[10.655,-65.52],[13.215,-33.08],[9.565,-14.48],[7.235,-8.19],[4.005,-1.26],[0.475,5.16],[-5.545,14.63],[-28.505,45.78],[-35.435,55.51],[-31.315,70.61],[-5.315,57.29],[6.495,41.26],[23.525,13.5],[27.195,6.17],[30.495,-1.55],[33.135,-9.98],[31.985,-71.25]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1311.494,789.69]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.89,-2.26],[-6.64,-7.83],[3.16,-3.93],[8.27,0.64],[-2.32,4.7],[-3.56,4.62]],"o":[[7.52,-12.63],[-4.47,6.49],[-10.81,13.47],[-8.27,-0.63],[0.9,-1.82],[3.16,2.26]],"v":[[1.62,0.21],[23.04,-11.08],[11.23,4.95],[-14.77,18.27],[-18.89,3.17],[-11.96,-6.56]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0.996,0.839,0.027,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1294.948,842.03]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.74,2.97],[-10.49,0.76],[5.75,-8.36],[7.52,-12.63],[3.16,2.26],[-9.12,13.39]],"o":[[4.87,6.32],[-5.06,9.57],[-6.64,-7.83],[-5.89,-2.26],[5.58,-7.23],[2.29,-3.36]],"v":[[2.965,-23.695],[26.015,-15.355],[8.985,12.405],[-12.435,23.695],[-26.015,16.925],[-3.055,-14.225]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1309.004,818.545]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-10.94,-0.02],[1.31,-2.47],[4.87,6.32],[-1.04,2.05]],"o":[[-1.13,2.41],[-10.49,0.76],[1.32,-2.24],[4.88,4.66]],"v":[[13.36,-0.33],[9.69,7],[-13.36,-1.34],[-9.83,-7.76]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1325.329,796.19]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-13.78,-0.14],[1.24,-2.65],[4.88,4.66],[-0.91,2.2]],"o":[[-0.95,2.49],[-10.94,-0.02],[1.25,-2.43],[5.39,4.71]],"v":[[13.245,-0.54],[9.945,7.18],[-13.245,-0.25],[-10.015,-7.18]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1328.744,788.68]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-9.69,1.43],[1.13,-2.97],[5.39,4.71],[-0.65,2.01],[0,0]],"o":[[-0.61,2.63],[-13.78,-0.14],[0.92,-2.19],[0,0],[6.2,3.77]],"v":[[12.95,-1.965],[10.31,6.465],[-12.95,-0.175],[-10.62,-6.465],[-10.29,-6.355]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1331.679,781.675]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.77,-20.67],[6.2,3.77],[0,0],[-0.59,5.85],[3.32,13.67]],"o":[[7.6,26.4],[-9.69,1.43],[0,0],[2.21,-6.83],[1.27,-12.72],[0,0]],"v":[[7.41,-31.35],[8.56,29.92],[-14.68,25.53],[-15.01,25.42],[-11.36,6.82],[-13.92,-25.62]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1336.069,749.79]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":1,"st":0},{"ind":12,"ty":4,"nm":"l","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":-8},"p":{"a":0,"k":[-294.274,-127.049,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,176,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.86,18.12]],"o":[[4.86,18.11],[0,0]],"v":[[-13.095,-6.19],[8.235,-11.92]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1335.244,730.36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-9.81,-9.55]],"o":[[-10.99,-7.08],[0,0]],"v":[[13.125,-2.715],[-3.315,9.795]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1267.284,637.015]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[27.65,20.4]],"o":[[-13.33,-35.3],[0,0]],"v":[[31.535,42.07],[-31.535,-42.07]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1311.944,676.37]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[23.79,22.12]],"o":[[-12.16,-31.02],[0,0]],"v":[[29.09,38.68],[-29.09,-38.68]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1293.059,685.49]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-13.33,-35.3],[4.86,18.11],[23.79,22.12],[-10.99,-7.08]],"o":[[4.86,18.12],[-12.16,-31.02],[-9.81,-9.55],[27.65,20.4]],"v":[[42.23,33.69],[20.9,39.42],[-37.28,-37.94],[-20.84,-50.45]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1301.249,684.75]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":1,"st":0}]},{"id":"comp_4","nm":"u","fr":24,"layers":[{"ind":1,"ty":3,"nm":"h","parent":2,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.5,-21.5,0],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":2,"ty":3,"nm":"a","parent":3,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":8,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":12,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":32,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":36,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":44,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":48,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":52,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":56,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":64,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":68,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":76,"s":[0]},{"t":80,"s":[-10]}]},"p":{"a":0,"k":[148,23,0],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":3,"ty":3,"nm":"a","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[89]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[152]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":8,"s":[89]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":12,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[89]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[152]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[89]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":32,"s":[89]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":36,"s":[152]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[89]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":44,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":48,"s":[89]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":52,"s":[152]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":56,"s":[89]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":64,"s":[89]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":68,"s":[152]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":72,"s":[89]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":76,"s":[0]},{"t":80,"s":[89]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[1132,358.5,0],"to":[0,-0.833,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[1132,353.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":8,"s":[1132,358.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":12,"s":[1132,353.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":16,"s":[1132,358.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[1132,353.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[1132,358.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":28,"s":[1132,353.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":32,"s":[1132,358.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":36,"s":[1132,353.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[1132,358.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44,"s":[1132,353.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[1132,358.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":52,"s":[1132,353.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":56,"s":[1132,358.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[1132,353.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":64,"s":[1132,358.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":68,"s":[1132,353.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":72,"s":[1132,358.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":76,"s":[1132,353.5,0],"to":[0,0,0],"ti":[0,-0.833,0]},{"t":80,"s":[1132,358.5,0]}],"l":2},"a":{"a":0,"k":[50,50,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"ip":0,"op":80,"st":0},{"ind":4,"ty":4,"nm":"h","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-246.97,338.11,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[3.01,-3.81],[0,0]],"o":[[-4.07,-4.27],[-3.01,3.81],[0,0]],"v":[[7.035,-2.385],[-4.025,-1.525],[-6.335,6.655]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1256.284,224.005]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.79,2.34]],"o":[[2.34,0.83],[0,0]],"v":[[-3.33,-0.2],[3.33,-1.17]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1265.639,242.16]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[2.86,-2.46],[0.64,-1.17],[-0.49,-1.89],[0,0]],"o":[[-4,-3.97],[-0.96,0.83],[-2.55,4.64],[0,0],[0,0]],"v":[[7.95,-3.705],[-2.97,-4.695],[-5.4,-1.655],[-7.37,7.665],[-7.37,7.675]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1266.479,226.535]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.01,0],[3.34,-2.1],[0.87,-1.23],[-2.19,-4.06],[-0.58,-0.48],[-0.18,0.28],[0,0]],"o":[[0,0],[-2.89,-4.76],[-1.23,0.76],[-2.76,3.95],[0.35,0.67],[4.39,3.17],[0,0],[0,0]],"v":[[9.105,-7.665],[9.095,-7.675],[-1.395,-10.435],[-4.595,-7.375],[-6.915,7.625],[-5.515,9.365],[2.055,7.855],[2.065,7.845]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1275.884,233.365]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.43,4.27],[-0.06,0.13],[2.7,1.95]],"o":[[2.7,2.32],[0.07,-0.12],[1.29,-2.82],[0,0]],"v":[[-7.1,1.37],[5.62,1.74],[5.81,1.37],[2.42,-6.01]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1256.689,240.22]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.7,0.57],[1.23,0.04],[1.21,-6.18],[-12.11,-3.78],[-8.62,14.11],[4.17,1.79],[1.87,-0.89],[0.96,-3.7],[-0.39,-1.74],[-1,-0.54],[-0.59,7.74]],"o":[[-1.85,-1.34],[-1,-0.33],[-3.88,-0.14],[-1.59,8.15],[13.19,3.27],[7.15,-13.9],[-1.66,-0.71],[-2.83,1.35],[-0.87,3.36],[0.33,1.46],[2.18,1.2],[0,0]],"v":[[-9.74,-9.17],[-15.48,-12.1],[-18.9,-12.71],[-28.39,-4.96],[-11.12,15.5],[22.83,4.38],[21.64,-18.06],[16.13,-17.68],[9.73,-9.72],[9.09,-2.15],[11.12,0.8],[19.73,-7.63]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1268.849,243.37]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.18,0.28],[-0.87,3.36],[-2.83,1.35],[-1.66,-0.71],[7.15,-13.9],[13.19,3.27],[-1.59,8.15],[-3.88,-0.14],[-1,-0.33],[-1.85,-1.34],[0,0],[1.29,-2.82],[0.07,-0.12],[-2.79,2.34],[-0.58,-0.48]],"o":[[-0.39,-1.74],[0.96,-3.7],[1.87,-0.89],[4.17,1.79],[-8.62,14.11],[-12.11,-3.78],[1.21,-6.18],[1.23,0.04],[1.7,0.57],[0,0],[2.7,1.95],[-0.06,0.13],[2.34,0.83],[0.35,0.67],[4.39,3.17]],"v":[[9.09,-2.15],[9.73,-9.72],[16.13,-17.68],[21.64,-18.06],[22.83,4.38],[-11.12,15.5],[-28.39,-4.96],[-18.9,-12.71],[-15.48,-12.1],[-9.74,-9.17],[-9.74,-9.16],[-6.35,-1.78],[-6.54,-1.41],[0.12,-2.38],[1.52,-0.64]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1268.849,243.37]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4,-3.97],[0,0],[0.87,-1.23],[-2.19,-4.06],[2.34,0.83],[-0.06,0.13],[2.7,1.95],[0,0],[-2.55,4.64],[-0.96,0.83]],"o":[[0,0],[-1.23,0.76],[-2.76,3.95],[-2.79,2.34],[0.07,-0.12],[1.29,-2.82],[0,0],[-0.49,-1.89],[0.64,-1.17],[2.86,-2.46]],"v":[[7.92,-8.265],[7.98,-8.165],[4.78,-5.105],[2.46,9.895],[-4.2,10.865],[-4.01,10.495],[-7.4,3.115],[-7.4,3.105],[-5.43,-6.215],[-3,-9.255]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1266.509,231.095]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.07,-4.27],[0,0],[0.64,-1.17],[-0.49,-1.89],[1.7,0.57],[1.23,0.04],[-3.01,3.81]],"o":[[0,0],[-0.96,0.83],[-2.55,4.64],[-1.85,-1.34],[-1,-0.33],[0,0],[3.01,-3.81]],"v":[[6.94,-4.155],[7.13,-3.935],[4.7,-0.895],[2.73,8.425],[-3.01,5.495],[-6.43,4.885],[-4.12,-3.295]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1256.379,225.775]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.23,0.76],[-2.89,-4.76],[0.96,-3.7],[-0.39,-1.74],[4.39,3.17],[0.35,0.67],[-2.76,3.95]],"o":[[3.34,-2.1],[-2.83,1.35],[-0.87,3.36],[-0.18,0.28],[-0.58,-0.48],[-2.19,-4.06],[0.87,-1.23]],"v":[[-1.39,-10.435],[9.1,-7.675],[2.7,0.285],[2.06,7.855],[-5.51,9.365],[-6.91,7.625],[-4.59,-7.375]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1275.879,233.365]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":44,"st":0},{"ind":5,"ty":4,"nm":"a","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-214.47,266.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-6.13,0.64]],"o":[[4.18,5.01],[0,0]],"v":[[-7.73,-3.595],[7.73,2.955]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1257.679,280.725]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-5.39,0.92],[0,0]],"o":[[5.39,3.9],[0,0],[0,0]],"v":[[-8.085,-2.695],[8.075,1.775],[8.085,1.775]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1260.544,269.825]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.42,-1.15]],"o":[[0,0],[3.14,4.74],[0,0]],"v":[[-7.165,-4.425],[-7.165,-4.415],[7.165,4.425]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1252.934,293.195]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.63,0.66]],"o":[[0.36,-5.98],[0,0]],"v":[[3.075,4.925],[-3.435,-4.925]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1268.064,251.725]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-5.3,-0.31]],"o":[[1.23,-7.85],[0,0]],"v":[[-5.175,5.245],[5.175,-4.935]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1259.454,251.735]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-7.05,15.66],[-1.54,4.9],[-0.91,4.19],[-0.62,5.21]],"o":[[11.12,-10.34],[1.99,-4.4],[1.23,-3.86],[1.06,-4.76],[0,0]],"v":[[-19.435,39.785],[8.395,1.185],[13.705,-12.755],[16.915,-24.835],[19.435,-39.785]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1251.704,296.435]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.68,-3.22],[0.94,-3.16],[1.58,-3.65]],"o":[[-0.53,3.56],[-0.74,3.51],[-1.22,4.13],[0,0]],"v":[[4.255,-15.895],[2.435,-5.745],[-0.075,4.255],[-4.255,15.895]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1250.024,272.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[9.56,-10.05]],"o":[[-5.12,11.94],[0,0]],"v":[[10.875,-15.895],[-10.875,15.895]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1234.894,304.675]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-6.42,-1.15],[11.12,-10.34],[0,0],[-5.12,11.94]],"o":[[-7.05,15.66],[0,0],[9.56,-10.05],[3.14,4.74]],"v":[[18.04,-14.88],[-9.79,23.72],[-18.04,8.07],[3.71,-23.72]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1242.059,312.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.22,4.13],[-6.13,0.64],[1.99,-4.4],[3.14,4.74],[0,0]],"o":[[4.18,5.01],[-1.54,4.9],[-6.42,-1.15],[0,0],[1.58,-3.65]],"v":[[-5.64,-10.245],[9.82,-3.695],[4.51,10.245],[-9.82,1.405],[-9.82,1.395]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1255.589,287.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.3,-0.31],[0.36,-5.98],[1.06,-4.76],[5.39,3.9],[-0.53,3.56]],"o":[[4.63,0.66],[-0.62,5.21],[-5.39,0.92],[0.68,-3.22],[1.23,-7.85]],"v":[[2.65,-12.705],[9.16,-2.855],[6.64,12.095],[-9.52,7.625],[-7.7,-2.525]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1261.979,259.505]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.74,3.51],[-5.39,0.92],[1.23,-3.86],[4.18,5.01]],"o":[[5.39,3.9],[-0.91,4.19],[-6.13,0.64],[0.94,-3.16]],"v":[[-6.825,-8.595],[9.335,-4.125],[6.125,7.955],[-9.335,1.405]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1259.284,275.725]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":44,"st":0},{"ind":6,"ty":4,"nm":"a","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-116.47,239.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-25.92,23.58],[-1.5,-2.76],[8.92,-8.56],[30.96,-0.76],[0.38,8.8]],"o":[[50.97,3.27],[5.48,-4.99],[1.5,2.76],[-7.4,7.61],[-30.96,0.76],[-0.38,-8.79]],"v":[[-50.885,6.21],[42.105,-19.95],[54.035,-19.35],[48.835,-2.67],[-18.395,24.18],[-57.375,14.62]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1181.913,340.52]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.5,-2.76],[8.92,-8.56],[30.96,-0.76],[0.38,8.8],[0,0],[-25.92,23.58]],"o":[[1.5,2.76],[-7.4,7.61],[-30.96,0.76],[-0.38,-8.79],[50.97,3.27],[5.48,-4.99]],"v":[[54.035,-19.35],[48.835,-2.67],[-18.395,24.18],[-57.375,14.62],[-50.885,6.21],[42.105,-19.95]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1181.914,340.52]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":0,"op":44,"st":0},{"ind":7,"ty":4,"nm":"h","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-246.97,338.11,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[3.01,-3.81],[0,0]],"o":[[-4.07,-4.27],[-3.01,3.81],[0,0]],"v":[[7.035,-2.385],[-4.025,-1.525],[-6.335,6.655]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1256.284,224.005]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.79,2.34]],"o":[[2.34,0.83],[0,0]],"v":[[-3.33,-0.2],[3.33,-1.17]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1265.639,242.16]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[2.86,-2.46],[0.64,-1.17],[-0.49,-1.89],[0,0]],"o":[[-4,-3.97],[-0.96,0.83],[-2.55,4.64],[0,0],[0,0]],"v":[[7.95,-3.705],[-2.97,-4.695],[-5.4,-1.655],[-7.37,7.665],[-7.37,7.675]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1266.479,226.535]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.01,0],[3.34,-2.1],[0.87,-1.23],[-2.19,-4.06],[-0.58,-0.48],[-0.18,0.28],[0,0]],"o":[[0,0],[-2.89,-4.76],[-1.23,0.76],[-2.76,3.95],[0.35,0.67],[4.39,3.17],[0,0],[0,0]],"v":[[9.105,-7.665],[9.095,-7.675],[-1.395,-10.435],[-4.595,-7.375],[-6.915,7.625],[-5.515,9.365],[2.055,7.855],[2.065,7.845]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1275.884,233.365]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-2.43,4.27],[-0.06,0.13],[2.7,1.95]],"o":[[2.7,2.32],[0.07,-0.12],[1.29,-2.82],[0,0]],"v":[[-7.1,1.37],[5.62,1.74],[5.81,1.37],[2.42,-6.01]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1256.689,240.22]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.7,0.57],[1.23,0.04],[1.21,-6.18],[-12.11,-3.78],[-8.62,14.11],[4.17,1.79],[1.87,-0.89],[0.96,-3.7],[-0.39,-1.74],[-1,-0.54],[-0.59,7.74]],"o":[[-1.85,-1.34],[-1,-0.33],[-3.88,-0.14],[-1.59,8.15],[13.19,3.27],[7.15,-13.9],[-1.66,-0.71],[-2.83,1.35],[-0.87,3.36],[0.33,1.46],[2.18,1.2],[0,0]],"v":[[-9.74,-9.17],[-15.48,-12.1],[-18.9,-12.71],[-28.39,-4.96],[-11.12,15.5],[22.83,4.38],[21.64,-18.06],[16.13,-17.68],[9.73,-9.72],[9.09,-2.15],[11.12,0.8],[19.73,-7.63]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1268.849,243.37]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.18,0.28],[-0.87,3.36],[-2.83,1.35],[-1.66,-0.71],[7.15,-13.9],[13.19,3.27],[-1.59,8.15],[-3.88,-0.14],[-1,-0.33],[-1.85,-1.34],[0,0],[1.29,-2.82],[0.07,-0.12],[-2.79,2.34],[-0.58,-0.48]],"o":[[-0.39,-1.74],[0.96,-3.7],[1.87,-0.89],[4.17,1.79],[-8.62,14.11],[-12.11,-3.78],[1.21,-6.18],[1.23,0.04],[1.7,0.57],[0,0],[2.7,1.95],[-0.06,0.13],[2.34,0.83],[0.35,0.67],[4.39,3.17]],"v":[[9.09,-2.15],[9.73,-9.72],[16.13,-17.68],[21.64,-18.06],[22.83,4.38],[-11.12,15.5],[-28.39,-4.96],[-18.9,-12.71],[-15.48,-12.1],[-9.74,-9.17],[-9.74,-9.16],[-6.35,-1.78],[-6.54,-1.41],[0.12,-2.38],[1.52,-0.64]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1268.849,243.37]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4,-3.97],[0,0],[0.87,-1.23],[-2.19,-4.06],[2.34,0.83],[-0.06,0.13],[2.7,1.95],[0,0],[-2.55,4.64],[-0.96,0.83]],"o":[[0,0],[-1.23,0.76],[-2.76,3.95],[-2.79,2.34],[0.07,-0.12],[1.29,-2.82],[0,0],[-0.49,-1.89],[0.64,-1.17],[2.86,-2.46]],"v":[[7.92,-8.265],[7.98,-8.165],[4.78,-5.105],[2.46,9.895],[-4.2,10.865],[-4.01,10.495],[-7.4,3.115],[-7.4,3.105],[-5.43,-6.215],[-3,-9.255]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1266.509,231.095]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.07,-4.27],[0,0],[0.64,-1.17],[-0.49,-1.89],[1.7,0.57],[1.23,0.04],[-3.01,3.81]],"o":[[0,0],[-0.96,0.83],[-2.55,4.64],[-1.85,-1.34],[-1,-0.33],[0,0],[3.01,-3.81]],"v":[[6.94,-4.155],[7.13,-3.935],[4.7,-0.895],[2.73,8.425],[-3.01,5.495],[-6.43,4.885],[-4.12,-3.295]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1256.379,225.775]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.23,0.76],[-2.89,-4.76],[0.96,-3.7],[-0.39,-1.74],[4.39,3.17],[0.35,0.67],[-2.76,3.95]],"o":[[3.34,-2.1],[-2.83,1.35],[-0.87,3.36],[-0.18,0.28],[-0.58,-0.48],[-2.19,-4.06],[0.87,-1.23]],"v":[[-1.39,-10.435],[9.1,-7.675],[2.7,0.285],[2.06,7.855],[-5.51,9.365],[-6.91,7.625],[-4.59,-7.375]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1275.879,233.365]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":60,"op":80,"st":0},{"ind":8,"ty":4,"nm":"a","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-214.47,266.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-6.13,0.64]],"o":[[4.18,5.01],[0,0]],"v":[[-7.73,-3.595],[7.73,2.955]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1257.679,280.725]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-5.39,0.92],[0,0]],"o":[[5.39,3.9],[0,0],[0,0]],"v":[[-8.085,-2.695],[8.075,1.775],[8.085,1.775]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1260.544,269.825]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.42,-1.15]],"o":[[0,0],[3.14,4.74],[0,0]],"v":[[-7.165,-4.425],[-7.165,-4.415],[7.165,4.425]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1252.934,293.195]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.63,0.66]],"o":[[0.36,-5.98],[0,0]],"v":[[3.075,4.925],[-3.435,-4.925]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1268.064,251.725]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-5.3,-0.31]],"o":[[1.23,-7.85],[0,0]],"v":[[-5.175,5.245],[5.175,-4.935]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1259.454,251.735]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-7.05,15.66],[-1.54,4.9],[-0.91,4.19],[-0.62,5.21]],"o":[[11.12,-10.34],[1.99,-4.4],[1.23,-3.86],[1.06,-4.76],[0,0]],"v":[[-19.435,39.785],[8.395,1.185],[13.705,-12.755],[16.915,-24.835],[19.435,-39.785]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1251.704,296.435]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.68,-3.22],[0.94,-3.16],[1.58,-3.65]],"o":[[-0.53,3.56],[-0.74,3.51],[-1.22,4.13],[0,0]],"v":[[4.255,-15.895],[2.435,-5.745],[-0.075,4.255],[-4.255,15.895]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1250.024,272.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[9.56,-10.05]],"o":[[-5.12,11.94],[0,0]],"v":[[10.875,-15.895],[-10.875,15.895]],"c":false}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1234.894,304.675]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-6.42,-1.15],[11.12,-10.34],[0,0],[-5.12,11.94]],"o":[[-7.05,15.66],[0,0],[9.56,-10.05],[3.14,4.74]],"v":[[18.04,-14.88],[-9.79,23.72],[-18.04,8.07],[3.71,-23.72]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1242.059,312.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.22,4.13],[-6.13,0.64],[1.99,-4.4],[3.14,4.74],[0,0]],"o":[[4.18,5.01],[-1.54,4.9],[-6.42,-1.15],[0,0],[1.58,-3.65]],"v":[[-5.64,-10.245],[9.82,-3.695],[4.51,10.245],[-9.82,1.405],[-9.82,1.395]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1255.589,287.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-5.3,-0.31],[0.36,-5.98],[1.06,-4.76],[5.39,3.9],[-0.53,3.56]],"o":[[4.63,0.66],[-0.62,5.21],[-5.39,0.92],[0.68,-3.22],[1.23,-7.85]],"v":[[2.65,-12.705],[9.16,-2.855],[6.64,12.095],[-9.52,7.625],[-7.7,-2.525]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.443,0.004,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1261.979,259.505]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.74,3.51],[-5.39,0.92],[1.23,-3.86],[4.18,5.01]],"o":[[5.39,3.9],[-0.91,4.19],[-6.13,0.64],[0.94,-3.16]],"v":[[-6.825,-8.595],[9.335,-4.125],[6.125,7.955],[-9.335,1.405]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[1,0.98,0.941,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1259.284,275.725]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":60,"op":80,"st":0},{"ind":9,"ty":4,"nm":"a","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-116.47,239.61,0],"l":2},"a":{"a":0,"k":[965.53,543.11,0],"l":2},"s":{"a":0,"k":[100,100,100],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-25.92,23.58],[-1.5,-2.76],[8.92,-8.56],[30.96,-0.76],[0.38,8.8]],"o":[[50.97,3.27],[5.48,-4.99],[1.5,2.76],[-7.4,7.61],[-30.96,0.76],[-0.38,-8.79]],"v":[[-50.885,6.21],[42.105,-19.95],[54.035,-19.35],[48.835,-2.67],[-18.395,24.18],[-57.375,14.62]],"c":true}},"nm":"P"},{"ty":"st","c":{"a":0,"k":[0,0.129,0.251,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"nm":"S"},{"ty":"tr","p":{"a":0,"k":[1181.913,340.52]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.5,-2.76],[8.92,-8.56],[30.96,-0.76],[0.38,8.8],[0,0],[-25.92,23.58]],"o":[[1.5,2.76],[-7.4,7.61],[-30.96,0.76],[-0.38,-8.79],[50.97,3.27],[5.48,-4.99]],"v":[[54.035,-19.35],[48.835,-2.67],[-18.395,24.18],[-57.375,14.62],[-50.885,6.21],[42.105,-19.95]],"c":true}},"nm":"P"},{"ty":"fl","c":{"a":0,"k":[0,0.549,0.349,1]},"o":{"a":0,"k":100},"r":1,"nm":"F"},{"ty":"tr","p":{"a":0,"k":[1181.914,340.52]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"G"}],"ip":60,"op":80,"st":0}]}],"layers":[{"ind":1,"ty":0,"nm":"C","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[187.5,120,0],"l":2},"a":{"a":0,"k":[187.5,200,0],"l":2},"s":{"a":0,"k":[60,60,100],"l":2}},"ao":0,"w":375,"h":400,"ip":0,"op":80,"st":0}],"markers":[]} \ No newline at end of file diff --git a/assets/images/eReceipt_background.svg b/assets/images/eReceipt_background.svg new file mode 100644 index 000000000000..5070ed3b2f24 --- /dev/null +++ b/assets/images/eReceipt_background.svg @@ -0,0 +1,1635 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/new-expensify-adhoc.svg b/assets/images/new-expensify-adhoc.svg index 26f18c8cc088..d3a926a097ec 100644 --- a/assets/images/new-expensify-adhoc.svg +++ b/assets/images/new-expensify-adhoc.svg @@ -1,6 +1,6 @@ diff --git a/assets/images/new-expensify-dark.svg b/assets/images/new-expensify-dark.svg index bcdb3c87f164..ad34f1d9dfce 100644 --- a/assets/images/new-expensify-dark.svg +++ b/assets/images/new-expensify-dark.svg @@ -1,29 +1,10 @@ - - - - - - - - - - - - - - - - - - + + diff --git a/assets/images/simple-illustrations/simple-illustration__handearth.svg b/assets/images/simple-illustrations/simple-illustration__handearth.svg new file mode 100644 index 000000000000..f79e3f73293b --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__handearth.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Business-Bank-Account-(AUD).md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Business-Bank-Account-(AUD).md index 6f0c738693ca..1fa5734293ac 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Business-Bank-Account-(AUD).md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Business-Bank-Account-(AUD).md @@ -7,16 +7,17 @@ description: This article provides insight on setting up and using an Australian A withdrawal account is the business bank account that you want to use to pay your employee reimbursements. _Your policy currency must be set to AUD and reimbursement setting set to Indirect to continue. If your main policy is used for something other than AUD, then you will need to create a new one and set that policy to AUD._ + To set this up, you’ll run through the following steps: -1. Go to *Settings > Your Account > Payments* and click *Add Verified Bank Account* +1. Go to **Settings > Your Account > Payments** and click **Add Verified Bank Account** ![Click the Verified Bank Account button in the bottom right-hand corner of the screen](https://help.expensify.com/assets/images/add-vba-australian-account.png){:width="100%"} 2. Enter the required information to connect to your business bank account. If you don't know your Bank User ID/Direct Entry ID/APCA Number, please contact your bank and they will be able to provide this. ![Enter your information in each of the required fields](https://help.expensify.com/assets/images/add-vba-australian-account-modal.png){:width="100%"} -3. Link the withdrawal account to your policy by heading to *Settings > Policies > Group > [Policy name] > Reimbursement* -4. Click *Direct* reimbursement +3. Link the withdrawal account to your policy by heading to **Settings > Policies > Group > [Policy name] > Reimbursement** +4. Click **Direct reimbursement** 5. Set the default withdrawal account for processing reimbursements 6. Tell your employees to add their deposit accounts and start reimbursing. @@ -24,7 +25,7 @@ To set this up, you’ll run through the following steps: If you’re no longer using a bank account you previously connected to Expensify, you can delete it by doing the following: 1. Navigate to Settings > Accounts > Payments -2. Click *Delete* +2. Click **Delete** ![Click the Delete button](https://help.expensify.com/assets/images/delete-australian-bank-account.png){:width="100%"} You can complete this process either via the web app (on a computer), or via the mobile app. @@ -34,14 +35,14 @@ You can complete this process either via the web app (on a computer), or via the If you are new to using Batch Payments in Australia, to reimburse your staff or process payroll, you may want to check out these bank-specific instructions for how to upload your .aba file: -ANZ Bank - [Import a file for payroll payments](https://www.anz.com.au/support/internet-banking/pay-transfer-business/payroll/import-file/) -CommBank - [Importing and using
 Direct Entry (EFT) files](https://www.commbank.com.au/business/pds/003-279-importing-a-de-file.pdf) -Westpac - [Importing Payment Files](https://www.westpac.com.au/business-banking/online-banking/support-faqs/import-files/) -NAB - [Quick Reference Guide - Upload a payment file](https://www.nab.com.au/business/online-banking/nab-connect/help) -Bendigo Bank - [Bulk payments user guide](https://www.bendigobank.com.au/globalassets/documents/business/bulk-payments-user-guide.pdf) -Bank of Queensland - [Payments file upload facility FAQ](https://www.boq.com.au/help-and-support/online-banking/ob-faqs-and-support/faq-pfuf) +- ANZ Bank - [Import a file for payroll payments](https://www.anz.com.au/support/internet-banking/pay-transfer-business/payroll/import-file/) +- CommBank - [Importing and using
 Direct Entry (EFT) files](https://www.commbank.com.au/business/pds/003-279-importing-a-de-file.pdf) +- Westpac - [Importing Payment Files](https://www.westpac.com.au/business-banking/online-banking/support-faqs/import-files/) +- NAB - [Quick Reference Guide - Upload a payment file](https://www.nab.com.au/business/online-banking/nab-connect/help) +- Bendigo Bank - [Bulk payments user guide](https://www.bendigobank.com.au/globalassets/documents/business/bulk-payments-user-guide.pdf) +- Bank of Queensland - [Payments file upload facility FAQ](https://www.boq.com.au/help-and-support/online-banking/ob-faqs-and-support/faq-pfuf) -*Note:* Some financial institutions require an ABA file to include a *self-balancing transaction*. If you are unsure, please check with your bank to ensure whether to tick this option or not, as selecting an incorrect option will result in the ABA file not working with your bank's internet banking platform. +**Note:** Some financial institutions require an ABA file to include a *self-balancing transaction*. If you are unsure, please check with your bank to ensure whether to tick this option or not, as selecting an incorrect option will result in the ABA file not working with your bank's internet banking platform. ## Enable Global Reimbursement diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md index 8e2aa7d4a377..67a96610633d 100644 --- a/docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md @@ -1,5 +1,45 @@ --- title: Annual Subscription -description: Annual Subscription +description: Learn more about managing your Annual Subscription. --- -## Resource Coming Soon! +# Overview +An Annual Subscription offers a 50% cost savings on active user pricing while allowing your company to manage multiple Workspaces across your organization and maintain predictable cost for your Expensify activity. + +_For pricing details, see [expensify.com/pricing](http://www.expensify.com/pricing), and find more ways to save with the Expensify Card here._ + +# How to set subscription size +When you first create a subscription, the best practice is to set your subscription size by entering the average number of active users you expect to have each month for the next year. For example, if you expect to have an average of 10 users each month, even if they are not always the same users, set your subscription size to 10. No need to provision and deprovision access to your team, so you still enjoy flexible usage across the entire company! + +If your Workspaces have more than 10 active users in a month, you will pay the unbundled Pay-per-use rate for the additional users. If you’d like to avoid this, you can enable Auto Increase so your subscription size increases based on Workspace user activity. + +An ‘Active User’ is anyone who chats, creates, submits, approves, reimburses, or exports a report in Expensify. This includes actions taken by Copilots and any automated settings. + +To set your subscription size, go to **Settings > Workspaces > Groups > Subscription**. + +If you do not set a specific subscription size, this will be automatically updated based on your past activity: + +* If you’ve never had activity in Expensify, your subscription size will be set after your first month. Work with your Setup Specialist or Account Manager to determine the best subscription size for your team! + +* For existing Workspaces switching to an Annual Subscription, the subscription size is set to the number of active users on your last month’s billing history. + +* If Auto Increase is not selected, and you have more active users than you’ve input as the subscription size, you will be billed for those at the Pay-per-use rate. + +# How to adjust subscription size +You can add users to your subscription at any time. However, note that when your subscription size is increased, you will start a new 12-month subscription at that new subscription size. + +You can increase your subscription size manually or automatically. + +* To manually increase the size, just update the number in your subscription settings (**Settings > Workspaces > Groups > Subscription**). + +* To automatically increase your subscription size, enable **Auto Increase**. This feature manages your subscription by automatically increasing the count whenever there is activity that exceeds your subscription size. (**Settings > Workspaces > Groups > Subscription**) + +Note: After increasing your subscription size, you won't be able to decrease it for the next 12 months. If your active user numbers tend to fluctuate, you might want to keep this feature disabled. This way, you'll only pay for additional active users in the months they are active. Keep in mind that increasing the subscription size will reset your 12-month subscription period. + +# How to disable Auto Renew +By default, your subscription is set to automatically renew after a year. To disable this, click the toggle from your subscription settings before the current subscription ends. (**Settings > Workspaces > Groups > Subscription**) + +If Auto Renew is disabled, then the last bill at the annual rate will be issued on the date listed under the Auto Renew settings. For example, if your subscription expires on March 1, 2021, then February 2021 will be the last month billed at the annual rate. If you do not set a new subscription, March activity will be billed at the Pay-per-use rate. + +We recommend that you review your user count annually on a proactive basis. Set a reminder to review your active user numbers a month before your subscription expires! If you’d like assistance determining your subscription number, please contact your Account Manager or concierge@expensify.com. + +If you need to decrease your subscription size, you can do this in the first billing month before you are billed. Using the example above, this would be during March 2021. diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md index 1ace758978aa..aa08340dd7a6 100644 --- a/docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md @@ -1,5 +1,67 @@ --- title: Individual Subscription -description: Individual Subscription +description: Learn more about managing an Individual Subscription. --- -## Resource Coming Soon! +# Overview +An Individual Subscription is a great option for solo entrepreneurs or anyone who needs to track their own expenses or get paid by someone outside their own organization. +A free Individual Subscription includes: +- Up to 25 SmartScans/month +- Expense tracking +- Mileage tracking +- Invoicing +- Bill splitting +- Receive & send money + +To get unlimited SmartScans, you can upgrade your Individual Subscription for $4.99 (USD per month. + + +# How to sign up for an Individual Subscription +## Website +To activate an Individual Subscription from the web: +1. Log into your Expensify account +2. Navigate to **Settings > Workspaces > Individual** +3. Click **Activate Subscription** under **Monthly** +4. If you don't already have a billing card associated with your account, you will be prompted to add one + +Once payment is complete, you’re all set! + +## Mobile App: +1. Tap **Settings** +2. Under the Workspaces section, select **Free Trial** +3. Select **Upgrade** +4.Tap **Subscription** to upgrade your account + + +# How to manage the subscription +## Web and Android: +When you buy a subscription on the web or through an Android device, you'll be asked to enter your billing information immediately. After the purchase, you can easily view or cancel your subscription anytime by going to **Settings > Workspaces > Individual > Subscription > Show Details**. + +## iOS: +If you purchase a monthly subscription on an iOS device, it will be managed, including cancellations, through the App Store rather than within the Expensify app. You can learn how to manage App Store Subscriptions here. + +After purchasing the subscription from the App Store, remember to sync your app by: +1. Log into the Expensify mobile app +2. Click the three bars in the upper left corner +3. Scroll to **Settings** +4. Select **Sync Account** + +The subscription renewal date is the same as the purchase date. For instance, if you sign up for the subscription on September 7th, it will renew automatically on October 7th. You can cancel your subscription anytime during the month if you no longer need unlimited SmartScans. If you do cancel, keep in mind that your subscription (and your ability to SmartScan) will continue until the last day of the billing cycle. + + +# FAQ +## Can I use an Individual Subscription while on a Collect or Control Plan? +You can! If you want to track expenses separately from your organization’s Workspace, you can sign up for an Individual Subscription. However, only Submit and Track Workspace plans are available when on an Individual Subscription. Collect and Control Workspace plans require an annual or pay-per-use subscription. For more information, visit expensify.com/pricing. + +## Can I cancel an Individual Subscription anytime? +Yep! You can cancel an Individual Subscription anytime. + +## How do I cancel my subscription? +Follow the steps below to cancel a Monthly Subscription started via the website or Android app: +1. Log into your account using your preferred web browser (ex: Firefox, Chrome, Safari) +2. Navigate to **Settings > Workspace > Individual > Subscriptions** +3. Click the **Cancel Subscription** button to cancel your Monthly Subscription + +Your subscription is a pre-purchase for 30 days of unlimited SmartScanning. This means when you cancel you do not get a refund and instead get to use the remainder of the month of unlimited SmartScanning you purchased. + +## How can I cancel my subscription from the iOS app? +If you signed up for the Monthly Subscription via iOS and your iTunes account, you will need to log into iTunes and locate the subscription there in order to cancel it. The ability to cancel an Expensify subscription started via iOS is strictly limited to your iTunes account. diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md index 77aca2a01678..1d689f5b0355 100644 --- a/docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md @@ -1,5 +1,29 @@ --- title: Pay-per-use Subscription -description: Pay-per-use Subscription +description: Learn more about your pay-per-use subscription. --- -## Resource Coming Soon! +# Overview +Pay-per-use is a billing option for people who prefer to use Expensify month to month or on an as-needed basis. On a pay-per-use subscription, you will only pay for active users in that given month. + +**We recommend this billing setup for companies that use Expensify a few months out of the year**. If you have expenses to manage for more than 6 out of 12 months, an [**Annual Subscription**](https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription#gsc.tab=0) may better suit your needs. + +# How to start a pay-per-use subscription +1. Create a Group Workspace if you haven’t already by going to **Settings > Workspaces > Group > New Workspace** +2. Once you’ve created your Workspace, under the “Subscription” section on the Group Workspace page, select “Pay-per-use”. + +# FAQ + +## What is considered an active user? +An active user is anyone who chats, creates, modifies, submits, approves, reimburses, or exports a report in Expensify. This includes actions taken by a Copilot and Workspace automation (such as Scheduled Submit and automated reimbursement). If no one on your Group Workspace uses Expensify in a given month, you will not be billed for that month. + +You can review the number of Active Users by selecting “View Activity” next to your billing receipt (**Settings > Account > Payments > Billing History**). + +## Why do I have pay-per-use users in addition to my Annual Subscription on my Expensify bill? +If you have an Annual Subscription, but go above your set user count, we will charge at the pay-per-use rate for these ad-hoc users. + +If you expect to have an increased number of users for more than 3 out of 12 months, the most cost-effective approach is to increase your Annual Subscription size. + +## Will billing only be in USD currency? +While USD is the default billing currency, we also have GBP, AUD, and NZD billing currencies. You can see the rates on our [pricing](https://www.expensify.com/pricing) page. + + diff --git a/docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md b/docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md index 304c93d1da6d..ae6a9ca77db1 100644 --- a/docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md +++ b/docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md @@ -1,5 +1,55 @@ --- title: Expense Rules -description: Expense Rules +description: Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant's name. + --- -## Resource Coming Soon! +# Overview +Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant’s name. + +# How to use Expense Rules +**To create an expense rule, follow these steps:** +1. Navigate to **Settings > Account > Expense Rules** +2. Click on **New Rule** +3. Fill in the required information to set up your rule + +When creating an expense rule, you will be able to apply the following rules to expenses: + +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_01.png){:width="100%"} + +- **Merchant:** Updates the merchant name, e.g., “Starbucks #238” could be changed to “Starbucks” +- **Category:** Applies a workspace category to the expense +- **Tag:** Applies a tag to the expense, e.g., a Department or Location +- **Description:** Adds a description to the description field on the expense +- **Reimbursability:** Determines whether the expense will be marked as reimbursable or non-reimbursable +- **Billable**: Determines whether the expense is billable +- **Add to a report named:** Adds the expense to a report with the name you type into the field. If no report with that name exists, a new report will be created + +## Tips on using Expense Rules +- If you'd like to apply a rule to all expenses (“Universal Rule”) rather than just one merchant, simply enter a period [.] and nothing else into the **“When the merchant name contains:”** field. **Note:** Universal Rules will always take precedence over all other rules for category (more on this below). +- You can apply a rule to previously entered expenses by checking the **Apply to existing matching expenses** checkbox. Click “Preview Matching Expenses” to see if your rule matches the intended expenses. +- You can create expense rules while editing an expense. To do this, simply check the box **“Create a rule based on your changes"** at the time of editing. Note that the expense must be saved, reopened, and edited for this option to appear. + + +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_02.png){:width="100%"} + + +To delete an expense rule, go to **Settings > Account > Expense Rules**, scroll down to the rule you’d like to remove, and then click the trash can icon in the upper right corner of the rule: + +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_03.png){:width="100%"} + +# Deep Dive +In general, your expense rules will be applied in order, from **top to bottom**, i.e., from the first rule. However, other settings can impact how expense rules are applied. Here is the hierarchy that determines how these are applied: +1. A Universal Rule will **always** precede over any other expense category rules. Rules that would otherwise change the expense category will **not** override the Universal Rule. +2. If Scheduled Submit and the setting “Enforce Default Report Title” are enabled on the workspace, this will take precedence over any rules trying to add the expense to a report. +3. If the expense is from a Company Card that is forced to a workspace with strict rule enforcement, those rules will take precedence over individual expense rules. +4. If you belong to a workspace that is tied to an accounting integration, the configuration settings for this connection may update your expense details upon export, even if the expense rules were successfully applied to the expense. + + +# FAQ +## How can I use Expense Rules to vendor match when exporting to an accounting package? +When exporting non-reimbursable expenses to your connected accounting package, the payee field will list "Credit Card Misc." if the merchant name on the expense in Expensify is not an exact match to a vendor in the accounting package. +When an exact match is unavailable, "Credit Card Misc." prevents multiple variations of the same vendor (e.g., Starbucks and Starbucks #1234, as is often seen in credit card statements) from being created in your accounting package. +For repeated expenses, the best practice is to use Expense Rules, which will automatically update the merchant name without having to do it manually each time. +This only works for connections to QuickBooks Online, Desktop, and Xero. Vendor matching cannot be performed in this manner for NetSuite or Sage Intacct due to limitations in the API of the accounting package. + + diff --git a/docs/articles/expensify-classic/expense-and-report-features/Report-Audit-Log-and-Comments.md b/docs/articles/expensify-classic/expense-and-report-features/Report-Audit-Log-and-Comments.md new file mode 100644 index 000000000000..229ca4ec1fe4 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Report-Audit-Log-and-Comments.md @@ -0,0 +1,60 @@ +--- +title: Report Audit Log and Comments +description: Details on the expense report audit log and how to leave comments on reports +--- + +# Overview + +At the bottom of each expense report, there’s a section that acts as an audit log for the report. This section details report actions, such as submitting, approving, or exporting. The audit log records the user who completed the action as well as the timestamp for the action. + +This section also doubles as the space where submitters, approvers, and admins can converse with each other by leaving comments. Comments trigger notifications to everyone connected to the report and help facilitate communication inside of Expensify. + +# How to use the audit log + +All report actions are recorded in the audit log. Anytime you need to identify who touched a report or track its progress through the approval process, simply scroll down to the bottom of the report and review the log. + +Each recorded action is timestamped - tap or mouse over the timestamp to see the exact date and time the action occurred. + +# How to use report comments + +There’s a freeform field just under the audit log where you can leave a comment on the report. Type in your comment and click or tap the green arrow to send. The comment will be visible to anyone with visibility on the report, and also automatically sent to anyone who has actioned the report. + +# Deep Dive + +## Audit log + +Here’s a list of actions recorded by the audit log: + +- Report creation +- Report submission +- Report approval +- Report reimbursement +- Exports to accounting or to CSV/Excel files +- Report and expense rejections +- Changes made to expenses by approvers/admins +- Changes made to report fields by approvers/admins +- Automated actions taken by Concierge + +Both manual and automated actions are recorded. If a report action is completed by Concierge, that generally indicates an automation feature triggered the action. For example, an entry that shows a report submitted by Concierge indicates that the **Scheduled Submit** feature is enabled. + +Note that timestamps for actions recorded in the log reflect your own timezone. You can either set a static time zone manually, or we can trace your location data to set a time zone automatically for you. + +To set your time zone manually, head to **Settings > Account > Preferences > Time Zone** and check **Automatically Set my Time Zone**, or uncheck the box and manually choose your time zone from the searchable list of locations. + +## Comments + +Anyone with visibility on a report can leave a comment. Comments are interspersed with audit log entries. + +Report comments initially trigger a mobile app notification to report participants. If you don't read the notification within a certain amount of time, you'll receive an email notification with the report comment instead. The email will include a link to the report, allowing you to view and add additional comments directly on the report. You can also reply directly to the email, which will record your response as a comment. + +Comments can be formatted with bold, italics, or strikethrough using basic Markdown formatting. You can also add receipts and supporting documents to a report by clicking the paperclip icon on the right side of the comment field. + +# FAQ + +## Why don’t some timestamps in Expensify match up with what’s shown in the report audit log? + +While the audit log is localized to your own timezone, some other features in Expensify (like times shown on the reports page) are not. Those use UTC as a baseline, so it’s possible that some times may look mismatched at first glance. In reality, it’s just a timezone discrepancy. + +## Is commenting on a report a billable action? + +Yes. If you comment on a report, you become a billable actor for the current month. diff --git a/docs/articles/expensify-classic/expense-and-report-features/Report-Comments.md b/docs/articles/expensify-classic/expense-and-report-features/Report-Comments.md deleted file mode 100644 index b7ed120fb28b..000000000000 --- a/docs/articles/expensify-classic/expense-and-report-features/Report-Comments.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Report Comments -description: Report Comments ---- -## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Set-Up-the-Card-for-Your-Company.md b/docs/articles/expensify-classic/expensify-card/Set-Up-the-Card-for-Your-Company.md new file mode 100644 index 000000000000..8f87b36ef3d9 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Set-Up-the-Card-for-Your-Company.md @@ -0,0 +1,67 @@ +--- +title: Set Up the Card for your Company +description: Details on setting up the Expensify Card for your company as an admin +--- +# Overview + +If you’re an admin interested in rolling out the Expensify Card for your organization, you’re in the right place. This article will cover how to qualify and apply for the Expensify Card program and begin issuing cards to your employees. + +# How to qualify for the Expensify Card program + +There are three prerequisites to consider before applying for the Expensify Card: + +1. The email address associated with your account must be on a private domain +2. You must claim your private domain in Expensify +3. You must add and verify a US business bank account to your Expensify account + +To claim a domain, you must be a workspace admin with a company email address matching the domain you want to claim. After you create an account and set up a workspace, head to **Settings > Domains** to claim your domain. + +You can add a business bank account by navigating to **Settings > Account > Payments** and clicking Add Verified Bank Account. Follow the setup steps and complete the verification process as required. + +# How to apply for the Expensify Card + +Once you’ve claimed your domain and added a verified US business bank account, you can apply for the Expensify Card. There are multiple ways to apply for the card from the web: + +## From the home page + +1. Log into your Expensify account using your preferred web browser +2. Head to your account’s home page +3. On the task that says “Introducing the Expensify Card,” click **Enable my Expensify Cards** to get started + +## From the Company Cards page + +1. Log into your Expensify account using your preferred web browser +2. Head to **Settings > Domains > _Domain Name_ > Company Cards** +3. Click **Get the Card** + +After we receive your application, we’ll review it ASAP and send you a confirmation email with the next steps once we have them. + +# How to issue cards + +After you’ve been approved, it’s time to set limits for your employees. Setting a limit triggers an email and task on the home page requesting the employee’s shipping address. Once they enter their details, a card will be shipped to them. We’ll also create a virtual card for the employee that can be used immediately. + +To set a limit, head over to the Company Cards UI via **Settings > Domains > _Domain Name_ > Company Cards**. Click the **Edit Limit** button next to members who need a card assigned, and set a non-$0 to issue them a card. + +If you have a validated domain, you can set a limit for multiple members by setting a limit for an entire domain group via **Settings > Domains > _Domain Name_ > Groups**. Keep in mind that custom limits that are set on an individual basis will override the group limit. + +The Company Cards page will act as a hub to view all employees who have been issued a card and where you can view and edit the individual card limits. You’ll also be able to see anyone who has requested a card but doesn’t have one yet. + +# FAQ + +## Are there foreign transaction fees? + +There are no foreign transaction fees when using your Expensify Card for international purchases. + +## How does the Expensify Card affect my or my company's credit score? + +Applying for or using the Expensify Card will never have any positive or negative effect on your personal credit score or your business's credit score. We do not consider your or your business' credit score when determining approval and your card limit. + +## How much does the Expensify Card cost? + +The Expensify Card is a free corporate card, and no fees are associated with it. In addition, if you use the Expensify Card, you can save money on your Expensify subscription. + +## If I have staff outside the US, can they use the Expensify Card? + +As long as the verified bank account used to apply for the Expensify Card is a US bank account, your cardholders can be anywhere in the world. + +Otherwise, the Expensify Card is not available for customers using non-US banks. With that said, launching international support is a top priority for us. Let us know if you’re interested in contacting support, and we’ll reach out as soon as the Expensify Card is available outside the United States. diff --git a/docs/articles/expensify-classic/get-paid-back/Distance-Tracking.md b/docs/articles/expensify-classic/get-paid-back/Distance-Tracking.md new file mode 100644 index 000000000000..c0d8956f71ac --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/Distance-Tracking.md @@ -0,0 +1,81 @@ +--- +title: Distance Tracking in Expensify +description: Learn how distance tracking works in Expensify! +--- + +# Overview + +Expensify provides a convenient feature for tracking your mileage-related expenses. You'll find all the essential information to begin logging your trips below. + +# How to Use Distance Tracking +## Mobile App + +First, you’ll want to click the **+** in the top right corner. + +If you select **Manually Create**, you’ll be prompted to enter your mileage, select a rate, and code the expense before clicking **Save**. + + ![Click manually create or odometer to create a distance request.](https://help.expensify.com/assets/images/ExpensifyHelp_CreateExpense_Mobile.png){:width="100%"} + +If you select **Manually Create**: + - Enter your mileage. + - Select a rate. + - Code the expense. + - Click **Save**. + +![Enter your mileage, rate, code the expense, and click save.](https://help.expensify.com/assets/images/ExpensifyHelp_ManualDistance_Mobile.png){:width="100%"} + +If you select **Odometer**: + - Enter your vehicle’s mileage reading before and after your trip. + - Select your rate. + - Code the expense. + - Click **Save**. + +![Etner your mileage readings, your rate, code the expense, and click save.](https://help.expensify.com/assets/images/ExpensifyHelp_Odometer_Mobile.png){:width="100%"} + +The **Start GPS** option also exists on the mobile app. However, we’ve learned that most customers prefer to track their mileage after their trips (thus not needing to hit that start button!) + +We’ve temporarily paused the development of GPS mileage tracking in the mobile app, and we recommend you use one of the above options instead! + + +## Web + +Navigate to the **Expenses** page, click **New Expense**, and review the two **Distance** options. + +![Select manually create or create from map to create a new distance request.](https://help.expensify.com/assets/images/ExpensifyHelp_CreateExpense.png){:width="100%"} + +If you select **Manually Create**: + - Enter the number of miles for your trip. + - Mileage rate is automatically selected based on your history, or manually select it if it's your first time. + - Complete any other applicable coding fields. + - Click **Save**. + +![Enter the number of miles, select your rate, code the expense, and click save.](https://help.expensify.com/assets/images/ExpensifyHelp_ManualDistance.png){:width="100%"} + +For **Create from Map** expenses: + - Add your start and end location, and the distance will be calculated. + - You can also click **Add Destination** for multiple stops. + - Leave **Create Receipt** selected if you want a map receipt generated. + - Click **Save**. + +![Enter your start and end locations, and click save.](https://help.expensify.com/assets/images/ExpensifyHelp_ManualDistanceMap.png){:width="100%"} + +Once you click **Save**, review the details from your map selection. + - Select your rate. + - Enter any other applicable coding. + - Click **Save**. + +![Select your rate, code the expense, and click save.](https://help.expensify.com/assets/images/ExpensifyHelp_ManualDistanceConfirm.png){:width="100%"} + +# Mileage Tracking FAQs +## **How can I change the rate of my mileage expenses?** +You can change the rate by going to Settings > Workspaces > [Your Workspace] > Expenses > Distance > Add a Mileage Rate. +If you submit mileage expenses on a group workspace, only workspace admins can do this. + +## **Do you plan to add the "Create from Map" option to the mobile app or "Odometer" option to web?** +Not now, but if that changes, you'll be the first to know! + +## **Will you restart maintenance on the mobile app's GPS option anytime soon?** +Not now, but if that changes, you'll be the first to know! + +## **Does Expensify automatically update IRS Mileage rates?** + We never automatically update mileage rates in Expensify because different companies want the new rates to become effective on different dates. diff --git a/docs/articles/expensify-classic/get-paid-back/Mileage.md b/docs/articles/expensify-classic/get-paid-back/Mileage.md deleted file mode 100644 index 248e80e1c115..000000000000 --- a/docs/articles/expensify-classic/get-paid-back/Mileage.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Mileage -description: Mileage ---- -## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md index 3ee1c8656b4b..ac0a90ba6d37 100644 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md @@ -1,5 +1,568 @@ --- -title: Coming Soon -description: Coming Soon +title: Sage Intacct +description: Connect your Expensify workspace with Sage Intacct --- -## Resource Coming Soon! +# Overview +Expensify’s seamless integration with Sage Intacct allows you to connect using either Role-based permissions or User-based permissions. + +Once connected to Intacct you’re able to automate report exports, customize your coding preferences, and utilize Sage Intacct’s advanced features. When you’ve configured these settings in Expensify correctly, you can use the integration's settings to automate many tasks, streamlining your workflow for increased efficiency. + +# How to connect to Sage Intacct +We support setting up Sage Intacct with both User-based permissions and Role-based permissions for Expense Reports and Vendor Bills. +- User-based Permissions - Expense Reports +- User-based Permissions - Vendor Bills +- Role-based Permissions - Expense Reports +- Role-based Permissions - Vendor Bills + + +## User-based Permissions - Expense Reports + +Please follow these steps if exporting as Expense Reports with **user-based permissions**. + + +### Checklist of items to complete: +1. Create a web services user and set up permissions. +2. Enable the Time & Expenses module **(Required if exporting as Expense Reports)**. +3. Set up Employees in Sage Intacct **(Required if exporting as Expense Reports)**. +4. Set up Expense Types in Sage Intacct **(Required if exporting as Expense Reports)**. +5. Enable Customization Services (only applicable if you don't already use Platform Services). +6. Create a test workspace and download the [Expensify Package](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fwww.expensify.com%2Ftools%2Fintegrations%2FdownloadPackage). +7. Upload the Package in Sage Intacct. +8. Add web services authorization. +9. Enter credentials and connect Expensify and Sage Intacct. +10. Configure integration sync options. +11. Export a test report. +12. Connect Sage Intacct to the production workspace. + + +### Step 1: Create a web services user 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, see the steps below on creating a web services user for role-based permissions._ +To connect to Sage Intacct, you'll need to create a special web services user. This user is essential for tracking actions in Sage Intacct, such as exporting expense reports and credit card charges from Expensify. It also helps ensure smooth operations when new members join or leave your accounting team. The good news is that setting up this web services user won't cost you anything. Just follow these steps: +Go to **Company > Web Services Users > New** +Setup the user using these configurations: + - **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" +Once you've created the user, you'll need to set the correct permissions. To set those, go to the **subscription** link for this user in the user list, **click on the checkbox** next to the Application/Module and then click on the **Permissions** link to modify those. + +These are the permissions required for a user to export reimbursable expenses as Expense Reports: +- **Administration (All)** +- **Company (Read-only)** +- **Cash Management (All)** +- **General Ledger (All)** +- **Time & Expense (All)** +- **Projects (Read-only)** (only needed if using Projects and Customers) +- **Accounts Payable (All)** (only needed for exporting non-reimbursable expenses as vendor bills) + +**Note:** you can set permissions for each Application/Module by selecting the radio button next to the desired Permission and clicking **Save**. + + +### Step 2: Enable the Time & Expenses Module (Only required if exporting reimbursable expenses as Expense Reports) +The Time & Expenses (T&E) module is often included in your Sage Intacct instance, but if it wasn't part of your initial Sage Intacct setup, you may need to enable it. **Enabling the T&E module is a paid subscription through Sage Intacct. For information on the costs of enabling this module, please contact your Sage Intacct account manager**. It's necessary for our integration and only takes a few minutes to configure. +1. In Sage Intacct, go to the **Company menu > Subscriptions > Time & Expenses** and toggle the switch to subscribe. +2. After enabling T&E, configure it as follows: + - Ensure that **Expense types** is checked: + - 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**: + - **Sequence ID:** EXP + - **Print Title:** EXPENSE REPORT + - **Starting Number:** 1 + - **Next Number:** 2 +3. Select **Advanced Settings** and configure the following: +- **Fixed Number Length:** 4 +- **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 configurations. + + +### Step 3: Set up Employees in Sage Intacct (Only required if exporting reimbursable expenses as Expense Reports) +To set up Employees in Sage Intacct, follow these steps: +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** + - In the **Primary contact name** field, click the dropdown arrow. + - Select the employee if they've already been created. + - Otherwise, click **+ Add** to create a new employee. + - Fill in their **Primary Email Address** along with any other required information. + + +### Step 4: Set up Expense Types in Sage Intacct (Only required if exporting reimbursable expenses as Expense Reports) + +Expense Types provide a user-friendly way to display the names of your expense accounts to your employees. They are essential for our integration. To set up Expense Types, follow these steps: +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 > click 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) +This step is necessary if you are exporting reimbursable expenses as Expense Reports. + + +### Step 5: Enable Customization Services +To enable Customization Services go to **Company > Subscriptions > Customization Services**. + - If you already have Platform Services enabled, you can skip this step. + + +### Step 6: Create a Test Workspace in Expensify and Download the [Expensify Package](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fwww.expensify.com%2Ftools%2Fintegrations%2FdownloadPackage) +Creating a test workspace in Expensify allows you to have a sandbox environment for testing before implementing the integration live. If you are already using Expensify, creating a test workspace ensures that your existing group workspace rules and approval workflows remain intact. Here's how to set it up: +1. Go to **expensify.com > Settings > Workspaces > New Workspace**. +2. Name the workspace something like "Sage Intacct Test Workspace." +3. Go to **Connections > Sage Intacct > Connect to Sage Intacct**. +4. 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**. +2. Click on **Choose File** and select the Package file from your downloads folder. +3. Click **Import**. + + +If you use **Platform Services**: +1. Go to **Platform Services > Custom Packages > New Package**. +2. Click on **Choose File** and select the Package file from your downloads folder. +3. Click **Import**. + + +### Step 8: Add Web Services Authorization +1. Go to **Company > Company Info > Security** in 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. Go back to **Settings > Workspaces > Group > [Workspace Name] > Connections > Configure**. +2. Click **Connect to Sage Intacct** and enter the credentials you've set for your web services user. +3. Click **Send** once you're done. + +Next, you’ll configure the Export, Coding, and Advanced tabs of the connection configuration in Expensify. + + +## User-based Permissions - Vendor Bills +In this setup guide, we'll take you through the steps to establish your connection for Vendor Bills with user-based permissions. Please follow this checklist of items to complete: +1. Create a web services user and set up permissions. +2. Enable Customization Services (only required if you don't already use Platform Services). +3. Create a test workspace in Expensify and download the [Expensify Package](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fwww.expensify.com%2Ftools%2Fintegrations%2FdownloadPackage) +4. Upload the Package in Sage Intacct. +5. Add web services authorization. +6. Enter credentials and connect Expensify and Sage Intacct. +7. Configure integration sync options. + + +### Step 1: Create a web services user 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, see the steps below on creating a web services user for role-based permissions. +To connect to Sage Intacct, it's necessary to set up a web services user. This user simplifies tracking activity within Sage Intacct, such as exporting expense reports and credit card charges from Expensify. It also ensures a seamless transition when someone joins or leaves your accounting department. Setting up the web services user is free of charge. Please follow these steps: +1. Go to **Company > Web Services Users > New**. +2. Configure the user as shown in the screenshot below, making sure to follow these guidelines: + - **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" + + +Once you've created the user, you'll need to set the correct permissions. To do this, follow these steps: +1. Go to the subscription link for this user in the user list. +2. Click on the checkbox next to the Application/Module you want to modify permissions for. +3. Click on the **Permissions** link to make modifications. + +These are the permissions the user needs to have if exporting reimbursable expenses as Vendor Bills: +- **Administration (All)** +- **Company (Read-only)** +- **Cash Management (All)** +- **General Ledger (All)** +- **Accounts Payable (All)** +- **Projects (Read-only)** (required if you're going to be using Projects and Customers) + +**Note:** that selecting the radio button next to the Permission you want and clicking **Save** will set the permission for that particular Application/Module. + + +### Step 2: Enable Customization Services (only applicable if you don't already use Platform Services) +To enable Customization Services go to **Company > Subscriptions > Customization Services**. + - If you already have Platform Services enabled, you can skip this step. + +### Step 3: Create a Test Workspace in Expensify and Download [Expensify Package](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fwww.expensify.com%2Ftools%2Fintegrations%2FdownloadPackage) +Creating a test workspace in Expensify allows you to establish a sandbox environment for testing before implementing the integration in a live environment. If you're already using Expensify, creating a test workspace ensures that your existing company workspace rules and approval workflows remain intact. Here's how to set it up: +1. Go to **expensify.com > Settings > Workspaces > Groups > New Workspace**. +2. Name the workspace something like "Sage Intacct Test Workspace." +3. Go to **Connections > Sage Intacct > Connect to Sage Intacct**. +4. Select "I've completed these" if you've downloaded the [Expensify Package](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fwww.expensify.com%2Ftools%2Fintegrations%2FdownloadPackage) and completed the previous steps in Sage Intacct. +5. Select **Download Package** (You only need to download the file; we'll upload it from your Downloads folder later). + +### Step 4: Upload the Package in Sage Intacct +If you use **Customization Services**: + +1. Go to **Customization Services > Custom Packages > New Package**. +2. Click on **Choose File** and select the Package file from your downloads folder. +3. Click **Import**. + + +If you use **Platform Services**: + +1. Go to **Platform Services > Custom Packages > New Package**. +2. Click on **Choose File** and select the Package file from your downloads folder. +3. Click **Import**. + +### Step 5: Add Web Services Authorization +1. Go to **Company > Company Info > Security** in Intacct and click **Edit**. +2. Scroll down to **Web Services Authorizations** and add "expensify" (all lowercase) as a Sender ID. + +### Step 6: Enter Credentials and Connect Expensify with Sage Intacct +1. Go back to **Settings > Workspaces > Groups > [Workspace Name] > Connections > Configure**. +2. Click on **Connect to Sage Intacct**. +3. Enter the credentials that you've previously set for your web services user. +4. Click **Send** once you've finished entering the credentials. + +Next, you’ll configure the Export, Coding, and Advanced tabs of the connection configuration in Expensify. + + + +## Role-based Permissions - Expense Reports + +For this setup guide, we're going to walk you through how to get your connection up and running as Expense Reports with role-based permissions. + +### Checklist of items to complete: + +1. Create web services user and set up permissions +2. Enable Time & Expenses module +3. Set up Employees in Sage Intacct +4. Set up Expense Types in Sage Intacct +5. Enable Customization Services (only applicable if you don't already use Platform Services) +6. Create a test workspace and download the [Expensify Package](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fwww.expensify.com%2Ftools%2Fintegrations%2FdownloadPackage) +7. Upload the Package in Sage Intacct +8. Add web services authorization +9. Enter credentials and connect Expensify and Sage Intacct +10. Configure integration sync options + + +### Step 1: Create a web services user with role-based permissions + +In Sage Intacct, click **Company**, then click on the **+** button next to **Roles**. + +Name the role, then click **Save**. + +Go to **Roles > Subscriptions** for the "Expensify" role you just created. + +Set the permissions for this role by clicking the checkbox and then clicking on the **Permissions** hyperlink. + +These are the permissions required for a user to export reimbursable expenses as Expense Reports: +- **Administration (All)** +- **Company (Read-only)** +- **Cash Management (All)** +- **General Ledger (All)** +- **Time & Expense (All)** +- **Projects (Read-only)** (only needed if using Projects and Customers) +- **Accounts Payable (All)** (only needed for exporting non-reimbursable expenses as vendor bills) + +Now, you'll need to create a web services user and assign this role to that user. + +- **Company > Web Services Users > New** +- Set up the user like the screenshot below, making sure to do the following: + - 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" + +To assign the role, go to **Roles Information**: + +- Click the **+** button, then find the "Expensify" role and click **Save**. + +### Step 2: Enable the Time & Expenses module (Only required if exporting reimbursable expenses as Expense Reports) + +The T&E module often comes standard on your Sage Intacct instance, but you may need to enable it if it was not a part of your initial Sage Intacct implementation. Enabling the T&E module is a paid subscription through Sage Intacct. Please reach out to your Sage Intacct account manager with any questions on the costs of enabling this module. It's required for our integration and takes just a few minutes to configure. + +In Sage Intacct, click on the **Company** menu > **Subscriptions** > **Time & Expenses** and click the toggle to subscribe. + +Once you've enabled T&E, you'll need to configure it properly: +- Ensure that **Expense types** is checked. +- Under Auto-numbering sequences, please set the following: + - To create the EXP sequence, click on the down arrow on the expense report line > **Add** + - Sequence ID: EXP + - Print Title: EXPENSE REPORT + - Starting Number: 1 + - Next Number: 2 + - Once you've done this, select **Advanced Settings** + - Fixed Number Length: 4 + - Fixed Prefix: EXP + - Once you've done this, hit **Save** +- Under Expense Report approval settings, make sure the "Enable expense report approval" is unchecked. +- Click **Save**! + +### Step 3: Set up Employees in Sage Intacct (Only required if exporting reimbursable expenses as Expense Reports) + +In order to set up Employees, go to **Time & Expenses** > click the plus button next to **Employees**. If you don't see Time & Expense in the top ribbon, you may need to adjust your settings by going to **Company > Roles > Time & Expenses > Enable all permissions**. To create an employee, you'll need to insert the following information: +- Employee ID +- Primary contact name +- Email address (click the dropdown arrow in the Primary contact name field) > select the employee if they've already been created. Otherwise click **+ Add** > fill in their Primary Email Address along with any other information you require. + +### Step 4: Set up Expense Types in Sage Intacct (only required if exporting reimbursable expenses as Expense Reports) + +Expense Types are a user-friendly way of displaying the names of your expense accounts to your employees. They are required for our integration. In order to set up Expense Types, you'll first need to setup your Chart of Accounts (these can be set up in bulk by going to **Company > Open Setup > Company Setup Checklist > click Import**). + +Once you've setup your Chart of Accounts, to set Expense Types, go to **Time & Expense** > **Open Setup** > click the plus button next to **Expense Types**. For each Expense Type, you'll need to include the following information: +- Expense Type +- Description +- Account Number (from your GL) + +### Step 5: Enable Customization Services + +To enable, go **Company > Subscriptions > Customization Services** (if you already have Platform Services enabled, you will skip this step). + +### Step 6: Create a test workspace in Expensify and download [Expensify Package](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fwww.expensify.com%2Ftools%2Fintegrations%2FdownloadPackage) + +The test workspace will be used as a sandbox environment where we can test before going live with the integration. If you're already using Expensify, creating a test workspace will ensure that your existing group workspace rules, approval workflow, etc remain intact. In order to set this up: + +- Go to **expensify.com > Settings > Workspaces > New Workspace** +- Name the workspace something like "Sage Intacct Test Workspace" +- Go to **Connections > Sage Intacct > Connect to Sage Intacct** +- Select **Download Package** (All you need to do is download the file. We'll upload it from your Downloads folder later). + +### Step 7: Upload Package in Sage Intacct + +If you use Customization Services: + +- **Customization Services > Custom Packages > New Package > Choose File >** select the Package file from your downloads folder > Import + +If you use Platform Services: + +- **Platform Services > Custom Packages > New Package > Choose File >** select the Package file from your downloads folder > Import + +### Step 8: Add web services authorization + +- Go to **Company > Company Info > Security** in Intacct and click Edit. Next, scroll down to Web Services authorizations and add "expensify" (this must be all lower case) as a Sender ID. + +### Step 9: Enter credentials and connect Expensify and Sage Intacct + +- Now, go back to **Settings > Workspaces > Group > [Workspace Name] > Connections > Configure > Connect to Sage Intacct** and enter the credentials that you've set for your web services user. Click Send once you're done. + +Next, follow the links in the related articles section below to complete the configuration for the Export, Coding, and Advanced tabs of the connection settings. + +## Role-based Permissions - Vendor Bills + +Follow the steps below to set up Sage Intacct with role-based permissions and export Vendor Bills: + +### Checklist of items to complete: + +1. Create a web services user and configure permissions. +2. Enable Customization Services (if not using Platform Services). +3. Create a test workspace in Expensify and download the [Expensify Package](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fwww.expensify.com%2Ftools%2Fintegrations%2FdownloadPackage). +4. Upload the Package in Sage Intacct. +5. Add web services authorization. +6. Enter credentials and connect Expensify and Sage Intacct. +7. Configure integration sync options. + + +### Step 1: Create a web services user with role-based permissions + +In Sage Intacct: +- Navigate to "Company" and click the **+** button next to "Roles." +- Name the role and click **Save**. +- Go to "Roles" > "Subscriptions" for the "Expensify" role you created. +- Set the permissions for this role by clicking the checkbox and then clicking on the Permissions hyperlink + + +These are the permissions required for a user to export reimbursable expenses as Vendor Bills: +- **Administration (All)** +- **Company (Read-only)** +- **Cash Management (All)** +- **General Ledger (All)** +- **Time & Expense (All)** +- **Projects (Read-only)** (only needed if using Projects and Customers) +- **Accounts Payable (All)** (only needed for exporting non-reimbursable expenses as vendor bills) + + +- Create a web services user: + - Go to **Company > Web Services Users > New** + - Configure the user as follows: + - 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" + - To assign the role, go to "Roles Information", click the **+** button, find the "Expensify" role, and click **Save** + +### Step 2: Enable Customization Services + +Only required if you don't already use Platform Services: +- To enable, go to **Company > Subscriptions > Customization Services** + +### Step 3: Create a test workspace in Expensify and download the [Expensify Package](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fwww.expensify.com%2Ftools%2Fintegrations%2FdownloadPackage) + +Create a test workspace in Expensify: +- Go to **Settings > Workspaces** and click **New Workspace** on the Expensify website. +- Name the workspace something like "Sage Intacct Test Workspace." +- Once created, navigate to **Settings > Workspaces > [Group Workspace Name] > Connections > Accounting Integrations > Connect to Sage Intacct** +- Select **Create a new Sage Intacct connection/Connect to Sage Intacct** +- Select **Download Package** (We'll upload it from your Downloads folder later.) + +### Step 4: Upload Package in Sage Intacct + +If you use **Customization Services**: +- Go to **Customization Services > Custom Packages > New Package > Choose File > select the Package file from your downloads folder > Import**. + +If you use **Platform Services**: +- Go to **Platform Services > Custom Packages > New Package > Choose File > select the Package file from your downloads folder > Import**. + +### Step 5: Add web services authorization + +- Go to **Company > Company Info > Security** in Intacct and click **Edit** +- Scroll down to **Web Services Authorizations** and add **expensify** (all lowercase) as a Sender ID. + +### Step 6: Enter credentials and connect Expensify and Sage Intacct + +Now, go back to **Settings > Workspaces > [Group Workspace Name] > Connections > Accounting Integrations > Configure > Connect to Sage Intacct** and enter the credentials you set for your web services user. Click **Send** when finished. + +### Step 7: Configure your connection + +Once the initial sync completes, you may receive the error "No Expense Types Found" if you're not using the Time and Expenses module. Close the error dialogue, and your configuration options will appear. Switch the reimbursable export option to **Vendor Bills** and click **Save** before completing your configuration. + +Next, refer to the related articles section below to finish configuring the Export, Coding, and Advanced tabs of the connection configuration. + +# How to configure export settings + +When you connect Intacct with Expensify, you can configure how information appears once exported. To do this, Admins who are connected to Intacct can go to **Settings > Workspaces > Group > [Workspace Name] > Connections**, and then click on **Configure** under Intacct. This is where you can set things up the way you want. + + +## Preferred Exporter + +Any workspace admin can export to Sage Intacct, but only the preferred exporter will see reports that are ready for export in their Inbox. + + + +## Date + +Choose which date you would like your Expense Reports or Vendor Bills to use when exported. + +- **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. + +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 + +**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 Domain Control, you can export expenses from each individual card to a specific account in Intacct. +Please note, Credit Card Transactions cannot be exported to Sage Intacct at the top-level if you have **Multi-Currency** enabled, so you will need to select an entity in the configuration of your Expensify Workspace by going to **Settings > Workspaces > Groups > [Workspace Name] > Connections > Configure**. + +## 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. + +## 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. +Having this option enabled will then 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. + + +# FAQ +## 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. diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Additional-Travel-Integrations.md b/docs/articles/expensify-classic/integrations/travel-integrations/Additional-Travel-Integrations.md new file mode 100644 index 000000000000..ac37a01b3e6b --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Additional-Travel-Integrations.md @@ -0,0 +1,71 @@ +--- +title: Importing Receipts from Various Platforms to Expensify +description: Detailed guide on how to import receipts from multiple travel platforms into Expensify. +--- + +# Overview +You can automatically import receipts from many travel platforms into Expensify, to make tracking expenses while traveling for business a breeze. Read on to learn how to import receipts from Bolt Work, Spot Hero, Trainline, Grab, HotelTonight, and Kayak for Business. + +## How to Connect to Bolt Work + +### Set Up Bolt Work Profile +- Open the Bolt app, go to the side navigation menu, and select Payment. +- At the bottom, select Set up work profile and follow the instructions, entering your work email for verification. + +### Link to Expensify +- In the Bolt app, go to Work Rides. +- Select Add expense provider, choose Expensify, and enter the associated email to receive a verification link. +- Ensure you select your work ride profile as the payment method before booking. + +## How to Connect to SpotHero + +### Set up a Business Profile +- Open the SpotHero app, click the hamburger icon, and go to Account Settings. +- Click Set up Business Profile. +- Specify the email connected to Expensify and set up your payment method. +- Upon checkout, choose between Business and Personal Profiles in the "Payment Details" section. +- If you want, you can set a weekly or monthly cadence for consolidated SpotHero expense reports in your Business Profile settings. This will batch all of your SpotHero expenses to import into Expensify at that cadence. + +## How to Connect to Trainline +- To send a ticket receipt to Expensify: + - In the Trainline app, navigate to the My Tickets tab. + - Tap Manage my booking > Expense receipt > Send to Expensify. +- That’s it! + +## How to Connect to Grab +- In the Grab app, tap on your name, go to “Profiles”, and “Add a business profile”. +- Follow instructions and enter your work email for verification. +- In your profile, tap on Business > Expense Solution > Expensify > Save. +- Before booking, select your Business profile and confirm. + +## How to Connect to HotelTonight +- In HotelTonight, go to the Bookings tab and select your booking. +- Select Receipt > Expensify, enter your Expensify email, and send. + +## How to Connect to Kayak for Business + +### Admin Setup +- Admins should go to “Company Settings” and click on “Connect to Expensify”. +- Bookings made by employees will automatically be sent to Expensify. + +### Traveler Setup +- From your account settings, choose whether expenses should be sent to Expensify automatically or manually. +- We recommend sending them automatically, so you can travel without even thinking about your expense reports. + +# FAQ + +**Q: What if I don’t have the option for Send to Expensify in Trainline?** + +A: This can happen if the native iOS Mail app is not installed on an Apple device. However, you can still use the native iOS share to Expensify function for Trainline receipts. + +**Q: Why should I choose automatic mode in Kayak for Business?** + +A: Automatic mode is less effort as it’s easier to delete an expense in Expensify than to remember to forward a forgotten receipt. + +**Q: Can I receive consolidated reports from SpotHero?** + +A: Yes, you can set a weekly or monthly cadence for SpotHero expenses to be emailed in a consolidated report. + +**Q: Do I need to select a specific profile before booking in Bolt Work and Grab?** + +A: Yes, ensure you have selected your work or business profile as the payment method before booking. diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Removing-Members.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Removing-Members.md new file mode 100644 index 000000000000..76ebba9ef76b --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Removing-Members.md @@ -0,0 +1,34 @@ +--- +title: Remove a Workspace Member +description: How to remove a member from a Workspace in Expensify +--- + +Removing a member from a workspace disables their ability to use the workspace. Please note that it does not delete their account or deactivate the Expensify Card. + +## How to Remove a Workspace Member +1. Important: Make sure the employee has submitted all Draft reports and the reports have been approved, reimbursed, etc. +2. Go to Settings > Workspaces > Group > [Workspace Name] > Members > Workspace Members +3. Select the member you'd like to remove and click the **Remove** button at the top of the Members table. +4. If this member was an approver, make sure that reports are not routing to them in the workflow. + +# FAQ + +## Will reports from this member on this workspace still be available? +Yes, as long as the reports have been submitted. You can navigate to the Reports page and enter the member's email in the search field to find them. However, Draft reports will be removed from the workspace, so these will no longer be visible to the Workspace Admin. + +## Can members still access their reports on a workspace after they have been removed? +Yes. Any report that has been approved will now show the workspace as “(not shared)” in their account. If it is a Draft Report they will still be able to edit it and add it to a new workspace. If the report is Approved or Reimbursed they will not be able to edit it further. + +## Who can remove members from a workspace? +Only Workspace Admins. It is not possible for a member to add or remove themselves from a workspace. It is not possible for a Domain Admin who is not also a Workspace Admin to remove a member from a workspace. + +## How do I remove a member from a workspace if I am seeing an error message? +If a member is a **preferred exporter, billing owner, report approver** or has **processing reports**, to remove them the workspace you will first need to: + +* **Preferred Exporter** - go to Settings > Workspaces > Group > [Workspace Name] > Connections > Configure and select a different Workspace Admin in the dropdown for **Preferred Exporter**. +* **Billing Owner** - take over billing on the Settings > Workspaces > Group > [Workspace Name] > Overview page. +* **Processing reports** - approve or reject the member’s reports on your Reports page. +* **Approval Workflow** - remove them as a workflow approver on your Settings > Workspaces > Group > [Workspace Name] > Members > Approval Mode > page by changing the "**Submit reports to**" field. + +## How do I remove a user completely from a company account? +If you have a Control Workspace and have Domain Control enabled, you will need to remove them from the domain to delete members' accounts entirely and deactivate the Expensify Card. diff --git a/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md b/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md index f61f26d91059..14ade143a35b 100644 --- a/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md +++ b/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md @@ -1,8 +1,63 @@ --- title: Third Party Payments -description: Third Party Payments +description: A help article that covers Third Party Payment options including PayPal, Venmo, Wise, and Paylocity. --- -## Resource Coming Soon! +# Expensify Third Party Payment Options +Expensify offers convenient third party payment options that allow you to streamline the process of reimbursing expenses and managing your finances. With these options, you can pay your expenses and get reimbursed faster and more efficiently. In this guide, we'll walk you through the steps to set up and use Expensify's third party payment options. - \ No newline at end of file +## Overview + +Expensify offers integration with various third party payment providers, making it easy to reimburse employees and manage your expenses seamlessly. Some of the key benefits of using third-party payment options in Expensify include: + +- Faster Reimbursements: Expedite the reimbursement process and reduce the time it takes for employees to receive their funds. +- Secure Transactions: Benefit from the security features and protocols provided by trusted payment providers. +- Centralized Expense Management: Consolidate all your expenses and payments within Expensify for a more efficient financial workflow. + +## Setting Up Third Party Payments + +To get started with third party payments in Expensify, follow these steps: + +1. **Log in to Expensify**: Access your Expensify account using your credentials. + +2. **Navigate to Settings**: Click on the "Settings" option in the top-right corner of the Expensify dashboard. + +3. **Select Payments**: In the Settings menu, find and click on the "Payments" or "Payment Methods" section. + +4. **Choose Third Party Payment Provider**: Select your preferred third party payment provider from the available options. Expensify may support providers such as PayPal, Venmo, Wise, and Paylocity. + +5. **Link Your Account**: Follow the prompts to link your third party payment account with Expensify. You may need to enter your account details and grant necessary permissions. + +6. **Verify Your Account**: Confirm your linked account to ensure it's correctly integrated with Expensify. + +## Using Third Party Payments + +Once you've set up your third party payment option, you can start using it to reimburse expenses and manage payments: + +1. **Create an Expense Report**: Begin by creating an expense report in Expensify, adding all relevant expenses. + +2. **Submit for Approval**: After reviewing and verifying the expenses, submit the report for approval within Expensify. + +3. **Approval and Reimbursement**: Once the report is approved, the approved expenses can be reimbursed directly through your chosen third party payment provider. Expensify will automatically initiate the payment process. + +4. **Track Payment Status**: You can track the status of payments and view transaction details within your Expensify account. + +## FAQ’s + +### Q: Are there any fees associated with using third party payment options in Expensify? + +A: The fees associated with third party payments may vary depending on the payment provider you choose. Be sure to review the terms and conditions of your chosen provider for details on any applicable fees. + +### Q: Can I use multiple third party payment providers with Expensify? + +A: Expensify allows you to link multiple payment providers if needed. You can select the most suitable payment method for each expense report. + +### Q: Is there a limit on the amount I can reimburse using third party payments? + +A: The reimbursement limit may depend on the policies and settings configured within your Expensify account and the limits imposed by your chosen payment provider. + +With Expensify's third party payment options, you can simplify your expense management and reimbursement processes. By following the steps outlined in this guide, you can set up and use third party payments efficiently. + + + + diff --git a/docs/articles/new-expensify/integrations/accounting-integrations/Xero b/docs/articles/new-expensify/integrations/accounting-integrations/Xero.md similarity index 100% rename from docs/articles/new-expensify/integrations/accounting-integrations/Xero rename to docs/articles/new-expensify/integrations/accounting-integrations/Xero.md diff --git a/docs/assets/Files/Hosting b/docs/assets/Files/Hosting new file mode 100644 index 000000000000..ad2a361edc03 --- /dev/null +++ b/docs/assets/Files/Hosting @@ -0,0 +1 @@ +Holding tank for help.expensify.com support files diff --git a/docs/assets/images/Auto-Reconciliation_Image2.png b/docs/assets/images/Auto-Reconciliation_Image2.png new file mode 100644 index 000000000000..5c7ed2d6b7ea Binary files /dev/null and b/docs/assets/images/Auto-Reconciliation_Image2.png differ diff --git a/docs/assets/images/Auto-Reconciliaton_Image1.png b/docs/assets/images/Auto-Reconciliaton_Image1.png new file mode 100644 index 000000000000..9fcb7e316aaf Binary files /dev/null and b/docs/assets/images/Auto-Reconciliaton_Image1.png differ diff --git a/docs/assets/images/ExpensifyHelp_AssignCardBtn.png b/docs/assets/images/ExpensifyHelp_AssignCardBtn.png new file mode 100644 index 000000000000..d9c6e919abcd Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_AssignCardBtn.png differ diff --git a/docs/assets/images/ExpensifyHelp_AssignCardForm.png b/docs/assets/images/ExpensifyHelp_AssignCardForm.png new file mode 100644 index 000000000000..f8c3ab5a6346 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_AssignCardForm.png differ diff --git a/docs/assets/images/ExpensifyHelp_AssignedCard.png b/docs/assets/images/ExpensifyHelp_AssignedCard.png new file mode 100644 index 000000000000..26f96ca07748 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_AssignedCard.png differ diff --git a/docs/assets/images/ExpensifyHelp_DomainCards.png b/docs/assets/images/ExpensifyHelp_DomainCards.png new file mode 100644 index 000000000000..f4c4cb0688d5 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_DomainCards.png differ diff --git a/docs/assets/images/ExpensifyHelp_DomainCardsList.png b/docs/assets/images/ExpensifyHelp_DomainCardsList.png new file mode 100644 index 000000000000..e1336bc20416 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_DomainCardsList.png differ diff --git a/docs/assets/images/ExpensifyHelp_ExpenseRules_01.png b/docs/assets/images/ExpensifyHelp_ExpenseRules_01.png new file mode 100644 index 000000000000..7a6c3c1b3a13 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_ExpenseRules_01.png differ diff --git a/docs/assets/images/ExpensifyHelp_ExpenseRules_02.png b/docs/assets/images/ExpensifyHelp_ExpenseRules_02.png new file mode 100644 index 000000000000..28c6a7689b77 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_ExpenseRules_02.png differ diff --git a/docs/assets/images/ExpensifyHelp_ExpenseRules_03.png b/docs/assets/images/ExpensifyHelp_ExpenseRules_03.png new file mode 100644 index 000000000000..90c9855c0c49 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_ExpenseRules_03.png differ diff --git a/docs/assets/images/ExpensifyHelp_UnassignCard-1.png b/docs/assets/images/ExpensifyHelp_UnassignCard-1.png new file mode 100644 index 000000000000..5fa344fed9cb Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_UnassignCard-1.png differ diff --git a/docs/assets/images/ExpensifyHelp_UnassignCard.png b/docs/assets/images/ExpensifyHelp_UnassignCard.png new file mode 100644 index 000000000000..add6a4e3a057 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_UnassignCard.png differ diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index e797b5055dee..f022cf3452a8 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.81 + 1.3.85 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.81.7 + 1.3.85.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index dce89fb2b19a..db42a9dc6d55 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.81 + 1.3.85 CFBundleSignature ???? CFBundleVersion - 1.3.81.7 + 1.3.85.0 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 54d0525fd3c9..cb120bca2b88 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -251,9 +251,9 @@ PODS: - nanopb/encode (= 2.30908.0) - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - - Onfido (27.4.0) - - onfido-react-native-sdk (7.4.0): - - Onfido (= 27.4.0) + - Onfido (28.3.0) + - onfido-react-native-sdk (8.3.0): + - Onfido (~> 28.3.0) - React - OpenSSL-Universal (1.1.1100) - Plaid (4.1.0) @@ -819,7 +819,7 @@ PODS: - SDWebImage/Core (~> 5.10) - SocketRocket (0.6.1) - Turf (2.6.1) - - VisionCamera (2.15.4): + - VisionCamera (2.16.2): - React - React-callinvoker - React-Core @@ -1204,8 +1204,8 @@ SPEC CHECKSUMS: MapboxMaps: af50ec61a7eb3b032c3f7962c6bd671d93d2a209 MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 - Onfido: e36f284b865adcf99d9c905590a64ac09d4a576b - onfido-react-native-sdk: 4ecde1a97435dcff9f00a878e3f8d1eb14fabbdc + Onfido: c7d010d9793790d44a07799d9be25aa8e3814ee7 + onfido-react-native-sdk: b346a620af5669f9fecb6dc3052314a35a94ad9f OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c Plaid: 7d340abeadb46c7aa1a91f896c5b22395a31fcf2 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef @@ -1287,7 +1287,7 @@ SPEC CHECKSUMS: SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 469ce2c3d22e5e8e4818d5a3b254699a5c89efa4 - VisionCamera: d3ec8883417a6a4a0e3a6ba37d81d22db7611601 + VisionCamera: 95f969b8950b411285579d633a1014782fe0e634 Yoga: 3efc43e0d48686ce2e8c60f99d4e6bd349aff981 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a diff --git a/package-lock.json b/package-lock.json index f92b6853a718..922c2a158654 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.81-7", + "version": "1.3.85-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.81-7", + "version": "1.3.85-0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -22,7 +22,7 @@ "@kie/act-js": "^2.0.1", "@kie/mock-github": "^1.0.0", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", - "@onfido/react-native-sdk": "7.4.0", + "@onfido/react-native-sdk": "8.3.0", "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-clipboard/clipboard": "^1.12.1", @@ -43,7 +43,6 @@ "@types/node": "^18.14.0", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", - "babel-plugin-transform-remove-console": "^6.9.4", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", "core-js": "^3.32.0", @@ -51,7 +50,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#009c2ab79bf7ddeab0eea7a3a4c0d9cc4277c34b", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#bdbdf44825658500ba581d3e86237d7b8996cc2e", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -94,7 +93,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.98", + "react-native-onyx": "1.0.100", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -111,7 +110,7 @@ "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "^3.6.0", - "react-native-vision-camera": "^2.15.4", + "react-native-vision-camera": "^2.16.2", "react-native-web-linear-gradient": "^1.1.2", "react-native-web-lottie": "^1.4.4", "react-native-webview": "^11.17.2", @@ -138,8 +137,6 @@ "@babel/runtime": "^7.20.0", "@electron/notarize": "^1.2.3", "@jest/globals": "^29.5.0", - "@kie/act-js": "^2.0.1", - "@kie/mock-github": "^1.0.0", "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", @@ -222,7 +219,7 @@ "react-native-performance-flipper-reporter": "^2.0.0", "react-native-svg-transformer": "^1.0.0", "react-test-renderer": "18.2.0", - "reassure": "^0.9.0", + "reassure": "^0.10.1", "setimmediate": "^1.0.5", "shellcheck": "^1.1.0", "style-loader": "^2.0.0", @@ -2539,16 +2536,21 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.22.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz", - "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, "node_modules/@babel/template": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", @@ -2623,13 +2625,13 @@ "license": "Apache-2.0" }, "node_modules/@callstack/reassure-cli": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@callstack/reassure-cli/-/reassure-cli-0.9.0.tgz", - "integrity": "sha512-auoxqyilxkT5mDdEPJqRRY+ZGlrihJjFQpopcFd/15ng76OPVka3L48RMEY2wXkFXLaOOs6enNGb596jYPuEtQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@callstack/reassure-cli/-/reassure-cli-0.10.0.tgz", + "integrity": "sha512-CYgOGOAWcFgA2GrJw6RJAvImCpHCpPbtPoYMDol7esjlRCX2QwIKG7/9byq47hML57w94fhFAa76KD84YlgMBg==", "dev": true, "dependencies": { - "@callstack/reassure-compare": "0.5.0", - "@callstack/reassure-logger": "0.3.0", + "@callstack/reassure-compare": "0.6.0", + "@callstack/reassure-logger": "0.3.1", "chalk": "4.1.2", "simple-git": "^3.16.0", "yargs": "^17.6.2" @@ -2759,12 +2761,12 @@ } }, "node_modules/@callstack/reassure-compare": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@callstack/reassure-compare/-/reassure-compare-0.5.0.tgz", - "integrity": "sha512-3sBeJ/+Hxjdb01KVb8LszO1kcJ8TXcrVnerUj+LYn2dkBOohAMqGYaOvCeoWsVEHJ+MIOzmvAGBJQRu69RoJdQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@callstack/reassure-compare/-/reassure-compare-0.6.0.tgz", + "integrity": "sha512-P3nmv36CJrQSXg0+z6EuEV/0xAbvxWbAZ7diQHnkbvqk2z8PKRXpkcthrRUpe02wLewa0MLxBUJwLenFnhxIRg==", "dev": true, "dependencies": { - "@callstack/reassure-logger": "0.3.0", + "@callstack/reassure-logger": "0.3.1", "markdown-builder": "^0.9.0", "markdown-table": "^2.0.0", "zod": "^3.20.2" @@ -2777,9 +2779,9 @@ "dev": true }, "node_modules/@callstack/reassure-logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@callstack/reassure-logger/-/reassure-logger-0.3.0.tgz", - "integrity": "sha512-JX5o+8qkIbIRL+cQn9XlQYdv9p/3L6J70zZX6NYi9j0VrSS9PZIRfo8ujMdLSqUNV6HZN1ay59RzuncLjVu0aQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@callstack/reassure-logger/-/reassure-logger-0.3.1.tgz", + "integrity": "sha512-IUsNrxVMdt0zgD2IN2snGVGUG8Yc6F3SWaPF8RXUn8qi7XZuYC6WevEL+mIKmlbTTa7qlX9brkn0pJoXAjfcPQ==", "dev": true, "dependencies": { "chalk": "4.1.2" @@ -2856,12 +2858,12 @@ } }, "node_modules/@callstack/reassure-measure": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@callstack/reassure-measure/-/reassure-measure-0.5.0.tgz", - "integrity": "sha512-KwlmNYcspBOp7FIw6XOz5O9mnKB4cWCCyM6vG4nFUPHSWQ6yVdRkawVvoPIV5qJ2hw7zCzdtqRrLWQSTF4eKlg==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@callstack/reassure-measure/-/reassure-measure-0.6.0.tgz", + "integrity": "sha512-phXY5DAtKhnu8dA2pmnl+pqFOfrVEFFDJOi4AnObwIcpDSn3IUXgNSe7rSi+JP/mXNWMLoUS8rnH4iIFDyf7qQ==", "dev": true, "dependencies": { - "@callstack/reassure-logger": "0.3.0", + "@callstack/reassure-logger": "0.3.1", "mathjs": "^11.5.0" }, "peerDependencies": { @@ -5460,7 +5462,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@kie/act-js/-/act-js-2.3.0.tgz", "integrity": "sha512-Q9k0b05uA46jXKWmVfoGDW+0xsCcE7QPiHi8IH7h41P36DujHKBj4k28RCeIEx3IwUCxYHKwubN8DH4Vzc9XcA==", - "dev": true, "hasInstallScript": true, "dependencies": { "@kie/mock-github": "^2.0.0", @@ -5480,7 +5481,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@kie/mock-github/-/mock-github-2.0.0.tgz", "integrity": "sha512-od6UyICJYKMnz9HgEWCQAFT/JsCpKkLp+JQH8fV23tf+ZmmQI1dK3C20k6aO5uJhAHA0yOcFtbKFVF4+8i3DTg==", - "dev": true, "dependencies": { "@octokit/openapi-types-ghec": "^18.0.0", "ajv": "^8.11.0", @@ -5495,14 +5495,12 @@ "node_modules/@kie/act-js/node_modules/@octokit/openapi-types-ghec": { "version": "18.1.1", "resolved": "https://registry.npmjs.org/@octokit/openapi-types-ghec/-/openapi-types-ghec-18.1.1.tgz", - "integrity": "sha512-5Ri7FLYX4gJSdG+G0Q8QDca/gOLfkPN4YR2hkbVg6hEL+0N62MIsJPTyNaT9pGEXCLd1KbYV6Lh3T2ggsmyBJw==", - "dev": true + "integrity": "sha512-5Ri7FLYX4gJSdG+G0Q8QDca/gOLfkPN4YR2hkbVg6hEL+0N62MIsJPTyNaT9pGEXCLd1KbYV6Lh3T2ggsmyBJw==" }, "node_modules/@kie/act-js/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", @@ -5516,7 +5514,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, "engines": { "node": ">=6" } @@ -5525,7 +5522,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@kie/mock-github/-/mock-github-1.1.0.tgz", "integrity": "sha512-fD+utlOiyZSOutOcXL0G9jfjbtvOO44PLUyTfgfkrm1+575R/dbvU6AcJfjc1DtHeRv7FC7f4ebyU+a1wgL6CA==", - "dev": true, "dependencies": { "@octokit/openapi-types-ghec": "^14.0.0", "ajv": "^8.11.0", @@ -5541,7 +5537,6 @@ "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", @@ -5555,7 +5550,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, "engines": { "node": ">=6" } @@ -5564,7 +5558,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", - "dev": true, "dependencies": { "debug": "^4.1.1" } @@ -5572,8 +5565,7 @@ "node_modules/@kwsites/promise-deferred": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", - "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", - "dev": true + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", @@ -5952,7 +5944,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -5966,7 +5957,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -5976,7 +5966,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -6107,8 +6096,7 @@ "node_modules/@octokit/openapi-types-ghec": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types-ghec/-/openapi-types-ghec-14.0.0.tgz", - "integrity": "sha512-xhd9oEvn2aroGn+sk09Ptx/76Y7aKU0EIgHukHPCU1+rGJreO36baEEk6k8ZPblieHNM39FcykJQmtDrETm0KA==", - "dev": true + "integrity": "sha512-xhd9oEvn2aroGn+sk09Ptx/76Y7aKU0EIgHukHPCU1+rGJreO36baEEk6k8ZPblieHNM39FcykJQmtDrETm0KA==" }, "node_modules/@octokit/plugin-paginate-rest": { "version": "3.1.0", @@ -6384,10 +6372,9 @@ } }, "node_modules/@onfido/react-native-sdk": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-7.4.0.tgz", - "integrity": "sha512-qeeaXLxVXz+J0lrqMwQGP52fXhCnTmEAC5K8jZe8YTqst2s1FZZGKkd1bxTloHG5hBBTa39SwWVUKmgPUm+Ssw==", - "license": "MIT", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-8.3.0.tgz", + "integrity": "sha512-nnhuvezd35v08WXUTQlX+gr4pbnNnwNV5KscC/jJrfjGikNUJnhnAHYxfnfJccTn44qUC6vRaKWq2GfpMUnqNA==", "peerDependencies": { "react": ">=17.0.0", "react-native": ">=0.68.2 <1.0.x" @@ -21214,7 +21201,6 @@ "version": "0.5.10", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", - "dev": true, "engines": { "node": ">=6.0" } @@ -21845,7 +21831,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, "license": "MIT" }, "node_modules/array-includes": { @@ -23288,7 +23273,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.2.tgz", "integrity": "sha512-jxJ0PbXR8eQyPlExCvCs3JFnikvs1Yp4gUJt6nmgathdOwvur+q22KWC3h20gvWl4T/14DXKj2IlkJwwZkZPOw==", - "dev": true, "dependencies": { "cmd-shim": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", @@ -23303,7 +23287,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -23315,7 +23298,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" @@ -23398,7 +23380,6 @@ }, "node_modules/body-parser": { "version": "1.20.0", - "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -23423,7 +23404,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -23433,7 +23413,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -23443,7 +23422,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -23456,7 +23434,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, "license": "MIT" }, "node_modules/bonjour-service": { @@ -24532,7 +24509,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -24961,7 +24937,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.1.tgz", "integrity": "sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==", - "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -25404,7 +25379,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -25417,7 +25391,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -25436,7 +25409,6 @@ }, "node_modules/content-type": { "version": "1.0.4", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -25451,7 +25423,6 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -25461,7 +25432,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true, "license": "MIT" }, "node_modules/copy-concurrently": { @@ -30219,8 +30189,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#009c2ab79bf7ddeab0eea7a3a4c0d9cc4277c34b", - "integrity": "sha512-mD9p6Qj8FfvLdb6JLXvF0UNqLN6ymssUU67Fm37zmK18hd1Abw+vR/pQkNpHR2iv+KRs8HdcyuZ0vaOec4VrFQ==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#bdbdf44825658500ba581d3e86237d7b8996cc2e", + "integrity": "sha512-/kXD/18YQJY/iWB5MOSN0ixB1mpxUA+NXEYgKjac1tJd+DoY3K6+bDeu++Qfqg26LCNfvjTkjkDGZVdGsJQ7Hw==", "license": "MIT", "dependencies": { "classnames": "2.3.1", @@ -30316,7 +30286,6 @@ }, "node_modules/express": { "version": "4.18.1", - "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -30359,7 +30328,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -30369,14 +30337,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, "license": "MIT" }, "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -30578,7 +30544,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -30663,7 +30628,6 @@ }, "node_modules/fastq": { "version": "1.13.0", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -30897,7 +30861,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -30916,7 +30879,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -30926,7 +30888,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, "license": "MIT" }, "node_modules/find-babel-config": { @@ -31113,7 +31074,6 @@ "version": "1.15.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "dev": true, "funding": [ { "type": "individual", @@ -31374,23 +31334,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.4.tgz", + "integrity": "sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==", "dev": true, "engines": { "node": "*" }, "funding": { "type": "patreon", - "url": "https://www.patreon.com/infusion" + "url": "https://github.com/sponsors/rawify" } }, "node_modules/fragment-cache": { @@ -31451,7 +31410,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -31716,7 +31674,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -33536,7 +33493,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" @@ -33861,7 +33817,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -33937,7 +33892,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -37364,7 +37318,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, "license": "ISC" }, "node_modules/json5": { @@ -38284,20 +38237,20 @@ } }, "node_modules/mathjs": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.8.0.tgz", - "integrity": "sha512-I7r8HCoqUGyEiHQdeOCF2m2k9N+tcOHO3cZQ3tyJkMMBQMFqMR7dMQEboBMJAiFW2Um3PEItGPwcOc4P6KRqwg==", + "version": "11.11.2", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.11.2.tgz", + "integrity": "sha512-SL4/0Fxm9X4sgovUpJTeyVeZ2Ifnk4tzLPTYWDyR3AIx9SabnXYqtCkyJtmoF3vZrDPKGkLvrhbIL4YN2YbXLQ==", "dev": true, "dependencies": { - "@babel/runtime": "^7.21.0", + "@babel/runtime": "^7.23.1", "complex.js": "^2.1.1", "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", - "fraction.js": "^4.2.0", + "fraction.js": "4.3.4", "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", "tiny-emitter": "^2.1.0", - "typed-function": "^4.1.0" + "typed-function": "^4.1.1" }, "bin": { "mathjs": "bin/cli.js" @@ -38880,7 +38833,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -39120,7 +39072,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true, "license": "MIT" }, "node_modules/merge-options": { @@ -39156,7 +39107,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -39166,7 +39116,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -41063,7 +41012,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -41114,7 +41062,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, "license": "MIT", "dependencies": { "minipass": "^3.0.0", @@ -41211,7 +41158,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -41447,7 +41393,6 @@ "version": "13.3.3", "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.3.tgz", "integrity": "sha512-z+KUlILy9SK/RjpeXDiDUEAq4T94ADPHE3qaRkf66mpEhzc/ytOMm3Bwdrbq6k1tMWkbdujiKim3G2tfQARuJw==", - "dev": true, "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", @@ -41677,7 +41622,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", - "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -42889,7 +42833,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true, "license": "MIT" }, "node_modules/path-type": { @@ -43606,7 +43549,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "dev": true, "engines": { "node": ">= 8" } @@ -43634,7 +43576,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -43922,7 +43863,6 @@ "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, "dependencies": { "side-channel": "^1.0.4" }, @@ -43983,7 +43923,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -44065,7 +44004,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -44081,7 +44019,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -44091,7 +44028,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -44692,9 +44628,9 @@ } }, "node_modules/react-native-onyx": { - "version": "1.0.98", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.98.tgz", - "integrity": "sha512-2wJNmZVBJs2Y0p1G/es4tQZnplJR8rOyVbHv9KZaq/SXluLUnIovttf1MMhVXidDLT+gcE+u20Mck/Gpb8bY0w==", + "version": "1.0.100", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.100.tgz", + "integrity": "sha512-m4bOF/uOtYpfL83fqoWhw7TYV4oKGXt0sfGoel/fhaT1HzXKheXc//ibt/G3VrTCf5nmRv7bXgsXkWjUYLH3UQ==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -44984,9 +44920,9 @@ } }, "node_modules/react-native-vision-camera": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-2.15.4.tgz", - "integrity": "sha512-SJXSWH1pu4V3Kj4UuX/vSgOxc9d5wb5+nHqBHd+5iUtVyVLEp0F6Jbbaha7tDoU+kUBwonhlwr2o8oV6NZ7Ibg==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-2.16.2.tgz", + "integrity": "sha512-QIpG33l3QB0AkTfX/ccRknwNRu1APNUkokVKF1lpRO2+tBnkXnGL0UapgXg5u9KIONZtrpupeDeO+J5B2TeQVw==", "peerDependencies": { "react": "*", "react-native": "*" @@ -45943,7 +45879,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", - "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -46190,14 +46125,14 @@ "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" }, "node_modules/reassure": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/reassure/-/reassure-0.9.0.tgz", - "integrity": "sha512-FIf0GPchyPGItsrW5Wwff/NWVrfOcCUuJJSs4Nur6iRdQt8yvmCpcba4UyemdZ1KaFTIW1gKbAV3u2tuA7zmtQ==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/reassure/-/reassure-0.10.1.tgz", + "integrity": "sha512-+GANr5ojh32NZu1YGfa6W8vIJm3iOIZJUvXT5Gc9fQyre7okYsCzyBq9WsHbnAQDjNq1g9SsM/4bwcVET9OIqA==", "dev": true, "dependencies": { - "@callstack/reassure-cli": "0.9.0", + "@callstack/reassure-cli": "0.10.0", "@callstack/reassure-danger": "0.1.1", - "@callstack/reassure-measure": "0.5.0" + "@callstack/reassure-measure": "0.6.0" } }, "node_modules/recast": { @@ -47098,7 +47033,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -47193,7 +47127,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -47928,7 +47861,6 @@ "version": "3.19.0", "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.19.0.tgz", "integrity": "sha512-hyH2p9Ptxjf/xPuL7HfXbpYt9gKhC1yWDh3KYIAYJJePAKV7AEjLN4xhp7lozOdNiaJ9jlVvAbBymVlcS2jRiA==", - "dev": true, "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", @@ -49132,7 +49064,6 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -49471,7 +49402,6 @@ "version": "6.1.15", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", - "dev": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -49488,7 +49418,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -50320,7 +50249,6 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, "license": "MIT", "dependencies": { "media-typer": "0.3.0", @@ -50392,9 +50320,9 @@ } }, "node_modules/typed-function": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz", - "integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.1.tgz", + "integrity": "sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ==", "dev": true, "engines": { "node": ">= 14" @@ -53267,9 +53195,9 @@ } }, "node_modules/zod": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", - "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "dev": true, "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -54804,11 +54732,18 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "@babel/runtime": { - "version": "7.22.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz", - "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + } } }, "@babel/template": { @@ -54872,13 +54807,13 @@ "dev": true }, "@callstack/reassure-cli": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@callstack/reassure-cli/-/reassure-cli-0.9.0.tgz", - "integrity": "sha512-auoxqyilxkT5mDdEPJqRRY+ZGlrihJjFQpopcFd/15ng76OPVka3L48RMEY2wXkFXLaOOs6enNGb596jYPuEtQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@callstack/reassure-cli/-/reassure-cli-0.10.0.tgz", + "integrity": "sha512-CYgOGOAWcFgA2GrJw6RJAvImCpHCpPbtPoYMDol7esjlRCX2QwIKG7/9byq47hML57w94fhFAa76KD84YlgMBg==", "dev": true, "requires": { - "@callstack/reassure-compare": "0.5.0", - "@callstack/reassure-logger": "0.3.0", + "@callstack/reassure-compare": "0.6.0", + "@callstack/reassure-logger": "0.3.1", "chalk": "4.1.2", "simple-git": "^3.16.0", "yargs": "^17.6.2" @@ -54974,12 +54909,12 @@ } }, "@callstack/reassure-compare": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@callstack/reassure-compare/-/reassure-compare-0.5.0.tgz", - "integrity": "sha512-3sBeJ/+Hxjdb01KVb8LszO1kcJ8TXcrVnerUj+LYn2dkBOohAMqGYaOvCeoWsVEHJ+MIOzmvAGBJQRu69RoJdQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@callstack/reassure-compare/-/reassure-compare-0.6.0.tgz", + "integrity": "sha512-P3nmv36CJrQSXg0+z6EuEV/0xAbvxWbAZ7diQHnkbvqk2z8PKRXpkcthrRUpe02wLewa0MLxBUJwLenFnhxIRg==", "dev": true, "requires": { - "@callstack/reassure-logger": "0.3.0", + "@callstack/reassure-logger": "0.3.1", "markdown-builder": "^0.9.0", "markdown-table": "^2.0.0", "zod": "^3.20.2" @@ -54992,9 +54927,9 @@ "dev": true }, "@callstack/reassure-logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@callstack/reassure-logger/-/reassure-logger-0.3.0.tgz", - "integrity": "sha512-JX5o+8qkIbIRL+cQn9XlQYdv9p/3L6J70zZX6NYi9j0VrSS9PZIRfo8ujMdLSqUNV6HZN1ay59RzuncLjVu0aQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@callstack/reassure-logger/-/reassure-logger-0.3.1.tgz", + "integrity": "sha512-IUsNrxVMdt0zgD2IN2snGVGUG8Yc6F3SWaPF8RXUn8qi7XZuYC6WevEL+mIKmlbTTa7qlX9brkn0pJoXAjfcPQ==", "dev": true, "requires": { "chalk": "4.1.2" @@ -55052,12 +54987,12 @@ } }, "@callstack/reassure-measure": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@callstack/reassure-measure/-/reassure-measure-0.5.0.tgz", - "integrity": "sha512-KwlmNYcspBOp7FIw6XOz5O9mnKB4cWCCyM6vG4nFUPHSWQ6yVdRkawVvoPIV5qJ2hw7zCzdtqRrLWQSTF4eKlg==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@callstack/reassure-measure/-/reassure-measure-0.6.0.tgz", + "integrity": "sha512-phXY5DAtKhnu8dA2pmnl+pqFOfrVEFFDJOi4AnObwIcpDSn3IUXgNSe7rSi+JP/mXNWMLoUS8rnH4iIFDyf7qQ==", "dev": true, "requires": { - "@callstack/reassure-logger": "0.3.0", + "@callstack/reassure-logger": "0.3.1", "mathjs": "^11.5.0" } }, @@ -56878,7 +56813,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@kie/act-js/-/act-js-2.3.0.tgz", "integrity": "sha512-Q9k0b05uA46jXKWmVfoGDW+0xsCcE7QPiHi8IH7h41P36DujHKBj4k28RCeIEx3IwUCxYHKwubN8DH4Vzc9XcA==", - "dev": true, "requires": { "@kie/mock-github": "^2.0.0", "adm-zip": "^0.5.10", @@ -56894,7 +56828,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@kie/mock-github/-/mock-github-2.0.0.tgz", "integrity": "sha512-od6UyICJYKMnz9HgEWCQAFT/JsCpKkLp+JQH8fV23tf+ZmmQI1dK3C20k6aO5uJhAHA0yOcFtbKFVF4+8i3DTg==", - "dev": true, "requires": { "@octokit/openapi-types-ghec": "^18.0.0", "ajv": "^8.11.0", @@ -56909,14 +56842,12 @@ "@octokit/openapi-types-ghec": { "version": "18.1.1", "resolved": "https://registry.npmjs.org/@octokit/openapi-types-ghec/-/openapi-types-ghec-18.1.1.tgz", - "integrity": "sha512-5Ri7FLYX4gJSdG+G0Q8QDca/gOLfkPN4YR2hkbVg6hEL+0N62MIsJPTyNaT9pGEXCLd1KbYV6Lh3T2ggsmyBJw==", - "dev": true + "integrity": "sha512-5Ri7FLYX4gJSdG+G0Q8QDca/gOLfkPN4YR2hkbVg6hEL+0N62MIsJPTyNaT9pGEXCLd1KbYV6Lh3T2ggsmyBJw==" }, "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, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -56926,8 +56857,7 @@ "totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==" } } }, @@ -56935,7 +56865,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@kie/mock-github/-/mock-github-1.1.0.tgz", "integrity": "sha512-fD+utlOiyZSOutOcXL0G9jfjbtvOO44PLUyTfgfkrm1+575R/dbvU6AcJfjc1DtHeRv7FC7f4ebyU+a1wgL6CA==", - "dev": true, "requires": { "@octokit/openapi-types-ghec": "^14.0.0", "ajv": "^8.11.0", @@ -56951,7 +56880,6 @@ "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, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -56961,8 +56889,7 @@ "totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==" } } }, @@ -56970,7 +56897,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", - "dev": true, "requires": { "debug": "^4.1.1" } @@ -56978,8 +56904,7 @@ "@kwsites/promise-deferred": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", - "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", - "dev": true + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" }, "@leichtgewicht/ip-codec": { "version": "2.0.4", @@ -57261,7 +57186,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -57270,14 +57194,12 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -57389,8 +57311,7 @@ "@octokit/openapi-types-ghec": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types-ghec/-/openapi-types-ghec-14.0.0.tgz", - "integrity": "sha512-xhd9oEvn2aroGn+sk09Ptx/76Y7aKU0EIgHukHPCU1+rGJreO36baEEk6k8ZPblieHNM39FcykJQmtDrETm0KA==", - "dev": true + "integrity": "sha512-xhd9oEvn2aroGn+sk09Ptx/76Y7aKU0EIgHukHPCU1+rGJreO36baEEk6k8ZPblieHNM39FcykJQmtDrETm0KA==" }, "@octokit/plugin-paginate-rest": { "version": "3.1.0", @@ -57609,9 +57530,9 @@ } }, "@onfido/react-native-sdk": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-7.4.0.tgz", - "integrity": "sha512-qeeaXLxVXz+J0lrqMwQGP52fXhCnTmEAC5K8jZe8YTqst2s1FZZGKkd1bxTloHG5hBBTa39SwWVUKmgPUm+Ssw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-8.3.0.tgz", + "integrity": "sha512-nnhuvezd35v08WXUTQlX+gr4pbnNnwNV5KscC/jJrfjGikNUJnhnAHYxfnfJccTn44qUC6vRaKWq2GfpMUnqNA==", "requires": {} }, "@pkgjs/parseargs": { @@ -68303,8 +68224,7 @@ "adm-zip": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", - "dev": true + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" }, "agent-base": { "version": "6.0.2", @@ -68782,8 +68702,7 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "array-includes": { "version": "3.1.6", @@ -69839,7 +69758,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.2.tgz", "integrity": "sha512-jxJ0PbXR8eQyPlExCvCs3JFnikvs1Yp4gUJt6nmgathdOwvur+q22KWC3h20gvWl4T/14DXKj2IlkJwwZkZPOw==", - "dev": true, "requires": { "cmd-shim": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", @@ -69850,14 +69768,12 @@ "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" }, "write-file-atomic": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, "requires": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" @@ -69930,7 +69846,6 @@ }, "body-parser": { "version": "1.20.0", - "dev": true, "requires": { "bytes": "3.1.2", "content-type": "~1.0.4", @@ -69949,14 +69864,12 @@ "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -69965,7 +69878,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -69973,8 +69885,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -70741,8 +70652,7 @@ "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, "chrome-trace-event": { "version": "1.0.3", @@ -71043,8 +70953,7 @@ "cmd-shim": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.1.tgz", - "integrity": "sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==", - "dev": true + "integrity": "sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==" }, "co": { "version": "4.6.0", @@ -71378,7 +71287,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, "requires": { "safe-buffer": "5.2.1" }, @@ -71386,14 +71294,12 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, "content-type": { - "version": "1.0.4", - "dev": true + "version": "1.0.4" }, "convert-source-map": { "version": "1.9.0", @@ -71403,14 +71309,12 @@ "cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "copy-concurrently": { "version": "1.0.5", @@ -74870,9 +74774,9 @@ } }, "expensify-common": { - "version": "git+ssh://git@github.com/Expensify/expensify-common.git#009c2ab79bf7ddeab0eea7a3a4c0d9cc4277c34b", - "integrity": "sha512-mD9p6Qj8FfvLdb6JLXvF0UNqLN6ymssUU67Fm37zmK18hd1Abw+vR/pQkNpHR2iv+KRs8HdcyuZ0vaOec4VrFQ==", - "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#009c2ab79bf7ddeab0eea7a3a4c0d9cc4277c34b", + "version": "git+ssh://git@github.com/Expensify/expensify-common.git#bdbdf44825658500ba581d3e86237d7b8996cc2e", + "integrity": "sha512-/kXD/18YQJY/iWB5MOSN0ixB1mpxUA+NXEYgKjac1tJd+DoY3K6+bDeu++Qfqg26LCNfvjTkjkDGZVdGsJQ7Hw==", + "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#bdbdf44825658500ba581d3e86237d7b8996cc2e", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", @@ -74944,7 +74848,6 @@ }, "express": { "version": "4.18.1", - "dev": true, "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -74983,7 +74886,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -74991,14 +74893,12 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -75138,7 +75038,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -75197,7 +75096,6 @@ }, "fastq": { "version": "1.13.0", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -75376,7 +75274,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -75391,7 +75288,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -75399,8 +75295,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -75538,8 +75433,7 @@ "follow-redirects": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "dev": true + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" }, "for-each": { "version": "0.3.3", @@ -75705,13 +75599,12 @@ "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.4.tgz", + "integrity": "sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==", "dev": true }, "fragment-cache": { @@ -75758,7 +75651,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, "requires": { "minipass": "^3.0.0" } @@ -75941,7 +75833,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "devOptional": true, "requires": { "is-glob": "^4.0.1" } @@ -77231,8 +77122,7 @@ "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, "is-absolute-url": { "version": "3.0.3", @@ -77427,8 +77317,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "devOptional": true + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, "is-finalizationregistry": { "version": "1.0.2", @@ -77475,7 +77364,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "devOptional": true, "requires": { "is-extglob": "^2.1.1" } @@ -79886,8 +79774,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "json5": { "version": "2.2.3", @@ -80555,20 +80442,20 @@ } }, "mathjs": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.8.0.tgz", - "integrity": "sha512-I7r8HCoqUGyEiHQdeOCF2m2k9N+tcOHO3cZQ3tyJkMMBQMFqMR7dMQEboBMJAiFW2Um3PEItGPwcOc4P6KRqwg==", + "version": "11.11.2", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.11.2.tgz", + "integrity": "sha512-SL4/0Fxm9X4sgovUpJTeyVeZ2Ifnk4tzLPTYWDyR3AIx9SabnXYqtCkyJtmoF3vZrDPKGkLvrhbIL4YN2YbXLQ==", "dev": true, "requires": { - "@babel/runtime": "^7.21.0", + "@babel/runtime": "^7.23.1", "complex.js": "^2.1.1", "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", - "fraction.js": "^4.2.0", + "fraction.js": "4.3.4", "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", "tiny-emitter": "^2.1.0", - "typed-function": "^4.1.0" + "typed-function": "^4.1.1" } }, "md5.js": { @@ -81010,8 +80897,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "mem": { "version": "8.1.1", @@ -81186,8 +81072,7 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, "merge-options": { "version": "3.0.4", @@ -81213,14 +81098,12 @@ "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "metro": { "version": "0.76.8", @@ -82583,7 +82466,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -82619,7 +82501,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, "requires": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -82693,8 +82574,7 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "mock-fs": { "version": "4.14.0", @@ -82866,7 +82746,6 @@ "version": "13.3.3", "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.3.tgz", "integrity": "sha512-z+KUlILy9SK/RjpeXDiDUEAq4T94ADPHE3qaRkf66mpEhzc/ytOMm3Bwdrbq6k1tMWkbdujiKim3G2tfQARuJw==", - "dev": true, "requires": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", @@ -83042,8 +82921,7 @@ "npm-normalize-package-bin": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", - "dev": true + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==" }, "npm-run-path": { "version": "4.0.1", @@ -83889,8 +83767,7 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, "path-type": { "version": "4.0.0", @@ -84395,8 +84272,7 @@ "propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "dev": true + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==" }, "property-information": { "version": "5.6.0", @@ -84416,7 +84292,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, "requires": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -84633,7 +84508,6 @@ "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, "requires": { "side-channel": "^1.0.4" } @@ -84674,8 +84548,7 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "quick-lru": { "version": "5.1.1", @@ -84725,7 +84598,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -84736,14 +84608,12 @@ "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -85254,9 +85124,9 @@ } }, "react-native-onyx": { - "version": "1.0.98", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.98.tgz", - "integrity": "sha512-2wJNmZVBJs2Y0p1G/es4tQZnplJR8rOyVbHv9KZaq/SXluLUnIovttf1MMhVXidDLT+gcE+u20Mck/Gpb8bY0w==", + "version": "1.0.100", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.100.tgz", + "integrity": "sha512-m4bOF/uOtYpfL83fqoWhw7TYV4oKGXt0sfGoel/fhaT1HzXKheXc//ibt/G3VrTCf5nmRv7bXgsXkWjUYLH3UQ==", "requires": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -85432,9 +85302,9 @@ "requires": {} }, "react-native-vision-camera": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-2.15.4.tgz", - "integrity": "sha512-SJXSWH1pu4V3Kj4UuX/vSgOxc9d5wb5+nHqBHd+5iUtVyVLEp0F6Jbbaha7tDoU+kUBwonhlwr2o8oV6NZ7Ibg==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-2.16.2.tgz", + "integrity": "sha512-QIpG33l3QB0AkTfX/ccRknwNRu1APNUkokVKF1lpRO2+tBnkXnGL0UapgXg5u9KIONZtrpupeDeO+J5B2TeQVw==", "requires": {} }, "react-native-web": { @@ -85994,8 +85864,7 @@ "read-cmd-shim": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", - "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", - "dev": true + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==" }, "read-config-file": { "version": "6.3.2", @@ -86181,14 +86050,14 @@ "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" }, "reassure": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/reassure/-/reassure-0.9.0.tgz", - "integrity": "sha512-FIf0GPchyPGItsrW5Wwff/NWVrfOcCUuJJSs4Nur6iRdQt8yvmCpcba4UyemdZ1KaFTIW1gKbAV3u2tuA7zmtQ==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/reassure/-/reassure-0.10.1.tgz", + "integrity": "sha512-+GANr5ojh32NZu1YGfa6W8vIJm3iOIZJUvXT5Gc9fQyre7okYsCzyBq9WsHbnAQDjNq1g9SsM/4bwcVET9OIqA==", "dev": true, "requires": { - "@callstack/reassure-cli": "0.9.0", + "@callstack/reassure-cli": "0.10.0", "@callstack/reassure-danger": "0.1.1", - "@callstack/reassure-measure": "0.5.0" + "@callstack/reassure-measure": "0.6.0" } }, "recast": { @@ -86850,8 +86719,7 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, "right-align": { "version": "0.1.3", @@ -86916,7 +86784,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -87462,7 +87329,6 @@ "version": "3.19.0", "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.19.0.tgz", "integrity": "sha512-hyH2p9Ptxjf/xPuL7HfXbpYt9gKhC1yWDh3KYIAYJJePAKV7AEjLN4xhp7lozOdNiaJ9jlVvAbBymVlcS2jRiA==", - "dev": true, "requires": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", @@ -88613,7 +88479,6 @@ "version": "6.1.15", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", - "dev": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -88626,8 +88491,7 @@ "minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" } } }, @@ -89224,7 +89088,6 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, "requires": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -89274,9 +89137,9 @@ } }, "typed-function": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz", - "integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.1.tgz", + "integrity": "sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ==", "dev": true }, "typedarray": { @@ -91302,9 +91165,9 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, "zod": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", - "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "dev": true }, "zwitch": { diff --git a/package.json b/package.json index 77a0bb1e1664..cd5c6034161a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.81-7", + "version": "1.3.85-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -8,9 +8,11 @@ "private": true, "scripts": { "configure-mapbox": "scripts/setup-mapbox-sdk-walkthrough.sh", + "setupNewDotWebForEmulators": "scripts/setup-newdot-web-emulators.sh", + "startAndroidEmulator": "scripts/start-android.sh", "postinstall": "scripts/postInstall.sh", "clean": "npx react-native clean-project-auto", - "android": "scripts/set-pusher-suffix.sh && npx react-native run-android --variant=developmentDebug --appId=com.expensify.chat.dev", + "android": "scripts/set-pusher-suffix.sh && npx react-native run-android --mode=developmentDebug --appId=com.expensify.chat.dev", "ios": "scripts/set-pusher-suffix.sh && npx react-native run-ios --list-devices --configuration=\"DebugDevelopment\" --scheme=\"New Expensify Dev\"", "pod-install": "cd ios && bundle exec pod install", "ipad": "concurrently \"npx react-native run-ios --simulator=\\\"iPad Pro (12.9-inch) (6th generation)\\\" --configuration=\\\"DebugDevelopment\\\" --scheme=\\\"New Expensify Dev\\\"\"", @@ -65,7 +67,7 @@ "@kie/act-js": "^2.0.1", "@kie/mock-github": "^1.0.0", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", - "@onfido/react-native-sdk": "7.4.0", + "@onfido/react-native-sdk": "8.3.0", "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-clipboard/clipboard": "^1.12.1", @@ -86,7 +88,6 @@ "@types/node": "^18.14.0", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", - "babel-plugin-transform-remove-console": "^6.9.4", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", "core-js": "^3.32.0", @@ -94,7 +95,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#009c2ab79bf7ddeab0eea7a3a4c0d9cc4277c34b", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#bdbdf44825658500ba581d3e86237d7b8996cc2e", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -137,7 +138,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.98", + "react-native-onyx": "1.0.100", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -154,7 +155,7 @@ "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "^3.6.0", - "react-native-vision-camera": "^2.15.4", + "react-native-vision-camera": "^2.16.2", "react-native-web-linear-gradient": "^1.1.2", "react-native-web-lottie": "^1.4.4", "react-native-webview": "^11.17.2", @@ -181,8 +182,6 @@ "@babel/runtime": "^7.20.0", "@electron/notarize": "^1.2.3", "@jest/globals": "^29.5.0", - "@kie/act-js": "^2.0.1", - "@kie/mock-github": "^1.0.0", "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", @@ -265,7 +264,7 @@ "react-native-performance-flipper-reporter": "^2.0.0", "react-native-svg-transformer": "^1.0.0", "react-test-renderer": "18.2.0", - "reassure": "^0.9.0", + "reassure": "^0.10.1", "setimmediate": "^1.0.5", "shellcheck": "^1.1.0", "style-loader": "^2.0.0", diff --git a/patches/@onfido+react-native-sdk+7.4.0.patch b/patches/@onfido+react-native-sdk+8.3.0.patch similarity index 90% rename from patches/@onfido+react-native-sdk+7.4.0.patch rename to patches/@onfido+react-native-sdk+8.3.0.patch index b84225c0f667..12245cb58355 100644 --- a/patches/@onfido+react-native-sdk+7.4.0.patch +++ b/patches/@onfido+react-native-sdk+8.3.0.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/@onfido/react-native-sdk/android/build.gradle b/node_modules/@onfido/react-native-sdk/android/build.gradle -index 781925b..9e16430 100644 +index b4c7106..d5083d3 100644 --- a/node_modules/@onfido/react-native-sdk/android/build.gradle +++ b/node_modules/@onfido/react-native-sdk/android/build.gradle -@@ -134,9 +134,9 @@ afterEvaluate { project -> +@@ -135,9 +135,9 @@ afterEvaluate { project -> group = "Reporting" description = "Generate Jacoco coverage reports after running tests." reports { diff --git a/patches/react-native+0.72.4+003+VerticalScrollBarPosition.patch b/patches/react-native+0.72.4+003+VerticalScrollBarPosition.patch deleted file mode 100644 index e6ed0d4f79a3..000000000000 --- a/patches/react-native+0.72.4+003+VerticalScrollBarPosition.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java -index 33658e7..31c20c0 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java -@@ -381,6 +381,17 @@ public class ReactScrollViewManager extends ViewGroupManager - view.setScrollEventThrottle(scrollEventThrottle); - } - -+ @ReactProp(name = "verticalScrollbarPosition") -+ public void setVerticalScrollbarPosition(ReactScrollView view, String position) { -+ if ("right".equals(position)) { -+ view.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT); -+ } else if ("left".equals(position)) { -+ view.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_LEFT); -+ } else { -+ view.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_DEFAULT); -+ } -+ } -+ - @ReactProp(name = "isInvertedVirtualizedList") - public void setIsInvertedVirtualizedList(ReactScrollView view, boolean applyFix) { - // Usually when inverting the scroll view we are using scaleY: -1 on the list diff --git a/patches/react-native-vision-camera+2.15.4.patch b/patches/react-native-vision-camera+2.15.4.patch deleted file mode 100644 index 0c80d6a8ce55..000000000000 --- a/patches/react-native-vision-camera+2.15.4.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorRuntimeManager.mm b/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorRuntimeManager.mm -index 3841b20..687ea94 100644 ---- a/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorRuntimeManager.mm -+++ b/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorRuntimeManager.mm -@@ -19,6 +19,8 @@ - #import - #import - -+#define VISION_CAMERA_DISABLE_FRAME_PROCESSORS 1 -+ - #ifndef VISION_CAMERA_DISABLE_FRAME_PROCESSORS - #if __has_include() - #if __has_include() diff --git a/scripts/select-device.sh b/scripts/select-device.sh new file mode 100755 index 000000000000..a53a6034d3da --- /dev/null +++ b/scripts/select-device.sh @@ -0,0 +1,133 @@ +#!/bin/bash + +# Utility script for iOS and Android Emulators +# ============================================ +# +# Purpose: +# -------- +# This script helps to start and kill iOS simulators and Android emulators instances. +# +# How this script helps: +# ---------------------- +# This script streamlines the process of starting and killing on both android and ios +# platforms. + +# Use functions and variables from the utils script +source scripts/shellUtils.sh + +select_device_ios() +{ + # shellcheck disable=SC2124 + IFS="$@" arr=$(xcrun xctrace list devices | grep -E "iPhone|iPad") + + # Create arrays to store device names and identifiers + device_names=() + device_identifiers=() + + # Parse the device list and populate the arrays + while IFS= read -r line; do + if [[ $line =~ ^(.*)\ \((.*)\)\ \((.*)\)$ ]]; then + device="${BASH_REMATCH[1]}" + version="${BASH_REMATCH[2]}" + identifier="${BASH_REMATCH[3]}" + device_identifiers+=("$identifier") + device_names+=("$device Version: $version") + else + info "Input does not match the expected pattern." + echo "$line" + fi + done <<< "$arr" + if [ ${#device_names[@]} -eq 0 ]; then + error "No devices detected, please create one." + exit 1 + fi + if [ ${#device_names[@]} -eq 1 ]; then + device_identifier="${device_identifiers[0]}" + success "Single device detected, launching ${device_names[0]}" + open -a Simulator --args -CurrentDeviceUDID "$device_identifier" + return + fi + info "Multiple devices detected, please select one from the list." + PS3='Please enter your choice: ' + select device_name in "${device_names[@]}"; do + if [ -n "$device_name" ]; then + selected_index=$((REPLY - 1)) + device_name_for_display="${device_names[$selected_index]}" + device_identifier="${device_identifiers[$selected_index]}" + break + else + echo "Invalid selection. Please choose a device." + fi + done + success "Launching $device_name_for_display" + open -a Simulator --args -CurrentDeviceUDID "$device_identifier" +} + +kill_all_emulators_ios() { + # kill all the emulators + killall Simulator +} + +restart_adb_server() { + info "Restarting adb ..." + adb kill-server + sleep 2 + adb start-server + sleep 2 + info "Restarting adb done" +} + +ensure_device_available() { + # Must turn off exit on error temporarily + set +e + if adb devices | grep -q offline; then + restart_adb_server + if adb devices | grep -q offline; then + error "Device remains 'offline'. Please investigate!" + exit 1 + fi + fi + set -e +} + +select_device_android() +{ + # shellcheck disable=SC2124 + IFS="$@" arr=$(emulator -list-avds) + + # Create arrays to store device names + device_names=() + + # Parse the device list and populate the arrays + while IFS= read -r line; do + device_names+=("$line") + done <<< "$arr" + if [ ${#device_names[@]} -eq 0 ]; then + error "No devices detected, please create one." + exit 1 + fi + if [ ${#device_names[@]} -eq 1 ]; then + device_identifier="${device_names[0]}" + success "Single device detected, launching $device_identifier" + emulator -avd "$device_identifier" -writable-system > /dev/null 2>&1 & + return + fi + info "Multiple devices detected, please select one from the list." + PS3='Please enter your choice: ' + select device_name in "${device_names[@]}"; do + if [ -n "$device_name" ]; then + selected_index=$((REPLY - 1)) + device_identifier="${device_names[$selected_index]}" + break + else + echo "Invalid selection. Please choose a device." + fi + done + success "Launching $device_identifier" + emulator -avd "$device_identifier" -writable-system > /dev/null 2>&1 & +} + +kill_all_emulators_android() { + # kill all the emulators + adb devices | grep emulator | cut -f1 | while read -r line; do adb -s "$line" emu kill; done +} diff --git a/scripts/setup-mapbox-sdk-walkthrough.sh b/scripts/setup-mapbox-sdk-walkthrough.sh index 20b79641fc42..46b970f2d462 100755 --- a/scripts/setup-mapbox-sdk-walkthrough.sh +++ b/scripts/setup-mapbox-sdk-walkthrough.sh @@ -21,7 +21,7 @@ # To configure Mapbox, invoke this script by running the following command from the project's root directory: # npm run configure-mapbox -# Use functions and varaibles from the utils script +# Use functions and variables from the utils script source scripts/shellUtils.sh # Intro message diff --git a/scripts/setup-mapbox-sdk.sh b/scripts/setup-mapbox-sdk.sh index 06fd75fba299..fa37cc2fbbad 100755 --- a/scripts/setup-mapbox-sdk.sh +++ b/scripts/setup-mapbox-sdk.sh @@ -36,7 +36,7 @@ # To run this script, pass the secret Mapbox access token as a command-line argument: # ./scriptname.sh YOUR_MAPBOX_ACCESS_TOKEN -# Use functions and varaibles from the utils script +# Use functions and variables from the utils script source scripts/shellUtils.sh NETRC_PATH="$HOME/.netrc" diff --git a/scripts/setup-newdot-web-emulators.sh b/scripts/setup-newdot-web-emulators.sh new file mode 100755 index 000000000000..a0ed1422d2a9 --- /dev/null +++ b/scripts/setup-newdot-web-emulators.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +# NewDot Web Configuration Script for iOS and Android Emulators +# ============================================================= +# +# Purpose: +# -------- +# This script configures Configure iOS simulators and Android emulators to connect to +# new.expensify.com.dev over https for local development. +# +# Background: +# ----------- +# We plan to change the URL to serve the App on the development environment from +# localhost:8082 to new.expensify.com.dev. This can be accomplished by adding a new entry +# to the laptop's hosts file along with changes made in the PR. +# However, we're not sure how we can access the App on Safari or Chrome on iOS or Android +# simulators. +# +# How this script helps: +# ---------------------- +# This script streamlines the process of adding the certificates to both android and +# ios platforms. +# +# Usage: +# ------ +# To run this script, pass the platform on which you want to run as a command-line +# argument which can be the following: +# 1. ios +# 2. android +# 3. all (default) +# ./setup-newdot-web-emulators.sh platform + +# Use functions and variables from the utils script +source scripts/shellUtils.sh + +# Use functions to select and open the emulator for iOS and Android +source scripts/select-device.sh + +if [ $# -eq 0 ]; then + platform="all" +else + platform=$1 +fi + +setup_ios() +{ + select_device_ios + sleep 30 + info "Installing certificates on iOS simulator" + xcrun simctl keychain booted add-root-cert "$(mkcert -CAROOT)/rootCA.pem" + kill_all_emulators_ios +} + +setup_android_path_1() +{ + adb root || { + error "Failed to restart adb as root" + info "You may want to check https://stackoverflow.com/a/45668555" + exit 1 + } + sleep 2 + adb remount + adb push /etc/hosts /system/etc/hosts + kill_all_emulators_android +} + +setup_android_path_2() +{ + adb root || { + error "Failed to restart adb as root" + info "You may want to check https://stackoverflow.com/a/45668555" + exit 1 + } + sleep 2 + adb disable-verity + adb reboot + sleep 30 + ensure_device_available + adb root + sleep 2 + adb remount + adb push /etc/hosts /system/etc/hosts + kill_all_emulators_android +} + +setup_android() +{ + select_device_android + sleep 30 + ensure_device_available + info "Installing certificates on Android emulator" + setup_android_path_1 || { + info "Looks like the system partition is read-only" + info "Trying another method to install the certificates" + setup_android_path_2 + } +} + +if [ "$platform" = "ios" ] || [ "$platform" = "all" ]; then + setup_ios || { + error "Failed to setup iOS simulator" + exit 1 + } +fi + +if [ "$platform" = "android" ] || [ "$platform" = "all" ]; then + setup_android || { + error "Failed to setup Android emulator" + exit 1 + } +fi + +success "Done!" diff --git a/scripts/shellUtils.sh b/scripts/shellUtils.sh index 4c9e2febc34d..848e6d238254 100644 --- a/scripts/shellUtils.sh +++ b/scripts/shellUtils.sh @@ -1,10 +1,29 @@ #!/bin/bash -declare -r GREEN=$'\e[1;32m' -declare -r RED=$'\e[1;31m' -declare -r BLUE=$'\e[1;34m' -declare -r TITLE=$'\e[1;4;34m' -declare -r RESET=$'\e[0m' +# Check if GREEN has already been defined +if [ -z "${GREEN+x}" ]; then + declare -r GREEN=$'\e[1;32m' +fi + +# Check if RED has already been defined +if [ -z "${RED+x}" ]; then + declare -r RED=$'\e[1;31m' +fi + +# Check if BLUE has already been defined +if [ -z "${BLUE+x}" ]; then + declare -r BLUE=$'\e[1;34m' +fi + +# Check if TITLE has already been defined +if [ -z "${TITLE+x}" ]; then + declare -r TITLE=$'\e[1;4;34m' +fi + +# Check if RESET has already been defined +if [ -z "${RESET+x}" ]; then + declare -r RESET=$'\e[0m' +fi function success { echo "🎉 $GREEN$1$RESET" diff --git a/scripts/start-android.sh b/scripts/start-android.sh new file mode 100644 index 000000000000..b9a4e08a07a2 --- /dev/null +++ b/scripts/start-android.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Script for starting android emulator correctly + +# Use functions to select and open the emulator for iOS and Android +source scripts/select-device.sh + +select_device_android +sleep 30 +ensure_device_available +adb reverse tcp:8082 tcp:8082 \ No newline at end of file diff --git a/src/CONFIG.ts b/src/CONFIG.ts index c02ed8065836..8b1dab5b3d71 100644 --- a/src/CONFIG.ts +++ b/src/CONFIG.ts @@ -64,6 +64,7 @@ export default { CONCIERGE_URL_PATHNAME: 'concierge/', DEVPORTAL_URL_PATHNAME: '_devportal/', CONCIERGE_URL: `${expensifyURL}concierge/`, + SAML_URL: `${expensifyURL}authentication/saml/login`, }, IS_IN_PRODUCTION: Platform.OS === 'web' ? process.env.NODE_ENV === 'production' : !__DEV__, IS_IN_STAGING: ENVIRONMENT === CONST.ENVIRONMENT.STAGING, diff --git a/src/CONST.ts b/src/CONST.ts index f6ab2748ceea..e358a850c33e 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -243,6 +243,7 @@ const CONST = { CUSTOM_STATUS: 'customStatus', NEW_DOT_CATEGORIES: 'newDotCategories', NEW_DOT_TAGS: 'newDotTags', + NEW_DOT_SAML: 'newDotSAML', }, BUTTON_STATES: { DEFAULT: 'default', @@ -472,6 +473,7 @@ const CONST = { HAND_ICON_HEIGHT: 152, HAND_ICON_WIDTH: 200, SHUTTER_SIZE: 90, + MAX_REPORT_PREVIEW_RECEIPTS: 3, }, REPORT: { MAXIMUM_PARTICIPANTS: 8, @@ -668,6 +670,7 @@ const CONST = { TOOLTIP_SENSE: 1000, TRIE_INITIALIZATION: 'trie_initialization', COMMENT_LENGTH_DEBOUNCE_TIME: 500, + SEARCH_FOR_REPORTS_DEBOUNCE_TIME: 300, }, PRIORITY_MODE: { GSD: 'gsd', @@ -904,6 +907,8 @@ const CONST = { HERE_TEXT: '@here', }, COMPOSER_MAX_HEIGHT: 125, + CHAT_FOOTER_SECONDARY_ROW_HEIGHT: 15, + CHAT_FOOTER_SECONDARY_ROW_PADDING: 5, CHAT_FOOTER_MIN_HEIGHT: 65, CHAT_SKELETON_VIEW: { AVERAGE_ROW_HEIGHT: 80, @@ -1242,6 +1247,7 @@ const CONST = { CLOSED: 6, STATE_SUSPENDED: 7, }, + ACTIVE_STATES: [2, 3, 4, 7], }, AVATAR_ROW_SIZE: { DEFAULT: 4, @@ -2660,8 +2666,8 @@ const CONST = { ATTACHMENT: 'common.attachment', }, TEACHERS_UNITE: { - PUBLIC_ROOM_ID: '207591744844000', - POLICY_ID: 'ABD1345ED7293535', + PUBLIC_ROOM_ID: '7470147100835202', + POLICY_ID: 'B795B6319125BDF2', POLICY_NAME: 'Expensify.org / Teachers Unite!', PUBLIC_ROOM_NAME: '#teachers-unite', }, @@ -2716,6 +2722,7 @@ const CONST = { DEMO_PAGES: { SAASTR: 'SaaStrDemoSetup', SBE: 'SbeDemoSetup', + MONEY2020: 'Money2020DemoSetup', }, MAPBOX: { diff --git a/src/Expensify.js b/src/Expensify.js index 642b8ceb456c..6010824cf275 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -29,6 +29,7 @@ import SplashScreenHider from './components/SplashScreenHider'; import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper'; import EmojiPicker from './components/EmojiPicker/EmojiPicker'; import * as EmojiPickerAction from './libs/actions/EmojiPickerAction'; +import * as DemoActions from './libs/actions/DemoActions'; import DeeplinkWrapper from './components/DeeplinkWrapper'; // This lib needs to be imported, but it has nothing to export since all it contains is an Onyx connection @@ -167,11 +168,13 @@ function Expensify(props) { // If the app is opened from a deep link, get the reportID (if exists) from the deep link and navigate to the chat report Linking.getInitialURL().then((url) => { + DemoActions.runDemoByURL(url); Report.openReportFromDeepLink(url, isAuthenticated); }); // Open chat report from a deep link (only mobile native) Linking.addEventListener('url', (state) => { + DemoActions.runDemoByURL(state.url); Report.openReportFromDeepLink(state.url, isAuthenticated); }); diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index bd1c387ca49a..f60506ab7245 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -26,6 +26,9 @@ const ONYXKEYS = { /** Boolean flag set whenever the sidebar has loaded */ IS_SIDEBAR_LOADED: 'isSidebarLoaded', + /** Boolean flag set whenever we are searching for reports in the server */ + IS_SEARCHING_FOR_REPORTS: 'isSearchingForReports', + /** Note: These are Persisted Requests - not all requests in the main queue as the key name might lead one to believe */ PERSISTED_REQUESTS: 'networkRequestQueue', @@ -47,6 +50,9 @@ const ONYXKEYS = { // draft status CUSTOM_STATUS_DRAFT: 'customStatusDraft', + // keep edit message focus state + INPUT_FOCUSED: 'inputFocused', + /** Contains all the personalDetails the user has access to, keyed by accountID */ PERSONAL_DETAILS_LIST: 'personalDetailsList', @@ -229,6 +235,8 @@ const ONYXKEYS = { DOWNLOAD: 'download_', POLICY: 'policy_', POLICY_MEMBERS: 'policyMembers_', + POLICY_DRAFTS: 'policyDrafts_', + POLICY_MEMBERS_DRAFTS: 'policyMembersDrafts_', POLICY_CATEGORIES: 'policyCategories_', POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', @@ -249,6 +257,7 @@ const ONYXKEYS = { REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_', SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', + SPLIT_TRANSACTION_DRAFT: 'splitTransactionDraft_', PRIVATE_NOTES_DRAFT: 'privateNotesDraft_', // Manual request tab selector @@ -311,6 +320,7 @@ type OnyxValues = { [ONYXKEYS.MODAL]: OnyxTypes.Modal; [ONYXKEYS.NETWORK]: OnyxTypes.Network; [ONYXKEYS.CUSTOM_STATUS_DRAFT]: OnyxTypes.CustomStatusDraft; + [ONYXKEYS.INPUT_FOCUSED]: boolean; [ONYXKEYS.PERSONAL_DETAILS_LIST]: Record; [ONYXKEYS.PRIVATE_PERSONAL_DETAILS]: OnyxTypes.PrivatePersonalDetails; [ONYXKEYS.TASK]: OnyxTypes.Task; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 3ef5ce44c4da..b7896225557d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -36,6 +36,8 @@ export default { APPLE_SIGN_IN: 'sign-in-with-apple', GOOGLE_SIGN_IN: 'sign-in-with-google', DESKTOP_SIGN_IN_REDIRECT: 'desktop-signin-redirect', + SAML_SIGN_IN: 'sign-in-with-saml', + // This is a special validation URL that will take the user to /workspace/new after validation. This is used // when linking users from e.com in order to share a session in this app. ENABLE_PAYMENTS: 'enable-payments', @@ -71,22 +73,26 @@ export default { SETTINGS_ABOUT: 'settings/about', SETTINGS_APP_DOWNLOAD_LINKS: 'settings/about/app-download-links', SETTINGS_WALLET: 'settings/wallet', - SETTINGS_WALLET_DOMAINCARDS: { + SETTINGS_WALLET_DOMAINCARD: { route: '/settings/wallet/card/:domain', getRoute: (domain: string) => `/settings/wallet/card/${domain}`, }, SETTINGS_REPORT_FRAUD: { - route: '/settings/wallet/cards/:domain/report-virtual-fraud', - getRoute: (domain: string) => `/settings/wallet/cards/${domain}/report-virtual-fraud`, + route: '/settings/wallet/card/:domain/report-virtual-fraud', + getRoute: (domain: string) => `/settings/wallet/card/${domain}/report-virtual-fraud`, }, SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card', SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account', SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments', + SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: { + route: 'settings/wallet/card/:domain/digital-details/update-address', + getRoute: (domain: string) => `settings/wallet/card/${domain}/digital-details/update-address`, + }, SETTINGS_WALLET_TRANSFER_BALANCE: 'settings/wallet/transfer-balance', SETTINGS_WALLET_CHOOSE_TRANSFER_ACCOUNT: 'settings/wallet/choose-transfer-account', SETTINGS_WALLET_CARD_ACTIVATE: { - route: 'settings/wallet/cards/:domain/activate', - getRoute: (domain: string) => `settings/wallet/cards/${domain}/activate`, + route: 'settings/wallet/card/:domain/activate', + getRoute: (domain: string) => `settings/wallet/card/${domain}/activate`, }, SETTINGS_PERSONAL_DETAILS: 'settings/profile/personal-details', SETTINGS_PERSONAL_DETAILS_LEGAL_NAME: 'settings/profile/personal-details/legal-name', @@ -171,6 +177,14 @@ export default { route: 'r/:reportID/split/:reportActionID', getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}`, }, + EDIT_SPLIT_BILL: { + route: `r/:reportID/split/:reportActionID/edit/:field`, + getRoute: (reportID: string, reportActionID: string, field: ValueOf) => `r/${reportID}/split/${reportActionID}/edit/${field}`, + }, + EDIT_SPLIT_BILL_CURRENCY: { + route: 'r/:reportID/split/:reportActionID/edit/currency', + getRoute: (reportID: string, reportActionID: string, currency: string, backTo: string) => `r/${reportID}/split/${reportActionID}/edit/currency?currency=${currency}&backTo=${backTo}`, + }, TASK_TITLE: { route: 'r/:reportID/title', getRoute: (reportID: string) => `r/${reportID}/title`, @@ -329,9 +343,10 @@ export default { getRoute: (policyID: string) => `workspace/${policyID}/members`, }, - // These are some on-off routes that will be removed once they're no longer needed (see GH issues for details) + // These are some one-off routes that will be removed once they're no longer needed (see GH issues for details) SAASTR: 'saastr', SBE: 'sbe', + MONEY2020: 'money2020', // Iframe screens from olddot HOME_OLDDOT: 'home', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 0346168f0407..8ef787edec2e 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -17,6 +17,8 @@ export default { WORKSPACES: 'Settings_Workspaces', SECURITY: 'Settings_Security', STATUS: 'Settings_Status', + WALLET: 'Settings_Wallet', + WALLET_DOMAIN_CARDS: 'Settings_Wallet_DomainCards', }, SAVE_THE_WORLD: { ROOT: 'SaveTheWorld_Root', @@ -24,6 +26,7 @@ export default { SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop', SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', DESKTOP_SIGN_IN_REDIRECT: 'DesktopSignInRedirect', + SAML_SIGN_IN: 'SAMLSignIn', // Iframe screens from olddot HOME_OLDDOT: 'Home_OLDDOT', diff --git a/src/components/AnonymousReportFooter.js b/src/components/AnonymousReportFooter.js index dd1a0864b0cf..43933210dc0b 100644 --- a/src/components/AnonymousReportFooter.js +++ b/src/components/AnonymousReportFooter.js @@ -36,6 +36,7 @@ function AnonymousReportFooter(props) { report={props.report} personalDetails={props.personalDetails} isAnonymous + shouldEnableDetailPageNavigation /> diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index b8bfb4c36122..61b138747950 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -376,7 +376,7 @@ function AttachmentModal(props) { text: props.translate('common.download'), onSelected: () => downloadAttachment(source), }); - if (TransactionUtils.hasReceipt(props.transaction) && !TransactionUtils.isReceiptBeingScanned(props.transaction)) { + if (TransactionUtils.hasReceipt(props.transaction) && !TransactionUtils.isReceiptBeingScanned(props.transaction) && !isSettled) { menuItems.push({ icon: Expensicons.Trashcan, text: props.translate('receipt.deleteReceipt'), @@ -469,7 +469,7 @@ function AttachmentModal(props) { )} )} - {isAttachmentReceipt ? ( + {isAttachmentReceipt && ( - ) : ( - )} + {!isAttachmentReceipt && ( + + )} {props.children && props.children({ diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index 3aeef8482e2d..096b6d60d428 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -10,6 +10,7 @@ import Button from '../../Button'; import AttachmentView from '../AttachmentView'; import SafeAreaConsumer from '../../SafeAreaConsumer'; import ReportAttachmentsContext from '../../../pages/home/report/ReportAttachmentsContext'; +import * as AttachmentsPropTypes from '../propTypes'; const propTypes = { /** Attachment required information such as the source and file name */ @@ -20,8 +21,8 @@ const propTypes = { /** Whether source URL requires authentication */ isAuthTokenRequired: PropTypes.bool, - /** The source (URL) of the attachment */ - source: PropTypes.string, + /** URL to full-sized attachment or SVG function */ + source: AttachmentsPropTypes.attachmentSourcePropType.isRequired, /** Additional information about the attachment file */ file: PropTypes.shape({ @@ -31,6 +32,9 @@ const propTypes = { /** Whether the attachment has been flagged */ hasBeenFlagged: PropTypes.bool, + + /** The id of the transaction related to the attachment */ + transactionID: PropTypes.string, }).isRequired, /** Whether the attachment is currently being viewed in the carousel */ @@ -97,6 +101,7 @@ function CarouselItem({item, isFocused, onPress}) { isFocused={isFocused} onPress={onPress} isUsedInCarousel + transactionID={item.transactionID} /> diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js index bd12020341be..bcea50698b3b 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.js +++ b/src/components/Attachments/AttachmentCarousel/index.native.js @@ -104,10 +104,10 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, onClose, * @returns {JSX.Element} */ const renderItem = useCallback( - ({item}) => ( + ({item, isActive}) => ( setShouldShowArrows(!shouldShowArrows)} /> ), diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index f4d3036ff802..a1b07fb99dd8 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -3,6 +3,7 @@ import {View, ActivityIndicator} from 'react-native'; import _ from 'underscore'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; +import {withOnyx} from 'react-native-onyx'; import styles from '../../../styles/styles'; import Icon from '../../Icon'; import * as Expensicons from '../../Icon/Expensicons'; @@ -17,7 +18,10 @@ import AttachmentViewPdf from './AttachmentViewPdf'; import addEncryptedAuthTokenToURL from '../../../libs/addEncryptedAuthTokenToURL'; import * as StyleUtils from '../../../styles/StyleUtils'; import {attachmentViewPropTypes, attachmentViewDefaultProps} from './propTypes'; +import * as TransactionUtils from '../../../libs/TransactionUtils'; +import DistanceEReceipt from '../../DistanceEReceipt'; import useNetwork from '../../../hooks/useNetwork'; +import ONYXKEYS from '../../../ONYXKEYS'; const propTypes = { ...attachmentViewPropTypes, @@ -38,6 +42,10 @@ const propTypes = { /** Denotes whether it is a workspace avatar or not */ isWorkspaceAvatar: PropTypes.bool, + + /** The id of the transaction related to the attachment */ + // eslint-disable-next-line react/no-unused-prop-types + transactionID: PropTypes.string, }; const defaultProps = { @@ -47,6 +55,7 @@ const defaultProps = { onToggleKeyboard: () => {}, containerStyles: [], isWorkspaceAvatar: false, + transactionID: '', }; function AttachmentView({ @@ -64,9 +73,9 @@ function AttachmentView({ isFocused, isWorkspaceAvatar, fallbackSource, + transaction, }) { const [loadComplete, setLoadComplete] = useState(false); - const [imageError, setImageError] = useState(false); useNetwork({onReconnect: () => setImageError(false)}); @@ -113,6 +122,10 @@ function AttachmentView({ ); } + if (TransactionUtils.isDistanceRequest(transaction)) { + return ; + } + // For this check we use both source and file.name since temporary file source is a blob // both PDFs and images will appear as images when pasted into the text field. // We also check for numeric source since this is how static images (used for preview) are represented in RN. @@ -168,4 +181,12 @@ AttachmentView.propTypes = propTypes; AttachmentView.defaultProps = defaultProps; AttachmentView.displayName = 'AttachmentView'; -export default compose(memo, withLocalize)(AttachmentView); +export default compose( + memo, + withLocalize, + withOnyx({ + transaction: { + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + }, + }), +)(AttachmentView); diff --git a/src/components/DatePicker/index.ios.js b/src/components/DatePicker/index.ios.js index 5d87636a9365..ef40aecb6f8c 100644 --- a/src/components/DatePicker/index.ios.js +++ b/src/components/DatePicker/index.ios.js @@ -1,147 +1,136 @@ -import React from 'react'; -// eslint-disable-next-line no-restricted-imports +import React, {useState, useRef, useCallback, useEffect} from 'react'; import {Button, View, Keyboard} from 'react-native'; import RNDatePicker from '@react-native-community/datetimepicker'; import moment from 'moment'; -import _ from 'underscore'; -import compose from '../../libs/compose'; +import isFunction from 'lodash/isFunction'; import TextInput from '../TextInput'; -import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import Popover from '../Popover'; import CONST from '../../CONST'; import styles from '../../styles/styles'; import themeColors from '../../styles/themes/default'; import {propTypes, defaultProps} from './datepickerPropTypes'; -import withKeyboardState, {keyboardStatePropTypes} from '../withKeyboardState'; +import useKeyboardState from '../../hooks/useKeyboardState'; +import useLocalize from '../../hooks/useLocalize'; -const datepickerPropTypes = { - ...propTypes, - ...withLocalizePropTypes, - ...keyboardStatePropTypes, -}; +function DatePicker({value, defaultValue, innerRef, onInputChange, preferredLocale, minDate, maxDate, label, disabled, onBlur, placeholder, containerStyles, errorText}) { + const [isPickerVisible, setIsPickerVisible] = useState(false); + const [selectedDate, setSelectedDate] = useState(moment(value || defaultValue).toDate()); + const {isKeyboardShown} = useKeyboardState(); + const {translate} = useLocalize(); + const initialValue = useRef(null); + const inputRef = useRef(null); -class DatePicker extends React.Component { - constructor(props) { - super(props); - - this.state = { - isPickerVisible: false, - selectedDate: props.value || props.defaultValue ? moment(props.value || props.defaultValue).toDate() : new Date(), - }; - - this.showPicker = this.showPicker.bind(this); - this.reset = this.reset.bind(this); - this.selectDate = this.selectDate.bind(this); - this.updateLocalDate = this.updateLocalDate.bind(this); - } - - showPicker() { - this.initialValue = this.state.selectedDate; + const showPicker = useCallback(() => { + initialValue.current = selectedDate; // Opens the popover only after the keyboard is hidden to avoid a "blinking" effect where the keyboard was on iOS // See https://github.com/Expensify/App/issues/14084 for more context - if (!this.props.isKeyboardShown) { - this.setState({isPickerVisible: true}); + if (!isKeyboardShown) { + setIsPickerVisible(true); return; } + const listener = Keyboard.addListener('keyboardDidHide', () => { - this.setState({isPickerVisible: true}); + setIsPickerVisible(true); listener.remove(); }); Keyboard.dismiss(); - } + }, [isKeyboardShown, selectedDate]); + + useEffect(() => { + if (!isFunction(innerRef)) { + return; + } + + const input = inputRef.current; + + if (input && input.focus && isFunction(input.focus)) { + innerRef({...input, focus: showPicker}); + return; + } + + innerRef(input); + }, [innerRef, showPicker]); /** * Reset the date spinner to the initial value */ - reset() { - this.setState({selectedDate: this.initialValue}); - } + const reset = () => { + setSelectedDate(initialValue.current); + }; /** * Accept the current spinner changes, close the spinner and propagate the change - * to the parent component (props.onInputChange) + * to the parent component (onInputChange) */ - selectDate() { - this.setState({isPickerVisible: false}); - const asMoment = moment(this.state.selectedDate, true); - this.props.onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING)); - } + const selectDate = () => { + setIsPickerVisible(false); + const asMoment = moment(selectedDate, true); + onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING)); + }; /** * @param {Event} event - * @param {Date} selectedDate + * @param {Date} date */ - updateLocalDate(event, selectedDate) { - this.setState({selectedDate}); - } + const updateLocalDate = (event, date) => { + setSelectedDate(date); + }; - render() { - const dateAsText = this.props.value || this.props.defaultValue ? moment(this.props.value || this.props.defaultValue).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; - return ( - <> - { - if (!_.isFunction(this.props.innerRef)) { - return; - } - if (el && el.focus && typeof el.focus === 'function') { - let inputRef = {...el}; - inputRef = {...inputRef, focus: this.showPicker}; - this.props.innerRef(inputRef); - return; - } + const dateAsText = value || defaultValue ? moment(value || defaultValue).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; - this.props.innerRef(el); - }} - /> - - - - - - + + + + + - - - ); - } + + + + + ); } -DatePicker.propTypes = datepickerPropTypes; +DatePicker.propTypes = propTypes; DatePicker.defaultProps = defaultProps; +DatePicker.displayName = 'DatePicker'; /** * We're applying localization here because we present a modal (with buttons) ourselves @@ -149,15 +138,10 @@ DatePicker.defaultProps = defaultProps; * locale. Otherwise the spinner would be present in the system locale and it would be weird if it happens * that the modal buttons are in one locale (app) while the (spinner) month names are another (system) */ -export default compose( - withLocalize, - withKeyboardState, -)( - React.forwardRef((props, ref) => ( - - )), -); +export default React.forwardRef((props, ref) => ( + +)); diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js new file mode 100644 index 000000000000..7c7837b8413d --- /dev/null +++ b/src/components/DistanceEReceipt.js @@ -0,0 +1,121 @@ +import React, {useMemo} from 'react'; +import {View, ScrollView} from 'react-native'; +import lodashGet from 'lodash/get'; +import _ from 'underscore'; +import Text from './Text'; +import styles from '../styles/styles'; +import transactionPropTypes from './transactionPropTypes'; +import * as ReceiptUtils from '../libs/ReceiptUtils'; +import * as ReportUtils from '../libs/ReportUtils'; +import * as CurrencyUtils from '../libs/CurrencyUtils'; +import * as TransactionUtils from '../libs/TransactionUtils'; +import tryResolveUrlFromApiRoot from '../libs/tryResolveUrlFromApiRoot'; +import ThumbnailImage from './ThumbnailImage'; +import useLocalize from '../hooks/useLocalize'; +import Icon from './Icon'; +import themeColors from '../styles/themes/default'; +import * as Expensicons from './Icon/Expensicons'; +import EReceiptBackground from '../../assets/images/eReceipt_background.svg'; +import useNetwork from '../hooks/useNetwork'; +import PendingMapView from './MapView/PendingMapView'; + +const propTypes = { + /** The transaction for the distance request */ + transaction: transactionPropTypes, +}; + +const defaultProps = { + transaction: {}, +}; + +function DistanceEReceipt({transaction}) { + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); + const {thumbnail} = TransactionUtils.hasReceipt(transaction) ? ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename) : {}; + const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant, created: transactionDate} = ReportUtils.getTransactionDetails(transaction); + const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : translate('common.tbd'); + const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); + const waypoints = lodashGet(transaction, 'comment.waypoints', {}); + const sortedWaypoints = useMemo( + () => + // The waypoint keys are sometimes out of order + _.chain(waypoints) + .keys() + .sort((keyA, keyB) => TransactionUtils.getWaypointIndex(keyA) - TransactionUtils.getWaypointIndex(keyB)) + .map((key) => ({[key]: waypoints[key]})) + .reduce((result, obj) => (obj ? _.assign(result, obj) : result), {}) + .value(), + [waypoints], + ); + return ( + + + + + + {isOffline || !thumbnailSource ? ( + + ) : ( + + )} + + + {formattedTransactionAmount} + {transactionMerchant} + + + {_.map(sortedWaypoints, (waypoint, key) => { + const index = TransactionUtils.getWaypointIndex(key); + let descriptionKey = 'distance.waypointDescription.'; + if (index === 0) { + descriptionKey += 'start'; + } else if (index === _.size(waypoints) - 1) { + descriptionKey += 'finish'; + } else { + descriptionKey += 'stop'; + } + return ( + + {translate(descriptionKey)} + {waypoint.address || ''} + + ); + })} + + {translate('common.date')} + {transactionDate} + + + + + {translate('eReceipt.guaranteed')} + + + + + ); +} + +export default DistanceEReceipt; +DistanceEReceipt.displayName = 'DistanceEReceipt'; +DistanceEReceipt.propTypes = propTypes; +DistanceEReceipt.defaultProps = defaultProps; diff --git a/src/components/DistanceRequest/DistanceRequestFooter.js b/src/components/DistanceRequest/DistanceRequestFooter.js index 574dda3d0dd6..c96adfee9ba0 100644 --- a/src/components/DistanceRequest/DistanceRequestFooter.js +++ b/src/components/DistanceRequest/DistanceRequestFooter.js @@ -10,8 +10,6 @@ import ONYXKEYS from '../../ONYXKEYS'; import styles from '../../styles/styles'; import useNetwork from '../../hooks/useNetwork'; import useLocalize from '../../hooks/useLocalize'; -import DotIndicatorMessage from '../DotIndicatorMessage'; -import * as ErrorUtils from '../../libs/ErrorUtils'; import theme from '../../styles/themes/default'; import * as TransactionUtils from '../../libs/TransactionUtils'; import Button from '../Button'; @@ -32,9 +30,6 @@ const propTypes = { }), ), - /** Whether there is an error with the route */ - hasRouteError: PropTypes.bool, - /** Function to call when the user wants to add a new waypoint */ navigateToWaypointEditPage: PropTypes.func.isRequired, @@ -53,13 +48,12 @@ const propTypes = { const defaultProps = { waypoints: {}, - hasRouteError: false, mapboxAccessToken: { token: '', }, transaction: {}, }; -function DistanceRequestFooter({waypoints, transaction, mapboxAccessToken, hasRouteError, navigateToWaypointEditPage}) { +function DistanceRequestFooter({waypoints, transaction, mapboxAccessToken, navigateToWaypointEditPage}) { const {isOffline} = useNetwork(); const {translate} = useLocalize(); @@ -103,13 +97,6 @@ function DistanceRequestFooter({waypoints, transaction, mapboxAccessToken, hasRo return ( <> - {hasRouteError && ( - - )}

>(WrappedComponent: React.ComponentType

) => React.ComponentType

>; + +type BlankHOC =

>(Component: React.ComponentType

) => React.ComponentType

; + +type SetupPerformanceObserver = () => void; +type DiffObject = (object: Record, base: Record) => Record; +type GetPerformanceMetrics = () => PerformanceEntry[]; +type PrintPerformanceMetrics = () => void; +type MarkStart = (name: string, detail?: Record) => PerformanceMark | void; +type MarkEnd = (name: string, detail?: Record) => PerformanceMark | void; +type MeasureFailSafe = (measureName: string, startOrMeasureOptions: string, endMark: string) => void; +type MeasureTTI = (endMark: string) => void; +type TraceRender = (id: string, phase: Phase, actualDuration: number, baseDuration: number, startTime: number, commitTime: number, interactions: Set) => PerformanceMeasure | void; +type WithRenderTrace = ({id}: WrappedComponentConfig) => WithRenderTraceHOC | BlankHOC; +type SubscribeToMeasurements = (callback: PerformanceEntriesCallback) => void; + +type PerformanceModule = { + diffObject: DiffObject; + setupPerformanceObserver: SetupPerformanceObserver; + getPerformanceMetrics: GetPerformanceMetrics; + printPerformanceMetrics: PrintPerformanceMetrics; + markStart: MarkStart; + markEnd: MarkEnd; + measureFailSafe: MeasureFailSafe; + measureTTI: MeasureTTI; + traceRender: TraceRender; + withRenderTrace: WithRenderTrace; + subscribeToMeasurements: SubscribeToMeasurements; +}; + +let rnPerformance: RNPerformance; /** * Deep diff between two objects. Useful for figuring out what changed about an object from one render to the next so * that state and props updates can be optimized. - * - * @param {Object} object - * @param {Object} base - * @return {Object} */ -function diffObject(object, base) { - function changes(obj, comparisonObject) { +function diffObject(object: Record, base: Record): Record { + function changes(obj: Record, comparisonObject: Record): Record { return lodashTransform(obj, (result, value, key) => { - if (_.isEqual(value, comparisonObject[key])) { + if (isEqual(value, comparisonObject[key])) { return; } // eslint-disable-next-line no-param-reassign - result[key] = _.isObject(value) && _.isObject(comparisonObject[key]) ? changes(value, comparisonObject[key]) : value; + result[key] = isObject(value) && isObject(comparisonObject[key]) ? changes(value as Record, comparisonObject[key] as Record) : value; }); } return changes(object, base); } -const Performance = { +const Performance: PerformanceModule = { // When performance monitoring is disabled the implementations are blank diffObject, setupPerformanceObserver: () => {}, @@ -44,7 +78,11 @@ const Performance = { measureFailSafe: () => {}, measureTTI: () => {}, traceRender: () => {}, - withRenderTrace: () => (Component) => Component, + withRenderTrace: + () => + // eslint-disable-next-line @typescript-eslint/naming-convention +

>(Component: React.ComponentType

): React.ComponentType

=> + Component, subscribeToMeasurements: () => {}, }; @@ -53,20 +91,21 @@ if (Metrics.canCapturePerformanceMetrics()) { perfModule.setResourceLoggingEnabled(true); rnPerformance = perfModule.default; - Performance.measureFailSafe = (measureName, startOrMeasureOptions, endMark) => { + Performance.measureFailSafe = (measureName: string, startOrMeasureOptions: string, endMark: string) => { try { rnPerformance.measure(measureName, startOrMeasureOptions, endMark); } catch (error) { // Sometimes there might be no start mark recorded and the measure will fail with an error - console.debug(error.message); + if (error instanceof Error) { + console.debug(error.message); + } } }; /** * Measures the TTI time. To be called when the app is considered to be interactive. - * @param {String} [endMark] Optional end mark name */ - Performance.measureTTI = (endMark) => { + Performance.measureTTI = (endMark: string) => { // Make sure TTI is captured when the app is really usable InteractionManager.runAfterInteractions(() => { requestAnimationFrame(() => { @@ -88,8 +127,8 @@ if (Metrics.canCapturePerformanceMetrics()) { performanceReported.setupDefaultFlipperReporter(); // Monitor some native marks that we want to put on the timeline - new perfModule.PerformanceObserver((list, observer) => { - list.getEntries().forEach((entry) => { + new perfModule.PerformanceObserver((list: PerformanceObserverEntryList, observer: PerformanceObserver) => { + list.getEntries().forEach((entry: PerformanceEntry) => { if (entry.name === 'nativeLaunchEnd') { Performance.measureFailSafe('nativeLaunch', 'nativeLaunchStart', 'nativeLaunchEnd'); } @@ -108,8 +147,8 @@ if (Metrics.canCapturePerformanceMetrics()) { }).observe({type: 'react-native-mark', buffered: true}); // Monitor for "_end" marks and capture "_start" to "_end" measures - new perfModule.PerformanceObserver((list) => { - list.getEntriesByType('mark').forEach((mark) => { + new perfModule.PerformanceObserver((list: PerformanceObserverEntryList) => { + list.getEntriesByType('mark').forEach((mark: PerformanceEntry) => { if (mark.name.endsWith('_end')) { const end = mark.name; const name = end.replace(/_end$/, ''); @@ -125,65 +164,64 @@ if (Metrics.canCapturePerformanceMetrics()) { }).observe({type: 'mark', buffered: true}); }; - Performance.getPerformanceMetrics = () => - _.chain([ + Performance.getPerformanceMetrics = (): PerformanceEntry[] => + [ ...rnPerformance.getEntriesByName('nativeLaunch'), ...rnPerformance.getEntriesByName('runJsBundle'), ...rnPerformance.getEntriesByName('jsBundleDownload'), ...rnPerformance.getEntriesByName('TTI'), ...rnPerformance.getEntriesByName('regularAppStart'), ...rnPerformance.getEntriesByName('appStartedToReady'), - ]) - .filter((entry) => entry.duration > 0) - .value(); + ].filter((entry) => entry.duration > 0); /** * Outputs performance stats. We alert these so that they are easy to access in release builds. */ Performance.printPerformanceMetrics = () => { const stats = Performance.getPerformanceMetrics(); - const statsAsText = _.map(stats, (entry) => `\u2022 ${entry.name}: ${entry.duration.toFixed(1)}ms`).join('\n'); + const statsAsText = stats.map((entry) => `\u2022 ${entry.name}: ${entry.duration.toFixed(1)}ms`).join('\n'); if (stats.length > 0) { Alert.alert('Performance', statsAsText); } }; - Performance.subscribeToMeasurements = (callback) => { - new perfModule.PerformanceObserver((list) => { + Performance.subscribeToMeasurements = (callback: PerformanceEntriesCallback) => { + new perfModule.PerformanceObserver((list: PerformanceObserverEntryList) => { list.getEntriesByType('measure').forEach(callback); }).observe({type: 'measure', buffered: true}); }; /** * Add a start mark to the performance entries - * @param {string} name - * @param {Object} [detail] - * @returns {PerformanceMark} */ - Performance.markStart = (name, detail) => rnPerformance.mark(`${name}_start`, {detail}); + Performance.markStart = (name: string, detail?: Record): PerformanceMark => rnPerformance.mark(`${name}_start`, {detail}); /** * Add an end mark to the performance entries * A measure between start and end is captured automatically - * @param {string} name - * @param {Object} [detail] - * @returns {PerformanceMark} */ - Performance.markEnd = (name, detail) => rnPerformance.mark(`${name}_end`, {detail}); + Performance.markEnd = (name: string, detail?: Record): PerformanceMark => rnPerformance.mark(`${name}_end`, {detail}); /** * Put data emitted by Profiler components on the timeline - * @param {string} id the "id" prop of the Profiler tree that has just committed - * @param {'mount'|'update'} phase either "mount" (if the tree just mounted) or "update" (if it re-rendered) - * @param {number} actualDuration time spent rendering the committed update - * @param {number} baseDuration estimated time to render the entire subtree without memoization - * @param {number} startTime when React began rendering this update - * @param {number} commitTime when React committed this update - * @param {Set} interactions the Set of interactions belonging to this update - * @returns {PerformanceMeasure} + * @param id the "id" prop of the Profiler tree that has just committed + * @param phase either "mount" (if the tree just mounted) or "update" (if it re-rendered) + * @param actualDuration time spent rendering the committed update + * @param baseDuration estimated time to render the entire subtree without memoization + * @param startTime when React began rendering this update + * @param commitTime when React committed this update + * @param interactions the Set of interactions belonging to this update */ - Performance.traceRender = (id, phase, actualDuration, baseDuration, startTime, commitTime, interactions) => + Performance.traceRender = ( + id: string, + phase: Phase, + actualDuration: number, + baseDuration: number, + startTime: number, + commitTime: number, + interactions: Set, + ): PerformanceMeasure => rnPerformance.measure(id, { start: startTime, duration: actualDuration, @@ -197,14 +235,12 @@ if (Metrics.canCapturePerformanceMetrics()) { /** * A HOC that captures render timings of the Wrapped component - * @param {object} config - * @param {string} config.id - * @returns {function(React.Component): React.FunctionComponent} */ Performance.withRenderTrace = - ({id}) => - (WrappedComponent) => { - const WithRenderTrace = forwardRef((props, ref) => ( + ({id}: WrappedComponentConfig) => + // eslint-disable-next-line @typescript-eslint/naming-convention +

>(WrappedComponent: React.ComponentType

): React.ComponentType

> => { + const WithRenderTrace: React.ComponentType

> = forwardRef((props: P, ref) => ( )); - WithRenderTrace.displayName = `withRenderTrace(${getComponentDisplayName(WrappedComponent)})`; + WithRenderTrace.displayName = `withRenderTrace(${getComponentDisplayName(WrappedComponent as React.ComponentType)})`; return WithRenderTrace; }; } diff --git a/src/libs/PolicyUtils.js b/src/libs/PolicyUtils.js index 347a825f59cc..6bbae72f1d80 100644 --- a/src/libs/PolicyUtils.js +++ b/src/libs/PolicyUtils.js @@ -174,7 +174,7 @@ function getMemberAccountIDsForWorkspace(policyMembers, personalDetails) { if (!personalDetail || !personalDetail.login) { return; } - memberEmailsToAccountIDs[personalDetail.login] = accountID; + memberEmailsToAccountIDs[personalDetail.login] = Number(accountID); }); return memberEmailsToAccountIDs; } diff --git a/src/libs/Pusher/EventType.js b/src/libs/Pusher/EventType.ts similarity index 97% rename from src/libs/Pusher/EventType.js rename to src/libs/Pusher/EventType.ts index 85ccc5e17242..89e8a0ca0260 100644 --- a/src/libs/Pusher/EventType.js +++ b/src/libs/Pusher/EventType.ts @@ -11,4 +11,4 @@ export default { MULTIPLE_EVENT_TYPE: { ONYX_API_UPDATE: 'onyxApiUpdate', }, -}; +} as const; diff --git a/src/libs/Pusher/library/index.js b/src/libs/Pusher/library/index.js deleted file mode 100644 index 12cfae7df02f..000000000000 --- a/src/libs/Pusher/library/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * We use the standard pusher-js module to support pusher on web environments. - * @see: https://github.com/pusher/pusher-js - */ -import Pusher from 'pusher-js/with-encryption'; - -export default Pusher; diff --git a/src/libs/Pusher/library/index.native.js b/src/libs/Pusher/library/index.native.js deleted file mode 100644 index 7b87d0c8bdfb..000000000000 --- a/src/libs/Pusher/library/index.native.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * We use the pusher-js/react-native module to support pusher on native environments. - * @see: https://github.com/pusher/pusher-js - */ -import Pusher from 'pusher-js/react-native'; - -export default Pusher; diff --git a/src/libs/Pusher/library/index.native.ts b/src/libs/Pusher/library/index.native.ts new file mode 100644 index 000000000000..f50834366515 --- /dev/null +++ b/src/libs/Pusher/library/index.native.ts @@ -0,0 +1,10 @@ +/** + * We use the pusher-js/react-native module to support pusher on native environments. + * @see: https://github.com/pusher/pusher-js + */ +import PusherImplementation from 'pusher-js/react-native'; +import Pusher from './types'; + +const PusherNative: Pusher = PusherImplementation; + +export default PusherNative; diff --git a/src/libs/Pusher/library/index.ts b/src/libs/Pusher/library/index.ts new file mode 100644 index 000000000000..6a7104a1d2a5 --- /dev/null +++ b/src/libs/Pusher/library/index.ts @@ -0,0 +1,10 @@ +/** + * We use the standard pusher-js module to support pusher on web environments. + * @see: https://github.com/pusher/pusher-js + */ +import PusherImplementation from 'pusher-js/with-encryption'; +import type Pusher from './types'; + +const PusherWeb: Pusher = PusherImplementation; + +export default PusherWeb; diff --git a/src/libs/Pusher/library/types.ts b/src/libs/Pusher/library/types.ts new file mode 100644 index 000000000000..cc8c70fccdbb --- /dev/null +++ b/src/libs/Pusher/library/types.ts @@ -0,0 +1,10 @@ +import PusherClass from 'pusher-js/with-encryption'; +import {LiteralUnion} from 'type-fest'; + +type Pusher = typeof PusherClass; + +type SocketEventName = LiteralUnion<'error' | 'connected' | 'disconnected' | 'state_change', string>; + +export default Pusher; + +export type {SocketEventName}; diff --git a/src/libs/Pusher/pusher.js b/src/libs/Pusher/pusher.ts similarity index 72% rename from src/libs/Pusher/pusher.js rename to src/libs/Pusher/pusher.ts index 4f2b63d36c0c..dad963e933fe 100644 --- a/src/libs/Pusher/pusher.js +++ b/src/libs/Pusher/pusher.ts @@ -1,9 +1,48 @@ import Onyx from 'react-native-onyx'; -import _ from 'underscore'; +import {Channel, ChannelAuthorizerGenerator, Options} from 'pusher-js/with-encryption'; +import isObject from 'lodash/isObject'; +import {LiteralUnion, ValueOf} from 'type-fest'; import ONYXKEYS from '../../ONYXKEYS'; import Pusher from './library'; import TYPE from './EventType'; import Log from '../Log'; +import DeepValueOf from '../../types/utils/DeepValueOf'; +import {SocketEventName} from './library/types'; +import CONST from '../../CONST'; +import {OnyxUpdateEvent, OnyxUpdatesFromServer} from '../../types/onyx'; + +type States = { + previous: string; + current: string; +}; + +type Args = { + appKey: string; + cluster: string; + authEndpoint: string; +}; + +type PushJSON = OnyxUpdateEvent[] | OnyxUpdatesFromServer; + +type EventCallbackError = {type: ValueOf; data: {code: number}}; + +type ChunkedDataEvents = {chunks: unknown[]; receivedFinal: boolean}; + +type EventData = {id?: string; chunk?: unknown; final?: boolean; index: number}; + +type SocketEventCallback = (eventName: SocketEventName, data?: States | EventCallbackError) => void; + +type PusherWithAuthParams = InstanceType & { + config: { + auth?: { + params?: unknown; + }; + }; +}; + +type PusherEventName = LiteralUnion, string>; + +type PusherSubscribtionErrorData = {type?: string; error?: string; status?: string}; let shouldForceOffline = false; Onyx.connect({ @@ -16,33 +55,23 @@ Onyx.connect({ }, }); -let socket; +let socket: PusherWithAuthParams | null; let pusherSocketID = ''; -const socketEventCallbacks = []; -let customAuthorizer; +const socketEventCallbacks: SocketEventCallback[] = []; +let customAuthorizer: ChannelAuthorizerGenerator; /** * Trigger each of the socket event callbacks with the event information - * - * @param {String} eventName - * @param {*} data */ -function callSocketEventCallbacks(eventName, data) { - _.each(socketEventCallbacks, (cb) => cb(eventName, data)); +function callSocketEventCallbacks(eventName: SocketEventName, data?: EventCallbackError | States) { + socketEventCallbacks.forEach((cb) => cb(eventName, data)); } /** * Initialize our pusher lib - * - * @param {Object} args - * @param {String} args.appKey - * @param {String} args.cluster - * @param {String} args.authEndpoint - * @param {Object} [params] - * @public - * @returns {Promise} resolves when Pusher has connected + * @returns resolves when Pusher has connected */ -function init(args, params) { +function init(args: Args, params?: unknown): Promise { return new Promise((resolve) => { if (socket) { return resolve(); @@ -55,7 +84,7 @@ function init(args, params) { // } // }; - const options = { + const options: Options = { cluster: args.cluster, authEndpoint: args.authEndpoint, }; @@ -65,7 +94,6 @@ function init(args, params) { } socket = new Pusher(args.appKey, options); - // If we want to pass params in our requests to api.php we'll need to add it to socket.config.auth.params // as per the documentation // (https://pusher.com/docs/channels/using_channels/connection#channels-options-parameter). @@ -77,21 +105,21 @@ function init(args, params) { } // Listen for connection errors and log them - socket.connection.bind('error', (error) => { + socket?.connection.bind('error', (error: EventCallbackError) => { callSocketEventCallbacks('error', error); }); - socket.connection.bind('connected', () => { - pusherSocketID = socket.connection.socket_id; + socket?.connection.bind('connected', () => { + pusherSocketID = socket?.connection.socket_id ?? ''; callSocketEventCallbacks('connected'); resolve(); }); - socket.connection.bind('disconnected', () => { + socket?.connection.bind('disconnected', () => { callSocketEventCallbacks('disconnected'); }); - socket.connection.bind('state_change', (states) => { + socket?.connection.bind('state_change', (states: States) => { callSocketEventCallbacks('state_change', states); }); }); @@ -99,12 +127,8 @@ function init(args, params) { /** * Returns a Pusher channel for a channel name - * - * @param {String} channelName - * - * @returns {Channel} */ -function getChannel(channelName) { +function getChannel(channelName: string): Channel | undefined { if (!socket) { return; } @@ -114,19 +138,14 @@ function getChannel(channelName) { /** * Binds an event callback to a channel + eventName - * @param {Pusher.Channel} channel - * @param {String} eventName - * @param {Function} [eventCallback] - * - * @private */ -function bindEventToChannel(channel, eventName, eventCallback = () => {}) { +function bindEventToChannel(channel: Channel | undefined, eventName: PusherEventName, eventCallback: (data: PushJSON) => void = () => {}) { if (!eventName) { return; } - const chunkedDataEvents = {}; - const callback = (eventData) => { + const chunkedDataEvents: Record = {}; + const callback = (eventData: string | Record | EventData) => { if (shouldForceOffline) { Log.info('[Pusher] Ignoring a Push event because shouldForceOffline = true'); return; @@ -134,7 +153,7 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}) { let data; try { - data = _.isObject(eventData) ? eventData : JSON.parse(eventData); + data = isObject(eventData) ? eventData : JSON.parse(eventData); } catch (err) { Log.alert('[Pusher] Unable to parse single JSON event data from Pusher', {error: err, eventData}); return; @@ -164,7 +183,7 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}) { // Only call the event callback if we've received the last packet and we don't have any holes in the complete // packet. - if (chunkedEvent.receivedFinal && chunkedEvent.chunks.length === _.keys(chunkedEvent.chunks).length) { + if (chunkedEvent.receivedFinal && chunkedEvent.chunks.length === Object.keys(chunkedEvent.chunks).length) { try { eventCallback(JSON.parse(chunkedEvent.chunks.join(''))); } catch (err) { @@ -181,22 +200,14 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}) { } }; - channel.bind(eventName, callback); + channel?.bind(eventName, callback); } /** * Subscribe to a channel and an event - * - * @param {String} channelName - * @param {String} eventName - * @param {Function} [eventCallback] - * @param {Function} [onResubscribe] Callback to be called when reconnection happen - * - * @return {Promise} - * - * @public + * @param [onResubscribe] Callback to be called when reconnection happen */ -function subscribe(channelName, eventName, eventCallback = () => {}, onResubscribe = () => {}) { +function subscribe(channelName: string, eventName: PusherEventName, eventCallback: (data: PushJSON) => void = () => {}, onResubscribe = () => {}): Promise { return new Promise((resolve, reject) => { // We cannot call subscribe() before init(). Prevent any attempt to do this on dev. if (!socket) { @@ -226,7 +237,7 @@ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscri onResubscribe(); }); - channel.bind('pusher:subscription_error', (data = {}) => { + channel.bind('pusher:subscription_error', (data: PusherSubscribtionErrorData = {}) => { const {type, error, status} = data; Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', { channelName, @@ -245,12 +256,8 @@ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscri /** * Unsubscribe from a channel and optionally a specific event - * - * @param {String} channelName - * @param {String} [eventName] - * @public */ -function unsubscribe(channelName, eventName = '') { +function unsubscribe(channelName: string, eventName: PusherEventName = '') { const channel = getChannel(channelName); if (!channel) { @@ -269,18 +276,14 @@ function unsubscribe(channelName, eventName = '') { Log.info('[Pusher] Unsubscribing from channel', false, {channelName}); channel.unbind(); - socket.unsubscribe(channelName); + socket?.unsubscribe(channelName); } } /** * Are we already in the process of subscribing to this channel? - * - * @param {String} channelName - * - * @returns {Boolean} */ -function isAlreadySubscribing(channelName) { +function isAlreadySubscribing(channelName: string): boolean { if (!socket) { return false; } @@ -291,12 +294,8 @@ function isAlreadySubscribing(channelName) { /** * Are we already subscribed to this channel? - * - * @param {String} channelName - * - * @returns {Boolean} */ -function isSubscribed(channelName) { +function isSubscribed(channelName: string): boolean { if (!socket) { return false; } @@ -307,12 +306,8 @@ function isSubscribed(channelName) { /** * Sends an event over a specific event/channel in pusher. - * - * @param {String} channelName - * @param {String} eventName - * @param {Object} payload */ -function sendEvent(channelName, eventName, payload) { +function sendEvent(channelName: string, eventName: PusherEventName, payload: Record) { // Check to see if we are subscribed to this channel before sending the event. Sending client events over channels // we are not subscribed too will throw errors and cause reconnection attempts. Subscriptions are not instant and // can happen later than we expect. @@ -325,15 +320,13 @@ function sendEvent(channelName, eventName, payload) { return; } - socket.send_event(eventName, payload, channelName); + socket?.send_event(eventName, payload, channelName); } /** * Register a method that will be triggered when a socket event happens (like disconnecting) - * - * @param {Function} cb */ -function registerSocketEventCallback(cb) { +function registerSocketEventCallback(cb: SocketEventCallback) { socketEventCallbacks.push(cb); } @@ -341,10 +334,8 @@ function registerSocketEventCallback(cb) { * A custom authorizer allows us to take a more fine-grained approach to * authenticating Pusher. e.g. we can handle failed attempts to authorize * with an expired authToken and retry the attempt. - * - * @param {Function} authorizer */ -function registerCustomAuthorizer(authorizer) { +function registerCustomAuthorizer(authorizer: ChannelAuthorizerGenerator) { customAuthorizer = authorizer; } @@ -376,18 +367,13 @@ function reconnect() { socket.connect(); } -/** - * @returns {String} - */ -function getPusherSocketID() { +function getPusherSocketID(): string { return pusherSocketID; } if (window) { /** * Pusher socket for debugging purposes - * - * @returns {Function} */ window.getPusherInstance = () => socket; } @@ -407,3 +393,5 @@ export { TYPE, getPusherSocketID, }; + +export type {EventCallbackError, States, PushJSON}; diff --git a/src/libs/PusherConnectionManager.ts b/src/libs/PusherConnectionManager.ts index 4ab08d6dc760..9b1f6ebe1b2a 100644 --- a/src/libs/PusherConnectionManager.ts +++ b/src/libs/PusherConnectionManager.ts @@ -1,11 +1,10 @@ -import {ValueOf} from 'type-fest'; +import {ChannelAuthorizationCallback} from 'pusher-js/with-encryption'; import * as Pusher from './Pusher/pusher'; import * as Session from './actions/Session'; import Log from './Log'; import CONST from '../CONST'; - -type EventCallbackError = {type: ValueOf; data: {code: number}}; -type CustomAuthorizerChannel = {name: string}; +import {SocketEventName} from './Pusher/library/types'; +import {EventCallbackError, States} from './Pusher/pusher'; function init() { /** @@ -14,30 +13,32 @@ function init() { * current valid token to generate the signed auth response * needed to subscribe to Pusher channels. */ - Pusher.registerCustomAuthorizer((channel: CustomAuthorizerChannel) => ({ - authorize: (socketID: string, callback: () => void) => { - Session.authenticatePusher(socketID, channel.name, callback); + Pusher.registerCustomAuthorizer((channel) => ({ + authorize: (socketId: string, callback: ChannelAuthorizationCallback) => { + Session.authenticatePusher(socketId, channel.name, callback); }, })); - Pusher.registerSocketEventCallback((eventName: string, error: EventCallbackError) => { + Pusher.registerSocketEventCallback((eventName: SocketEventName, error?: EventCallbackError | States) => { switch (eventName) { case 'error': { - const errorType = error?.type; - const code = error?.data?.code; - if (errorType === CONST.ERROR.PUSHER_ERROR && code === 1006) { - // 1006 code happens when a websocket connection is closed. There may or may not be a reason attached indicating why the connection was closed. - // https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5 - Log.hmmm('[PusherConnectionManager] Channels Error 1006', {error}); - } else if (errorType === CONST.ERROR.PUSHER_ERROR && code === 4201) { - // This means the connection was closed because Pusher did not receive a reply from the client when it pinged them for a response - // https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol/#4200-4299 - Log.hmmm('[PusherConnectionManager] Pong reply not received', {error}); - } else if (errorType === CONST.ERROR.WEB_SOCKET_ERROR) { - // It's not clear why some errors are wrapped in a WebSocketError type - this error could mean different things depending on the contents. - Log.hmmm('[PusherConnectionManager] WebSocketError', {error}); - } else { - Log.alert(`${CONST.ERROR.ENSURE_BUGBOT} [PusherConnectionManager] Unknown error event`, {error}); + if (error && 'type' in error) { + const errorType = error?.type; + const code = error?.data?.code; + if (errorType === CONST.ERROR.PUSHER_ERROR && code === 1006) { + // 1006 code happens when a websocket connection is closed. There may or may not be a reason attached indicating why the connection was closed. + // https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5 + Log.hmmm('[PusherConnectionManager] Channels Error 1006', {error}); + } else if (errorType === CONST.ERROR.PUSHER_ERROR && code === 4201) { + // This means the connection was closed because Pusher did not receive a reply from the client when it pinged them for a response + // https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol/#4200-4299 + Log.hmmm('[PusherConnectionManager] Pong reply not received', {error}); + } else if (errorType === CONST.ERROR.WEB_SOCKET_ERROR) { + // It's not clear why some errors are wrapped in a WebSocketError type - this error could mean different things depending on the contents. + Log.hmmm('[PusherConnectionManager] WebSocketError', {error}); + } else { + Log.alert(`${CONST.ERROR.ENSURE_BUGBOT} [PusherConnectionManager] Unknown error event`, {error}); + } } break; } diff --git a/src/libs/PusherUtils.ts b/src/libs/PusherUtils.ts index 5baa4b68d5f8..d47283f21bbf 100644 --- a/src/libs/PusherUtils.ts +++ b/src/libs/PusherUtils.ts @@ -4,9 +4,7 @@ import Log from './Log'; import NetworkConnection from './NetworkConnection'; import * as Pusher from './Pusher/pusher'; import CONST from '../CONST'; -import {OnyxUpdateEvent, OnyxUpdatesFromServer} from '../types/onyx'; - -type PushJSON = OnyxUpdateEvent[] | OnyxUpdatesFromServer; +import {PushJSON} from './Pusher/pusher'; type Callback = (data: OnyxUpdate[]) => Promise; diff --git a/src/libs/ReportActionComposeFocusManager.ts b/src/libs/ReportActionComposeFocusManager.ts index ca4f9d77898b..65466fa4a204 100644 --- a/src/libs/ReportActionComposeFocusManager.ts +++ b/src/libs/ReportActionComposeFocusManager.ts @@ -1,5 +1,7 @@ import React from 'react'; import {TextInput} from 'react-native'; +import ROUTES from '../ROUTES'; +import Navigation from './Navigation/Navigation'; type FocusCallback = () => void; @@ -28,6 +30,11 @@ function onComposerFocus(callback: FocusCallback, isMainComposer = false) { * Request focus on the ReportActionComposer */ function focus() { + /** Do not trigger the refocusing when the active route is not the report route, */ + if (!Navigation.isActiveRoute(ROUTES.REPORT_WITH_ID.getRoute(Navigation.getTopmostReportId() ?? ''))) { + return; + } + if (typeof focusCallback !== 'function') { if (typeof mainComposerFocusCallback !== 'function') { return; diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 258582d9f653..fa1883dd9b98 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -68,6 +68,14 @@ function isDeletedParentAction(reportAction) { return lodashGet(reportAction, ['message', 0, 'isDeletedParentAction'], false) && lodashGet(reportAction, 'childVisibleActionCount', 0) > 0; } +/** + * @param {Object} reportAction + * @returns {Boolean} + */ +function isReversedTransaction(reportAction) { + return lodashGet(reportAction, ['message', 0, 'isReversedTransaction'], false) && lodashGet(reportAction, 'childVisibleActionCount', 0) > 0; +} + /** * @param {Object} reportAction * @returns {Boolean} @@ -352,7 +360,7 @@ function shouldReportActionBeVisible(reportAction, key) { // All other actions are displayed except thread parents, deleted, or non-pending actions const isDeleted = isDeletedAction(reportAction); const isPending = !!reportAction.pendingAction; - return !isDeleted || isPending || isDeletedParentAction(reportAction); + return !isDeleted || isPending || isDeletedParentAction(reportAction) || isReversedTransaction(reportAction); } /** @@ -673,6 +681,7 @@ export { isTransactionThread, isSentMoneyReportAction, isDeletedParentAction, + isReversedTransaction, isReportPreviewAction, isModifiedExpenseAction, getIOUReportIDFromReportActionPreview, diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index c0b8b5620bfa..0b7bbfd61461 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1267,6 +1267,17 @@ function isWaitingForTaskCompleteFromAssignee(report, parentReportAction = {}) { return isTaskReport(report) && isReportManager(report) && isOpenTaskReport(report, parentReportAction); } +/** + * Returns number of transactions that are nonReimbursable + * + * @param {Object|null} iouReportID + * @returns {Number} + */ +function hasNonReimbursableTransactions(iouReportID) { + const allTransactions = TransactionUtils.getAllReportTransactions(iouReportID); + return _.filter(allTransactions, (transaction) => transaction.reimbursable === false).length > 0; +} + /** * @param {Object} report * @param {Object} allReportsDict @@ -1344,6 +1355,10 @@ function getMoneyRequestReportName(report, policy = undefined) { return `${payerPaidAmountMesssage} • ${Localize.translateLocal('iou.pending')}`; } + if (hasNonReimbursableTransactions(report.reportID)) { + return Localize.translateLocal('iou.payerSpentAmount', {payer: payerName, amount: formattedAmount}); + } + if (report.hasOutstandingIOU) { return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName, amount: formattedAmount}); } @@ -1373,6 +1388,8 @@ function getTransactionDetails(transaction, createdDateFormat = CONST.DATE.FNS_F tag: TransactionUtils.getTag(transaction), mccGroup: TransactionUtils.getMCCGroup(transaction), cardID: TransactionUtils.getCardID(transaction), + originalAmount: TransactionUtils.getOriginalAmount(transaction), + originalCurrency: TransactionUtils.getOriginalCurrency(transaction), }; } @@ -1477,7 +1494,11 @@ function hasMissingSmartscanFields(iouReportID) { * @returns {String} */ function getTransactionReportName(reportAction) { - if (ReportActionsUtils.isDeletedParentAction(reportAction)) { + if (ReportActionsUtils.isReversedTransaction(reportAction)) { + return Localize.translateLocal('parentReportAction.reversedTransaction'); + } + + if (ReportActionsUtils.isDeletedAction(reportAction)) { return Localize.translateLocal('parentReportAction.deletedRequest'); } @@ -1515,6 +1536,20 @@ function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceip return reportActionMessage; } + if (!isIOUReport(report) && ReportActionsUtils.isSplitBillAction(reportAction)) { + // This covers group chats where the last action is a split bill action + const linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); + if (_.isEmpty(linkedTransaction)) { + return reportActionMessage; + } + if (TransactionUtils.isReceiptBeingScanned(linkedTransaction)) { + return Localize.translateLocal('iou.receiptScanning'); + } + const {amount, currency, comment} = getTransactionDetails(linkedTransaction); + const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); + return Localize.translateLocal('iou.didSplitAmount', {formattedAmount, comment}); + } + const totalAmount = getMoneyRequestTotal(report); const payerName = isExpenseReport(report) ? getPolicyName(report) : getDisplayNameForParticipant(report.managerID, true); const formattedAmount = CurrencyUtils.convertToDisplayString(totalAmount, report.currency); @@ -1548,7 +1583,8 @@ function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceip return Localize.translateLocal('iou.waitingOnBankAccount', {submitterDisplayName}); } - return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName, amount: formattedAmount}); + const containsNonReimbursable = hasNonReimbursableTransactions(report.reportID); + return Localize.translateLocal(containsNonReimbursable ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', {payer: payerName, amount: formattedAmount}); } /** @@ -2121,6 +2157,7 @@ function buildOptimisticIOUReport(payeeAccountID, payerAccountID, total, chatRep reportID: generateReportID(), state: CONST.REPORT.STATE.SUBMITTED, stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.PROCESSING, + statusNum: isSendingMoney ? CONST.REPORT.STATUS.REIMBURSED : CONST.REPORT.STATE_NUM.PROCESSING, total, // We don't translate reportName because the server response is always in English @@ -2576,6 +2613,7 @@ function buildOptimisticTaskReportAction(taskReportID, actionName, message = '') * @param {String} notificationPreference * @param {String} parentReportActionID * @param {String} parentReportID + * @param {String} welcomeMessage * @returns {Object} */ function buildOptimisticChatReport( @@ -2591,6 +2629,7 @@ function buildOptimisticChatReport( notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, parentReportActionID = '', parentReportID = '', + welcomeMessage = '', ) { const currentTime = DateUtils.getDBTime(); return { @@ -2617,7 +2656,7 @@ function buildOptimisticChatReport( stateNum: 0, statusNum: 0, visibility, - welcomeMessage: '', + welcomeMessage, writeCapability, }; } @@ -2832,6 +2871,7 @@ function buildOptimisticTaskReport(ownerAccountID, assigneeAccountID = 0, parent policyID, stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS.OPEN, + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, }; } @@ -3395,8 +3435,12 @@ function getMoneyRequestOptions(report, reportParticipants) { // User created policy rooms and default rooms like #admins or #announce will always have the Split Bill option // unless there are no participants at all (e.g. #admins room for a policy with only 1 admin) // DM chats will have the Split Bill option only when there are at least 3 people in the chat. - // There is no Split Bill option for Workspace chats, IOU or Expense reports which are threads - if ((isChatRoom(report) && participants.length > 0) || (hasMultipleParticipants && !isPolicyExpenseChat(report) && !isMoneyRequestReport(report)) || isControlPolicyExpenseChat(report)) { + // There is no Split Bill option for IOU or Expense reports which are threads + if ( + (isChatRoom(report) && participants.length > 0) || + (hasMultipleParticipants && !isPolicyExpenseChat(report) && !isMoneyRequestReport(report)) || + (isControlPolicyExpenseChat(report) && report.isOwnPolicyExpenseChat) + ) { return [CONST.IOU.MONEY_REQUEST_TYPE.SPLIT]; } @@ -3552,7 +3596,8 @@ function shouldDisableWriteActions(report) { * @returns {String} */ function getOriginalReportID(reportID, reportAction) { - return isThreadFirstChat(reportAction, reportID) ? lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, 'parentReportID']) : reportID; + const currentReportAction = ReportActionsUtils.getReportAction(reportID, reportAction.reportActionID); + return isThreadFirstChat(reportAction, reportID) && _.isEmpty(currentReportAction) ? lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, 'parentReportID']) : reportID; } /** @@ -3967,6 +4012,7 @@ export { areAllRequestsBeingSmartScanned, getReportPreviewDisplayTransactions, getTransactionsWithReceipts, + hasNonReimbursableTransactions, hasMissingSmartscanFields, getIOUReportActionDisplayMessage, isWaitingForTaskCompleteFromAssignee, diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index 7a32db660021..314a1d63760e 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -347,17 +347,17 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, if ((result.isChatRoom || result.isPolicyExpenseChat || result.isThread || result.isTaskReport) && !result.isArchivedRoom) { const lastAction = visibleReportActionItems[report.reportID]; - if (lodashGet(lastAction, 'actionName', '') === CONST.REPORT.ACTIONS.TYPE.RENAMED) { + if (lastAction && lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED) { const newName = lodashGet(lastAction, 'originalMessage.newName', ''); result.alternateText = Localize.translate(preferredLocale, 'newRoomPage.roomRenamedTo', {newName}); - } else if (lodashGet(lastAction, 'actionName', '') === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED) { + } else if (lastAction && lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED) { result.alternateText = `${Localize.translate(preferredLocale, 'task.messages.reopened')}`; - } else if (lodashGet(lastAction, 'actionName', '') === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED) { + } else if (lastAction && lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED) { result.alternateText = `${Localize.translate(preferredLocale, 'task.messages.completed')}`; - } else if (lodashGet(lastAction, 'actionName', '') !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) { + } else if (lastAction && lastAction.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) { result.alternateText = `${lastActorDisplayName}: ${lastMessageText}`; } else { - result.alternateText = lastMessageTextFromReport.length > 0 ? lastMessageText : Localize.translate(preferredLocale, 'report.noActivityYet'); + result.alternateText = lastAction && lastMessageTextFromReport.length > 0 ? lastMessageText : Localize.translate(preferredLocale, 'report.noActivityYet'); } } else { if (!lastMessageText) { diff --git a/src/libs/SuggestionUtils.js b/src/libs/SuggestionUtils.js index aa2640d006c8..9c3e92799334 100644 --- a/src/libs/SuggestionUtils.js +++ b/src/libs/SuggestionUtils.js @@ -26,4 +26,22 @@ function trimLeadingSpace(str) { return str.slice(0, 1) === ' ' ? str.slice(1) : str; } -export {getMaxArrowIndex, trimLeadingSpace}; +/** + * Checks if space is available to render large suggestion menu + * @param {Number} listHeight + * @param {Number} composerHeight + * @param {Number} totalSuggestions + * @returns {Boolean} + */ +function hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, totalSuggestions) { + const maxSuggestions = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER; + const chatFooterHeight = CONST.CHAT_FOOTER_SECONDARY_ROW_HEIGHT + 2 * CONST.CHAT_FOOTER_SECONDARY_ROW_PADDING; + const availableHeight = listHeight - composerHeight - chatFooterHeight; + const menuHeight = + (!totalSuggestions || totalSuggestions > maxSuggestions ? maxSuggestions : totalSuggestions) * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT + + CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING * 2; + + return availableHeight > menuHeight; +} + +export {getMaxArrowIndex, trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu}; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 2b7729abc442..77fc4f04f99d 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -59,16 +59,13 @@ function buildOptimisticTransaction( commentJSON.originalTransactionID = originalTransactionID; } - // For the SmartScan to run successfully, we need to pass the merchant field empty to the API - const defaultMerchant = !receipt || Object.keys(receipt).length === 0 ? CONST.TRANSACTION.DEFAULT_MERCHANT : ''; - return { transactionID, amount, currency, reportID, comment: commentJSON, - merchant: merchant || defaultMerchant, + merchant: merchant || CONST.TRANSACTION.DEFAULT_MERCHANT, created: created || DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, receipt, @@ -83,21 +80,35 @@ function hasReceipt(transaction: Transaction | undefined | null): boolean { return !!transaction?.receipt?.state; } -function areRequiredFieldsEmpty(transaction: Transaction): boolean { - return ( +function isMerchantMissing(transaction: Transaction) { + const isMerchantEmpty = + transaction.merchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transaction.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction.merchant === ''; + + const isModifiedMerchantEmpty = + !transaction.modifiedMerchant || transaction.modifiedMerchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transaction.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || - (transaction.modifiedMerchant === '' && - (transaction.merchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transaction.merchant === '' || transaction.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT)) || - (transaction.modifiedAmount === 0 && transaction.amount === 0) || - (transaction.modifiedCreated === '' && transaction.created === '') - ); + transaction.modifiedMerchant === ''; + + return isMerchantEmpty && isModifiedMerchantEmpty; +} + +function isAmountMissing(transaction: Transaction) { + return transaction.amount === 0 && (!transaction.modifiedAmount || transaction.modifiedAmount === 0); +} + +function isCreatedMissing(transaction: Transaction) { + return transaction.created === '' && (!transaction.created || transaction.modifiedCreated === ''); +} + +function areRequiredFieldsEmpty(transaction: Transaction): boolean { + return isMerchantMissing(transaction) || isAmountMissing(transaction) || isCreatedMissing(transaction); } /** * Given the edit made to the money request, return an updated transaction object. */ -function getUpdatedTransaction(transaction: Transaction, transactionChanges: TransactionChanges, isFromExpenseReport: boolean): Transaction { +function getUpdatedTransaction(transaction: Transaction, transactionChanges: TransactionChanges, isFromExpenseReport: boolean, shouldUpdateReceiptState = true): Transaction { // Only changing the first level fields so no need for deep clone now const updatedTransaction = {...transaction}; let shouldStopSmartscan = false; @@ -144,7 +155,13 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra updatedTransaction.tag = transactionChanges.tag; } - if (shouldStopSmartscan && transaction?.receipt && Object.keys(transaction.receipt).length > 0 && transaction?.receipt?.state !== CONST.IOU.RECEIPT_STATE.OPEN) { + if ( + shouldUpdateReceiptState && + shouldStopSmartscan && + transaction?.receipt && + Object.keys(transaction.receipt).length > 0 && + transaction?.receipt?.state !== CONST.IOU.RECEIPT_STATE.OPEN + ) { updatedTransaction.receipt.state = CONST.IOU.RECEIPT_STATE.OPEN; } @@ -217,6 +234,21 @@ function getCurrency(transaction: Transaction): string { return transaction?.currency ?? CONST.CURRENCY.USD; } +/** + * Return the original currency field from the transaction. + */ +function getOriginalCurrency(transaction: Transaction): string { + return transaction?.originalCurrency ?? ''; +} + +/** + * Return the absolute value of the original amount field from the transaction. + */ +function getOriginalAmount(transaction: Transaction): number { + const amount = transaction?.originalAmount ?? 0; + return Math.abs(amount); +} + /** * Return the merchant field from the transaction, return the modifiedMerchant if present. */ @@ -285,6 +317,9 @@ function isDistanceRequest(transaction: Transaction): boolean { return type === CONST.TRANSACTION.TYPE.CUSTOM_UNIT && customUnitName === CONST.CUSTOM_UNITS.NAME_DISTANCE; } +/** + * Determine whether a transaction is made with an Expensify card. + */ function isExpensifyCardTransaction(transaction: Transaction): boolean { if (!transaction.cardID) { return false; @@ -292,6 +327,9 @@ function isExpensifyCardTransaction(transaction: Transaction): boolean { return isExpensifyCard(transaction.cardID); } +/** + * Check if the transaction status is set to Pending. + */ function isPending(transaction: Transaction): boolean { if (!transaction.status) { return false; @@ -299,6 +337,9 @@ function isPending(transaction: Transaction): boolean { return transaction.status === CONST.TRANSACTION.STATUS.PENDING; } +/** + * Check if the transaction status is set to Posted. + */ function isPosted(transaction: Transaction): boolean { if (!transaction.status) { return false; @@ -420,6 +461,8 @@ export { getAmount, getCurrency, getCardID, + getOriginalCurrency, + getOriginalAmount, getMerchant, getMCCGroup, getCreated, @@ -438,6 +481,10 @@ export { isPending, isPosted, getWaypoints, + isAmountMissing, + isMerchantMissing, + isCreatedMissing, + areRequiredFieldsEmpty, hasMissingSmartscanFields, getWaypointIndex, waypointHasValidAddress, diff --git a/src/libs/UnreadIndicatorUpdater/updateUnread/index.website.js b/src/libs/UnreadIndicatorUpdater/updateUnread/index.website.js index 244eaf805d10..4c829239ef14 100644 --- a/src/libs/UnreadIndicatorUpdater/updateUnread/index.website.js +++ b/src/libs/UnreadIndicatorUpdater/updateUnread/index.website.js @@ -3,6 +3,7 @@ */ import CONFIG from '../../../CONFIG'; +let unreadTotalCount = 0; /** * Set the page title on web * @@ -10,7 +11,7 @@ import CONFIG from '../../../CONFIG'; */ function updateUnread(totalCount) { const hasUnread = totalCount !== 0; - + unreadTotalCount = totalCount; // This setTimeout is required because due to how react rendering messes with the DOM, the document title can't be modified synchronously, and we must wait until all JS is done // running before setting the title. setTimeout(() => { @@ -22,4 +23,8 @@ function updateUnread(totalCount) { }, 0); } +window.addEventListener('popstate', () => { + updateUnread(unreadTotalCount); +}); + export default updateUnread; diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 7500af6d829e..2dbc1001f068 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -44,6 +44,19 @@ Onyx.connect({ callback: (val) => (preferredLocale = val), }); +let priorityMode; +Onyx.connect({ + key: ONYXKEYS.NVP_PRIORITY_MODE, + callback: (nextPriorityMode) => { + // When someone switches their priority mode we need to fetch all their chats because only #focus mode works with a subset of a user's chats. This is only possible via the OpenApp command. + if (nextPriorityMode === CONST.PRIORITY_MODE.DEFAULT && priorityMode === CONST.PRIORITY_MODE.GSD) { + // eslint-disable-next-line no-use-before-define + openApp(); + } + priorityMode = nextPriorityMode; + }, +}); + let resolveIsReadyPromise; const isReadyToOpenApp = new Promise((resolve) => { resolveIsReadyPromise = resolve; @@ -207,7 +220,8 @@ function getOnyxDataForOpenOrReconnect(isOpenApp = false) { */ function openApp() { getPolicyParamsForOpenOrReconnect().then((policyParams) => { - API.read('OpenApp', policyParams, getOnyxDataForOpenOrReconnect(true)); + const params = {enablePriorityModeFilter: true, ...policyParams}; + API.read('OpenApp', params, getOnyxDataForOpenOrReconnect(true)); }); } @@ -336,6 +350,40 @@ function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = fal .then(endSignOnTransition); } +/** + * Create a new draft workspace and navigate to it + * + * @param {String} [policyOwnerEmail] Optional, the email of the account to make the owner of the policy + * @param {String} [policyName] Optional, custom policy name we will use for created workspace + * @param {Boolean} [transitionFromOldDot] Optional, if the user is transitioning from old dot + */ +function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', policyName = '', transitionFromOldDot = false) { + const policyID = Policy.generatePolicyID(); + Policy.createDraftInitialWorkspace(policyOwnerEmail, policyName, policyID); + + Navigation.isNavigationReady() + .then(() => { + if (transitionFromOldDot) { + // We must call goBack() to remove the /transition route from history + Navigation.goBack(ROUTES.HOME); + } + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); + }) + .then(endSignOnTransition); +} + +/** + * Create a new workspace and delete the draft + * + * @param {String} [policyID] the ID of the policy to use + * @param {String} [policyName] custom policy name we will use for created workspace + * @param {String} [policyOwnerEmail] Optional, the email of the account to make the owner of the policy + * @param {Boolean} [makeMeAdmin] Optional, leave the calling account as an admin on the policy + */ +function savePolicyDraftByNewWorkspace(policyID, policyName, policyOwnerEmail = '', makeMeAdmin = false) { + Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID); +} + /** * This action runs when the Navigator is ready and the current route changes * @@ -513,4 +561,6 @@ export { createWorkspaceAndNavigateToIt, getMissingOnyxUpdates, finalReconnectAppAfterActivatingReliableUpdates, + savePolicyDraftByNewWorkspace, + createWorkspaceWithPolicyDraftAndNavigateToIt, }; diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js deleted file mode 100644 index b1cb09a8a5e2..000000000000 --- a/src/libs/actions/BankAccounts.js +++ /dev/null @@ -1,441 +0,0 @@ -import Onyx from 'react-native-onyx'; -import CONST from '../../CONST'; -import * as API from '../API'; -import ONYXKEYS from '../../ONYXKEYS'; -import * as ErrorUtils from '../ErrorUtils'; -import * as PlaidDataProps from '../../pages/ReimbursementAccount/plaidDataPropTypes'; -import Navigation from '../Navigation/Navigation'; -import ROUTES from '../../ROUTES'; -import * as ReimbursementAccount from './ReimbursementAccount'; - -export { - goToWithdrawalAccountSetupStep, - setBankAccountFormValidationErrors, - resetReimbursementAccount, - resetFreePlanBankAccount, - hideBankAccountErrors, - setWorkspaceIDForReimbursementAccount, - setBankAccountSubStep, - updateReimbursementAccountDraft, - requestResetFreePlanBankAccount, - cancelResetFreePlanBankAccount, -} from './ReimbursementAccount'; -export {openPlaidBankAccountSelector, openPlaidBankLogin} from './Plaid'; -export {openOnfidoFlow, answerQuestionsForWallet, verifyIdentity, acceptWalletTerms} from './Wallet'; - -function clearPlaid() { - Onyx.set(ONYXKEYS.PLAID_LINK_TOKEN, ''); - - return Onyx.set(ONYXKEYS.PLAID_DATA, PlaidDataProps.plaidDataDefaultProps); -} - -function openPlaidView() { - clearPlaid().then(() => ReimbursementAccount.setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID)); -} - -/** - * Open the personal bank account setup flow, with an optional exitReportID to redirect to once the flow is finished. - * @param {String} exitReportID - */ -function openPersonalBankAccountSetupView(exitReportID) { - clearPlaid().then(() => { - if (exitReportID) { - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {exitReportID}); - } - Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT); - }); -} - -function clearPersonalBankAccount() { - clearPlaid(); - Onyx.set(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {}); -} - -function clearOnfidoToken() { - Onyx.merge(ONYXKEYS.ONFIDO_TOKEN, ''); -} - -/** - * Helper method to build the Onyx data required during setup of a Verified Business Bank Account - * - * @returns {Object} - */ -function getVBBADataForOnyx() { - return { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - isLoading: true, - errors: null, - }, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - isLoading: false, - errors: null, - }, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - isLoading: false, - errors: ErrorUtils.getMicroSecondOnyxError('walletPage.addBankAccountFailure'), - }, - }, - ], - }; -} - -/** - * Submit Bank Account step with Plaid data so php can perform some checks. - * - * @param {Number} bankAccountID - * @param {Object} selectedPlaidBankAccount - */ -function connectBankAccountWithPlaid(bankAccountID, selectedPlaidBankAccount) { - const commandName = 'ConnectBankAccountWithPlaid'; - - const parameters = { - bankAccountID, - routingNumber: selectedPlaidBankAccount.routingNumber, - accountNumber: selectedPlaidBankAccount.accountNumber, - bank: selectedPlaidBankAccount.bankName, - plaidAccountID: selectedPlaidBankAccount.plaidAccountID, - plaidAccessToken: selectedPlaidBankAccount.plaidAccessToken, - }; - - API.write(commandName, parameters, getVBBADataForOnyx()); -} - -/** - * Adds a bank account via Plaid - * - * @param {Object} account - * @TODO offline pattern for this command will have to be added later once the pattern B design doc is complete - */ -function addPersonalBankAccount(account) { - const commandName = 'AddPersonalBankAccount'; - - const parameters = { - addressName: account.addressName, - routingNumber: account.routingNumber, - accountNumber: account.accountNumber, - isSavings: account.isSavings, - setupType: 'plaid', - bank: account.bankName, - plaidAccountID: account.plaidAccountID, - plaidAccessToken: account.plaidAccessToken, - }; - - const onyxData = { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, - value: { - isLoading: true, - errors: null, - plaidAccountID: account.plaidAccountID, - }, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, - value: { - isLoading: false, - errors: null, - shouldShowSuccess: true, - }, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, - value: { - isLoading: false, - errors: ErrorUtils.getMicroSecondOnyxError('walletPage.addBankAccountFailure'), - }, - }, - ], - }; - - API.write(commandName, parameters, onyxData); -} - -function deletePaymentBankAccount(bankAccountID) { - API.write( - 'DeletePaymentBankAccount', - { - bankAccountID, - }, - { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`, - value: {[bankAccountID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}}, - }, - ], - - // Sometimes pusher updates aren't received when we close the App while still offline, - // so we are setting the bankAccount to null here to ensure that it gets cleared out once we come back online. - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`, - value: {[bankAccountID]: null}, - }, - ], - }, - ); -} - -/** - * Update the user's personal information on the bank account in database. - * - * This action is called by the requestor step in the Verified Bank Account flow - * - * @param {Object} params - * - * @param {String} [params.dob] - * @param {String} [params.firstName] - * @param {String} [params.lastName] - * @param {String} [params.requestorAddressStreet] - * @param {String} [params.requestorAddressCity] - * @param {String} [params.requestorAddressState] - * @param {String} [params.requestorAddressZipCode] - * @param {String} [params.ssnLast4] - * @param {String} [params.isControllingOfficer] - * @param {Object} [params.onfidoData] - * @param {Boolean} [params.isOnfidoSetupComplete] - */ -function updatePersonalInformationForBankAccount(params) { - API.write('UpdatePersonalInformationForBankAccount', params, getVBBADataForOnyx()); -} - -/** - * @param {Number} bankAccountID - * @param {String} validateCode - */ -function validateBankAccount(bankAccountID, validateCode) { - API.write( - 'ValidateBankAccountWithTransactions', - { - bankAccountID, - validateCode, - }, - { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - isLoading: true, - errors: null, - }, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - isLoading: false, - }, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - isLoading: false, - }, - }, - ], - }, - ); -} - -function openReimbursementAccountPage(stepToOpen, subStep, localCurrentStep) { - const onyxData = { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - isLoading: true, - }, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - isLoading: false, - }, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - isLoading: false, - }, - }, - ], - }; - - const param = { - stepToOpen, - subStep, - localCurrentStep, - }; - - return API.read('OpenReimbursementAccountPage', param, onyxData); -} - -/** - * Updates the bank account in the database with the company step data - * - * @param {Object} bankAccount - * @param {Number} [bankAccount.bankAccountID] - * - * Fields from BankAccount step - * @param {String} [bankAccount.routingNumber] - * @param {String} [bankAccount.accountNumber] - * @param {String} [bankAccount.bankName] - * @param {String} [bankAccount.plaidAccountID] - * @param {String} [bankAccount.plaidAccessToken] - * @param {Boolean} [bankAccount.isSavings] - * - * Fields from Company step - * @param {String} [bankAccount.companyName] - * @param {String} [bankAccount.addressStreet] - * @param {String} [bankAccount.addressCity] - * @param {String} [bankAccount.addressState] - * @param {String} [bankAccount.addressZipCode] - * @param {String} [bankAccount.companyPhone] - * @param {String} [bankAccount.website] - * @param {String} [bankAccount.companyTaxID] - * @param {String} [bankAccount.incorporationType] - * @param {String} [bankAccount.incorporationState] - * @param {String} [bankAccount.incorporationDate] - * @param {Boolean} [bankAccount.hasNoConnectionToCannabis] - * @param {String} policyID - */ -function updateCompanyInformationForBankAccount(bankAccount, policyID) { - API.write('UpdateCompanyInformationForBankAccount', {...bankAccount, policyID}, getVBBADataForOnyx()); -} - -/** - * Add beneficial owners for the bank account, accept the ACH terms and conditions and verify the accuracy of the information provided - * - * @param {Object} params - * - * // ACH Contract Step - * @param {Boolean} [params.ownsMoreThan25Percent] - * @param {Boolean} [params.hasOtherBeneficialOwners] - * @param {Boolean} [params.acceptTermsAndConditions] - * @param {Boolean} [params.certifyTrueInformation] - * @param {String} [params.beneficialOwners] - */ -function updateBeneficialOwnersForBankAccount(params) { - API.write('UpdateBeneficialOwnersForBankAccount', {...params}, getVBBADataForOnyx()); -} - -/** - * Create the bank account with manually entered data. - * - * @param {number} [bankAccountID] - * @param {String} [accountNumber] - * @param {String} [routingNumber] - * @param {String} [plaidMask] - * - */ -function connectBankAccountManually(bankAccountID, accountNumber, routingNumber, plaidMask) { - API.write( - 'ConnectBankAccountManually', - { - bankAccountID, - accountNumber, - routingNumber, - plaidMask, - }, - getVBBADataForOnyx(), - ); -} - -/** - * Verify the user's identity via Onfido - * - * @param {Number} bankAccountID - * @param {Object} onfidoData - */ -function verifyIdentityForBankAccount(bankAccountID, onfidoData) { - API.write( - 'VerifyIdentityForBankAccount', - { - bankAccountID, - onfidoData: JSON.stringify(onfidoData), - }, - getVBBADataForOnyx(), - ); -} - -function openWorkspaceView() { - API.read('OpenWorkspaceView'); -} - -function handlePlaidError(bankAccountID, error, error_description, plaidRequestID) { - API.write('BankAccount_HandlePlaidError', { - bankAccountID, - error, - error_description, - plaidRequestID, - }); -} - -/** - * Set the reimbursement account loading so that it happens right away, instead of when the API command is processed. - * - * @param {Boolean} isLoading - */ -function setReimbursementAccountLoading(isLoading) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {isLoading}); -} - -export { - addPersonalBankAccount, - clearOnfidoToken, - clearPersonalBankAccount, - clearPlaid, - openPlaidView, - connectBankAccountManually, - connectBankAccountWithPlaid, - deletePaymentBankAccount, - handlePlaidError, - openPersonalBankAccountSetupView, - openReimbursementAccountPage, - updateBeneficialOwnersForBankAccount, - updateCompanyInformationForBankAccount, - updatePersonalInformationForBankAccount, - openWorkspaceView, - validateBankAccount, - verifyIdentityForBankAccount, - setReimbursementAccountLoading, -}; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts new file mode 100644 index 000000000000..249d7de9293a --- /dev/null +++ b/src/libs/actions/BankAccounts.ts @@ -0,0 +1,444 @@ +import Onyx from 'react-native-onyx'; +import CONST from '../../CONST'; +import * as API from '../API'; +import ONYXKEYS from '../../ONYXKEYS'; +import * as ErrorUtils from '../ErrorUtils'; +import * as PlaidDataProps from '../../pages/ReimbursementAccount/plaidDataPropTypes'; +import Navigation from '../Navigation/Navigation'; +import ROUTES from '../../ROUTES'; +import * as ReimbursementAccount from './ReimbursementAccount'; +import type PlaidBankAccount from '../../types/onyx/PlaidBankAccount'; +import type {ACHContractStepProps, BankAccountStepProps, CompanyStepProps, OnfidoData, ReimbursementAccountProps, RequestorStepProps} from '../../types/onyx/ReimbursementAccountDraft'; +import type {OnyxData} from '../../types/onyx/Request'; +import type {BankAccountStep, BankAccountSubStep} from '../../types/onyx/ReimbursementAccount'; + +export { + goToWithdrawalAccountSetupStep, + setBankAccountFormValidationErrors, + resetReimbursementAccount, + resetFreePlanBankAccount, + hideBankAccountErrors, + setWorkspaceIDForReimbursementAccount, + setBankAccountSubStep, + updateReimbursementAccountDraft, + requestResetFreePlanBankAccount, + cancelResetFreePlanBankAccount, +} from './ReimbursementAccount'; +export {openPlaidBankAccountSelector, openPlaidBankLogin} from './Plaid'; +export {openOnfidoFlow, answerQuestionsForWallet, verifyIdentity, acceptWalletTerms} from './Wallet'; + +type BankAccountCompanyInformation = BankAccountStepProps & CompanyStepProps & ReimbursementAccountProps; + +type ReimbursementAccountStep = BankAccountStep | ''; + +type ReimbursementAccountSubStep = BankAccountSubStep | ''; + +function clearPlaid(): Promise { + Onyx.set(ONYXKEYS.PLAID_LINK_TOKEN, ''); + + return Onyx.set(ONYXKEYS.PLAID_DATA, PlaidDataProps.plaidDataDefaultProps); +} + +function openPlaidView() { + clearPlaid().then(() => ReimbursementAccount.setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID)); +} + +/** + * Open the personal bank account setup flow, with an optional exitReportID to redirect to once the flow is finished. + */ +function openPersonalBankAccountSetupView(exitReportID: string) { + clearPlaid().then(() => { + if (exitReportID) { + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {exitReportID}); + } + Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT); + }); +} + +function clearPersonalBankAccount() { + clearPlaid(); + Onyx.set(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {}); +} + +function clearOnfidoToken() { + Onyx.merge(ONYXKEYS.ONFIDO_TOKEN, ''); +} + +/** + * Helper method to build the Onyx data required during setup of a Verified Business Bank Account + */ +function getVBBADataForOnyx(currentStep?: BankAccountStep): OnyxData { + return { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: true, + errors: null, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: false, + errors: null, + // When setting up a bank account, we save the draft form values in Onyx. + // When we update the information for a step, the value of some fields that are returned from the API + // can be different from the value that we stored as the draft in Onyx (i.e. the phone number is formatted). + // This is why we store the current step used to call the API in order to update the corresponding draft data in Onyx. + // If currentStep is undefined that means this step don't need to update the data of the draft in Onyx. + draftStep: currentStep, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: false, + errors: ErrorUtils.getMicroSecondOnyxError('walletPage.addBankAccountFailure'), + }, + }, + ], + }; +} + +/** + * Submit Bank Account step with Plaid data so php can perform some checks. + */ +function connectBankAccountWithPlaid(bankAccountID: number, selectedPlaidBankAccount: PlaidBankAccount) { + const commandName = 'ConnectBankAccountWithPlaid'; + + type ConnectBankAccountWithPlaidParams = { + bankAccountID: number; + routingNumber: string; + accountNumber: string; + bank?: string; + plaidAccountID: string; + plaidAccessToken: string; + }; + + const parameters: ConnectBankAccountWithPlaidParams = { + bankAccountID, + routingNumber: selectedPlaidBankAccount.routingNumber, + accountNumber: selectedPlaidBankAccount.accountNumber, + bank: selectedPlaidBankAccount.bankName, + plaidAccountID: selectedPlaidBankAccount.plaidAccountID, + plaidAccessToken: selectedPlaidBankAccount.plaidAccessToken, + }; + + API.write(commandName, parameters, getVBBADataForOnyx()); +} + +/** + * Adds a bank account via Plaid + * + * @TODO offline pattern for this command will have to be added later once the pattern B design doc is complete + */ +function addPersonalBankAccount(account: PlaidBankAccount) { + const commandName = 'AddPersonalBankAccount'; + + type AddPersonalBankAccountParams = { + addressName: string; + routingNumber: string; + accountNumber: string; + isSavings: boolean; + setupType: string; + bank?: string; + plaidAccountID: string; + plaidAccessToken: string; + }; + + const parameters: AddPersonalBankAccountParams = { + addressName: account.addressName, + routingNumber: account.routingNumber, + accountNumber: account.accountNumber, + isSavings: account.isSavings, + setupType: 'plaid', + bank: account.bankName, + plaidAccountID: account.plaidAccountID, + plaidAccessToken: account.plaidAccessToken, + }; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, + value: { + isLoading: true, + errors: null, + plaidAccountID: account.plaidAccountID, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, + value: { + isLoading: false, + errors: null, + shouldShowSuccess: true, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, + value: { + isLoading: false, + errors: ErrorUtils.getMicroSecondOnyxError('walletPage.addBankAccountFailure'), + }, + }, + ], + }; + + API.write(commandName, parameters, onyxData); +} + +function deletePaymentBankAccount(bankAccountID: number) { + type DeletePaymentBankAccountParams = {bankAccountID: number}; + + const parameters: DeletePaymentBankAccountParams = {bankAccountID}; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`, + value: {[bankAccountID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}}, + }, + ], + + // Sometimes pusher updates aren't received when we close the App while still offline, + // so we are setting the bankAccount to null here to ensure that it gets cleared out once we come back online. + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`, + value: {[bankAccountID]: null}, + }, + ], + }; + + API.write('DeletePaymentBankAccount', parameters, onyxData); +} + +/** + * Update the user's personal information on the bank account in database. + * + * This action is called by the requestor step in the Verified Bank Account flow + */ +function updatePersonalInformationForBankAccount(params: RequestorStepProps) { + API.write('UpdatePersonalInformationForBankAccount', params, getVBBADataForOnyx(CONST.BANK_ACCOUNT.STEP.REQUESTOR)); +} + +function validateBankAccount(bankAccountID: number, validateCode: string) { + type ValidateBankAccountWithTransactionsParams = { + bankAccountID: number; + validateCode: string; + }; + + const parameters: ValidateBankAccountWithTransactionsParams = { + bankAccountID, + validateCode, + }; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: true, + errors: null, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: false, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: false, + }, + }, + ], + }; + + API.write('ValidateBankAccountWithTransactions', parameters, onyxData); +} + +function clearReimbursementAccount() { + Onyx.set(ONYXKEYS.REIMBURSEMENT_ACCOUNT, null); +} + +function openReimbursementAccountPage(stepToOpen: ReimbursementAccountStep, subStep: ReimbursementAccountSubStep, localCurrentStep: ReimbursementAccountStep) { + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: true, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: false, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: false, + }, + }, + ], + }; + + type OpenReimbursementAccountPageParams = { + stepToOpen: ReimbursementAccountStep; + subStep: ReimbursementAccountSubStep; + localCurrentStep: ReimbursementAccountStep; + }; + + const parameters: OpenReimbursementAccountPageParams = { + stepToOpen, + subStep, + localCurrentStep, + }; + + return API.read('OpenReimbursementAccountPage', parameters, onyxData); +} + +/** + * Updates the bank account in the database with the company step data + */ +function updateCompanyInformationForBankAccount(bankAccount: BankAccountCompanyInformation, policyID: string) { + type UpdateCompanyInformationForBankAccountParams = BankAccountCompanyInformation & {policyID: string}; + + const parameters: UpdateCompanyInformationForBankAccountParams = {...bankAccount, policyID}; + + API.write('UpdateCompanyInformationForBankAccount', parameters, getVBBADataForOnyx(CONST.BANK_ACCOUNT.STEP.COMPANY)); +} + +/** + * Add beneficial owners for the bank account, accept the ACH terms and conditions and verify the accuracy of the information provided + */ +function updateBeneficialOwnersForBankAccount(params: ACHContractStepProps) { + API.write('UpdateBeneficialOwnersForBankAccount', params, getVBBADataForOnyx()); +} + +/** + * Create the bank account with manually entered data. + * + */ +function connectBankAccountManually(bankAccountID: number, accountNumber?: string, routingNumber?: string, plaidMask?: string) { + type ConnectBankAccountManuallyParams = { + bankAccountID: number; + accountNumber?: string; + routingNumber?: string; + plaidMask?: string; + }; + + const parameters: ConnectBankAccountManuallyParams = { + bankAccountID, + accountNumber, + routingNumber, + plaidMask, + }; + + API.write('ConnectBankAccountManually', parameters, getVBBADataForOnyx(CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT)); +} + +/** + * Verify the user's identity via Onfido + */ +function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: OnfidoData) { + type VerifyIdentityForBankAccountParams = { + bankAccountID: number; + onfidoData: string; + }; + + const parameters: VerifyIdentityForBankAccountParams = { + bankAccountID, + onfidoData: JSON.stringify(onfidoData), + }; + + API.write('VerifyIdentityForBankAccount', parameters, getVBBADataForOnyx()); +} + +function openWorkspaceView() { + API.read('OpenWorkspaceView', {}, {}); +} + +function handlePlaidError(bankAccountID: number, error: string, errorDescription: string, plaidRequestID: string) { + type BankAccountHandlePlaidErrorParams = { + bankAccountID: number; + error: string; + errorDescription: string; + plaidRequestID: string; + }; + + const parameters: BankAccountHandlePlaidErrorParams = { + bankAccountID, + error, + errorDescription, + plaidRequestID, + }; + + API.write('BankAccount_HandlePlaidError', parameters); +} + +/** + * Set the reimbursement account loading so that it happens right away, instead of when the API command is processed. + */ +function setReimbursementAccountLoading(isLoading: boolean) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {isLoading}); +} + +export { + addPersonalBankAccount, + clearOnfidoToken, + clearPersonalBankAccount, + clearPlaid, + openPlaidView, + connectBankAccountManually, + connectBankAccountWithPlaid, + deletePaymentBankAccount, + handlePlaidError, + openPersonalBankAccountSetupView, + clearReimbursementAccount, + openReimbursementAccountPage, + updateBeneficialOwnersForBankAccount, + updateCompanyInformationForBankAccount, + updatePersonalInformationForBankAccount, + openWorkspaceView, + validateBankAccount, + verifyIdentityForBankAccount, + setReimbursementAccountLoading, +}; diff --git a/src/libs/actions/DemoActions.js b/src/libs/actions/DemoActions.js new file mode 100644 index 000000000000..29c983c35262 --- /dev/null +++ b/src/libs/actions/DemoActions.js @@ -0,0 +1,70 @@ +import Config from 'react-native-config'; +import Onyx from 'react-native-onyx'; +import lodashGet from 'lodash/get'; +import * as API from '../API'; +import * as ReportUtils from '../ReportUtils'; +import Navigation from '../Navigation/Navigation'; +import ROUTES from '../../ROUTES'; +import ONYXKEYS from '../../ONYXKEYS'; + +let currentUserEmail; +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (val) => { + currentUserEmail = lodashGet(val, 'email', ''); + }, +}); + +function runMoney2020Demo() { + // Try to navigate to existing demo chat if it exists in Onyx + const money2020AccountID = Number(Config ? Config.EXPENSIFY_ACCOUNT_ID_MONEY2020 : 15864555); + const existingChatReport = ReportUtils.getChatByParticipants([money2020AccountID]); + if (existingChatReport) { + // We must call goBack() to remove the demo route from nav history + Navigation.goBack(); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(existingChatReport.reportID)); + return; + } + + // We use makeRequestWithSideEffects here because we need to get the chat report ID to navigate to it after it's created + // eslint-disable-next-line rulesdir/no-api-side-effects-method + API.makeRequestWithSideEffects('CreateChatReport', { + emailList: `${currentUserEmail},money2020@expensify.com`, + activationConference: 'money2020', + }).then((response) => { + // If there's no response or no reportID in the response, navigate the user home so user doesn't get stuck. + if (!response || !response.reportID) { + Navigation.goBack(); + Navigation.navigate(ROUTES.HOME); + return; + } + + // Get reportID & navigate to it + // Note: We must call goBack() to remove the demo route from history + const chatReportID = response.reportID; + Navigation.goBack(); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chatReportID)); + }); +} + +/** + * Runs code for specific demos, based on the provided URL + * + * @param {String} url - URL user is navigating to via deep link (or regular link in web) + */ +function runDemoByURL(url = '') { + const cleanUrl = (url || '').toLowerCase(); + + if (cleanUrl.endsWith(ROUTES.MONEY2020)) { + Onyx.set(ONYXKEYS.DEMO_INFO, { + money2020: { + isBeginningDemo: true, + }, + }); + } else { + // No demo is being run, so clear out demo info + Onyx.set(ONYXKEYS.DEMO_INFO, null); + } +} + +export {runMoney2020Demo, runDemoByURL}; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index bc6ee9be4e0b..422dd8fbb03a 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1,6 +1,7 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; import lodashGet from 'lodash/get'; +import lodashHas from 'lodash/has'; import Str from 'expensify-common/lib/str'; import {format} from 'date-fns'; import CONST from '../../CONST'; @@ -53,6 +54,15 @@ Onyx.connect({ }, }); +let allDraftSplitTransactions; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (val) => { + allDraftSplitTransactions = val || {}; + }, +}); + let allRecentlyUsedTags = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS, @@ -1055,6 +1065,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco let oneOnOneChatReport; let isNewOneOnOneChatReport = false; let shouldCreateOptimisticPersonalDetails = false; + const personalDetailExists = lodashHas(allPersonalDetails, accountID); // If this is a split between two people only and the function // wasn't provided with an existing group chat report id @@ -1063,11 +1074,11 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco // entering code that creates optimistic personal details if ((!hasMultipleParticipants && !existingSplitChatReportID) || isOwnPolicyExpenseChat) { oneOnOneChatReport = splitChatReport; - shouldCreateOptimisticPersonalDetails = !existingSplitChatReport; + shouldCreateOptimisticPersonalDetails = !existingSplitChatReport && !personalDetailExists; } else { const existingChatReport = ReportUtils.getChatByParticipants([accountID]); isNewOneOnOneChatReport = !existingChatReport; - shouldCreateOptimisticPersonalDetails = isNewOneOnOneChatReport; + shouldCreateOptimisticPersonalDetails = isNewOneOnOneChatReport && !personalDetailExists; oneOnOneChatReport = existingChatReport || ReportUtils.buildOptimisticChatReport([accountID]); } @@ -1294,7 +1305,18 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co const receiptObject = {state, source}; // ReportID is -2 (aka "deleted") on the group transaction - const splitTransaction = TransactionUtils.buildOptimisticTransaction(0, CONST.CURRENCY.USD, CONST.REPORT.SPLIT_REPORTID, comment, '', '', '', '', receiptObject, filename); + const splitTransaction = TransactionUtils.buildOptimisticTransaction( + 0, + CONST.CURRENCY.USD, + CONST.REPORT.SPLIT_REPORTID, + comment, + '', + '', + '', + CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, + receiptObject, + filename, + ); // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat const splitChatCreatedReportAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); @@ -1410,7 +1432,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co errors: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), }, [splitIOUReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('report.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), }, }, }, @@ -1489,6 +1511,237 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co Report.notifyNewAction(splitChatReport.chatReportID, currentUserAccountID); } +/** Used for editing a split bill while it's still scanning or when SmartScan fails, it completes a split bill started by startSplitBill above. + * + * @param {number} chatReportID - The group chat or workspace reportID + * @param {Object} reportAction - The split action that lives in the chatReport above + * @param {Object} updatedTransaction - The updated **draft** split transaction + * @param {Number} sessionAccountID - accountID of the current user + * @param {String} sessionEmail - email of the current user + */ +function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessionAccountID, sessionEmail) { + const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(sessionEmail); + const {transactionID} = updatedTransaction; + const unmodifiedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + + // Save optimistic updated transaction and action + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: { + ...updatedTransaction, + receipt: { + state: CONST.IOU.RECEIPT_STATE.OPEN, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, + value: { + [reportAction.reportActionID]: { + lastModified: DateUtils.getDBTime(), + whisperedToAccountIDs: [], + }, + }, + }, + ]; + + const successData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: {pendingAction: null}, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, + value: null, + }, + ]; + + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: { + ...unmodifiedTransaction, + errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, + value: { + [reportAction.reportActionID]: { + ...reportAction, + errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + }, + }, + }, + ]; + + const splitParticipants = updatedTransaction.comment.splits; + const {modifiedAmount: amount, modifiedCurrency: currency} = updatedTransaction; + + // Exclude the current user when calculating the split amount, `calculateAmount` takes it into account + const splitAmount = IOUUtils.calculateAmount(splitParticipants.length - 1, amount, currency, false); + + const splits = [{email: currentUserEmailForIOUSplit}]; + _.each(splitParticipants, (participant) => { + // Skip creating the transaction for the current user + if (participant.email === currentUserEmailForIOUSplit) { + return; + } + const isPolicyExpenseChat = !_.isEmpty(participant.policyID); + + if (!isPolicyExpenseChat) { + // In case this is still the optimistic accountID saved in the splits array, return early as we cannot know + // if there is an existing chat between the split creator and this participant + // Instead, we will rely on Auth generating the report IDs and the user won't see any optimistic chats or reports created + const participantPersonalDetails = allPersonalDetails[participant.accountID] || {}; + if (!participantPersonalDetails || participantPersonalDetails.isOptimisticPersonalDetail) { + splits.push({ + email: participant.email, + }); + return; + } + } + + let oneOnOneChatReport; + let isNewOneOnOneChatReport = false; + if (isPolicyExpenseChat) { + // The workspace chat reportID is saved in the splits array when starting a split bill with a workspace + oneOnOneChatReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${participant.chatReportID}`]; + } else { + const existingChatReport = ReportUtils.getChatByParticipants([participant.accountID]); + isNewOneOnOneChatReport = !existingChatReport; + oneOnOneChatReport = existingChatReport || ReportUtils.buildOptimisticChatReport([participant.accountID]); + } + + let oneOnOneIOUReport = lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`, undefined); + const shouldCreateNewOneOnOneIOUReport = + _.isUndefined(oneOnOneIOUReport) || (isPolicyExpenseChat && ReportUtils.isControlPolicyExpenseReport(oneOnOneIOUReport) && ReportUtils.isReportApproved(oneOnOneIOUReport)); + + if (shouldCreateNewOneOnOneIOUReport) { + oneOnOneIOUReport = isPolicyExpenseChat + ? ReportUtils.buildOptimisticExpenseReport(oneOnOneChatReport.reportID, participant.policyID, sessionAccountID, splitAmount, currency) + : ReportUtils.buildOptimisticIOUReport(sessionAccountID, participant.accountID, splitAmount, oneOnOneChatReport.reportID, currency); + } else if (isPolicyExpenseChat) { + // Because of the Expense reports are stored as negative values, we subtract the total from the amount + oneOnOneIOUReport.total -= splitAmount; + } else { + oneOnOneIOUReport = IOUUtils.updateIOUOwnerAndTotal(oneOnOneIOUReport, sessionAccountID, splitAmount, currency); + } + + const oneOnOneTransaction = TransactionUtils.buildOptimisticTransaction( + isPolicyExpenseChat ? -splitAmount : splitAmount, + currency, + oneOnOneIOUReport.reportID, + updatedTransaction.comment.comment, + updatedTransaction.modifiedCreated, + CONST.IOU.MONEY_REQUEST_TYPE.SPLIT, + transactionID, + updatedTransaction.modifiedMerchant, + {...updatedTransaction.receipt, state: CONST.IOU.RECEIPT_STATE.OPEN}, + updatedTransaction.filename, + ); + + const oneOnOneCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); + const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); + const oneOnOneIOUAction = ReportUtils.buildOptimisticIOUReportAction( + CONST.IOU.REPORT_ACTION_TYPE.CREATE, + splitAmount, + currency, + updatedTransaction.comment.comment, + [participant], + oneOnOneTransaction.transactionID, + '', + oneOnOneIOUReport.reportID, + ); + + let oneOnOneReportPreviewAction = ReportActionsUtils.getReportPreviewAction(oneOnOneChatReport.reportID, oneOnOneIOUReport.reportID); + if (oneOnOneReportPreviewAction) { + oneOnOneReportPreviewAction = ReportUtils.updateReportPreview(oneOnOneIOUReport, oneOnOneReportPreviewAction); + } else { + oneOnOneReportPreviewAction = ReportUtils.buildOptimisticReportPreview(oneOnOneChatReport, oneOnOneIOUReport, '', oneOnOneTransaction); + } + + const [oneOnOneOptimisticData, oneOnOneSuccessData, oneOnOneFailureData] = buildOnyxDataForMoneyRequest( + oneOnOneChatReport, + oneOnOneIOUReport, + oneOnOneTransaction, + oneOnOneCreatedActionForChat, + oneOnOneCreatedActionForIOU, + oneOnOneIOUAction, + {}, + oneOnOneReportPreviewAction, + {}, + {}, + isNewOneOnOneChatReport, + shouldCreateNewOneOnOneIOUReport, + ); + + splits.push({ + email: participant.email, + accountID: participant.accountID, + policyID: participant.policyID, + iouReportID: oneOnOneIOUReport.reportID, + chatReportID: oneOnOneChatReport.reportID, + transactionID: oneOnOneTransaction.transactionID, + reportActionID: oneOnOneIOUAction.reportActionID, + createdChatReportActionID: oneOnOneCreatedActionForChat.reportActionID, + createdIOUReportActionID: oneOnOneCreatedActionForIOU.reportActionID, + reportPreviewReportActionID: oneOnOneReportPreviewAction.reportActionID, + }); + + optimisticData.push(...oneOnOneOptimisticData); + successData.push(...oneOnOneSuccessData); + failureData.push(...oneOnOneFailureData); + }); + + const { + amount: transactionAmount, + currency: transactionCurrency, + created: transactionCreated, + merchant: transactionMerchant, + comment: transactionComment, + } = ReportUtils.getTransactionDetails(updatedTransaction); + + API.write( + 'CompleteSplitBill', + { + transactionID, + amount: transactionAmount, + currency: transactionCurrency, + created: transactionCreated, + merchant: transactionMerchant, + comment: transactionComment, + splits: JSON.stringify(splits), + }, + {optimisticData, successData, failureData}, + ); + Navigation.dismissModal(chatReportID); + Report.notifyNewAction(chatReportID, sessionAccountID); +} + +/** + * @param {String} transactionID + * @param {Object} transactionChanges + */ +function setDraftSplitTransaction(transactionID, transactionChanges = {}) { + let draftSplitTransaction = allDraftSplitTransactions[`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`]; + + if (!draftSplitTransaction) { + draftSplitTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + } + + const updatedTransaction = TransactionUtils.getUpdatedTransaction(draftSplitTransaction, transactionChanges, false, false); + + Onyx.merge(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, updatedTransaction); +} + /** * @param {String} transactionID * @param {Number} transactionThreadReportID @@ -1535,7 +1788,8 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC updatedMoneyRequestReport.lastMessageHtml = lastMessage[0].html; // Update the last message of the chat report - const messageText = Localize.translateLocal('iou.payerOwesAmount', { + const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport); + const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', { payer: updatedMoneyRequestReport.managerEmail, amount: CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency), }); @@ -1755,7 +2009,8 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView updatedIOUReport.lastVisibleActionCreated = lastVisibleAction.created; updatedReportPreviewAction = {...reportPreviewAction}; - const messageText = Localize.translateLocal('iou.payerOwesAmount', { + const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport); + const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', { payer: updatedIOUReport.managerEmail, amount: CurrencyUtils.convertToDisplayString(updatedIOUReport.total, updatedIOUReport.currency), }); @@ -2587,9 +2842,12 @@ function setMoneyRequestReceipt(receiptPath, receiptFilename) { Onyx.merge(ONYXKEYS.IOU, {receiptPath, receiptFilename, merchant: ''}); } -function createEmptyTransaction() { +function setUpDistanceTransaction() { const transactionID = NumberUtils.rand64(); - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {transactionID}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { + transactionID, + comment: {type: CONST.TRANSACTION.TYPE.CUSTOM_UNIT, customUnit: {name: CONST.CUSTOM_UNITS.NAME_DISTANCE}}, + }); Onyx.merge(ONYXKEYS.IOU, {transactionID}); } @@ -2660,7 +2918,9 @@ export { deleteMoneyRequest, splitBill, splitBillAndOpenReport, + setDraftSplitTransaction, startSplitBill, + completeSplitBill, requestMoney, sendMoneyElsewhere, approveMoneyRequest, @@ -2682,7 +2942,7 @@ export { setMoneyRequestBillable, setMoneyRequestParticipants, setMoneyRequestReceipt, - createEmptyTransaction, + setUpDistanceTransaction, navigateToNextPage, updateDistanceRequest, replaceReceipt, diff --git a/src/libs/actions/InputFocus/index.desktop.ts b/src/libs/actions/InputFocus/index.desktop.ts new file mode 100644 index 000000000000..b6cf1aba6138 --- /dev/null +++ b/src/libs/actions/InputFocus/index.desktop.ts @@ -0,0 +1,29 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '../../../ONYXKEYS'; +import ReportActionComposeFocusManager from '../../ReportActionComposeFocusManager'; + +function inputFocusChange(focus: boolean) { + Onyx.set(ONYXKEYS.INPUT_FOCUSED, focus); +} + +let refSave: HTMLElement | undefined; +function composerFocusKeepFocusOn(ref: HTMLElement, isFocused: boolean, modal: {willAlertModalBecomeVisible: boolean; isVisible: boolean}, onyxFocused: boolean) { + if (isFocused && !onyxFocused) { + inputFocusChange(true); + ref.focus(); + } + if (isFocused) { + refSave = ref; + } + if (!isFocused && !onyxFocused && !modal.willAlertModalBecomeVisible && !modal.isVisible && refSave) { + if (!ReportActionComposeFocusManager.isFocused()) { + refSave.focus(); + } else { + refSave = undefined; + } + } +} + +const callback = (method: () => void) => method(); + +export {composerFocusKeepFocusOn, inputFocusChange, callback}; diff --git a/src/libs/actions/InputFocus/index.ts b/src/libs/actions/InputFocus/index.ts new file mode 100644 index 000000000000..1840b0625626 --- /dev/null +++ b/src/libs/actions/InputFocus/index.ts @@ -0,0 +1,5 @@ +function inputFocusChange() {} +function composerFocusKeepFocusOn() {} +const callback = () => {}; + +export {composerFocusKeepFocusOn, inputFocusChange, callback}; diff --git a/src/libs/actions/InputFocus/index.website.ts b/src/libs/actions/InputFocus/index.website.ts new file mode 100644 index 000000000000..7c044b169a03 --- /dev/null +++ b/src/libs/actions/InputFocus/index.website.ts @@ -0,0 +1,30 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '../../../ONYXKEYS'; +import * as Browser from '../../Browser'; +import ReportActionComposeFocusManager from '../../ReportActionComposeFocusManager'; + +function inputFocusChange(focus: boolean) { + Onyx.set(ONYXKEYS.INPUT_FOCUSED, focus); +} + +let refSave: HTMLElement | undefined; +function composerFocusKeepFocusOn(ref: HTMLElement, isFocused: boolean, modal: {willAlertModalBecomeVisible: boolean; isVisible: boolean}, onyxFocused: boolean) { + if (isFocused && !onyxFocused) { + inputFocusChange(true); + ref.focus(); + } + if (isFocused) { + refSave = ref; + } + if (!isFocused && !onyxFocused && !modal.willAlertModalBecomeVisible && !modal.isVisible && refSave) { + if (!ReportActionComposeFocusManager.isFocused()) { + refSave.focus(); + } else { + refSave = undefined; + } + } +} + +const callback = (method: () => void) => !Browser.isMobile() && method(); + +export {composerFocusKeepFocusOn, inputFocusChange, callback}; diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 1a73b148e100..53753e193fb1 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -909,6 +909,48 @@ function buildOptimisticCustomUnits() { }; } +/** + * Optimistically creates a Policy Draft for a new workspace + * + * @param {String} [policyOwnerEmail] Optional, the email of the account to make the owner of the policy + * @param {String} [policyName] Optional, custom policy name we will use for created workspace + * @param {String} [policyID] Optional, custom policy id we will use for created workspace + */ +function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', policyID = generatePolicyID()) { + const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); + const {customUnits} = buildOptimisticCustomUnits(); + + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`, + value: { + id: policyID, + type: CONST.POLICY.TYPE.FREE, + name: workspaceName, + role: CONST.POLICY.ROLE.ADMIN, + owner: sessionEmail, + isPolicyExpenseChatEnabled: true, + outputCurrency: lodashGet(allPersonalDetails, [sessionAccountID, 'localCurrencyCode'], CONST.CURRENCY.USD), + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + customUnits, + }, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS}${policyID}`, + value: { + [sessionAccountID]: { + role: CONST.POLICY.ROLE.ADMIN, + errors: {}, + }, + }, + }, + ]; + + Onyx.update(optimisticData); +} + /** * Optimistically creates a new workspace and default workspace chats * @@ -1027,6 +1069,16 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportID}`, value: expenseReportActionData, }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`, + value: null, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS}${policyID}`, + value: null, + }, ], successData: [ { @@ -1131,6 +1183,7 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName ], }, ); + return adminsChatReportID; } @@ -1259,4 +1312,5 @@ export { clearErrors, openDraftWorkspaceRequest, buildOptimisticPolicyRecentlyUsedCategories, + createDraftInitialWorkspace, }; diff --git a/src/libs/actions/ReimbursementAccount/index.js b/src/libs/actions/ReimbursementAccount/index.js index 49ff30e7be8e..68774d0ba8b0 100644 --- a/src/libs/actions/ReimbursementAccount/index.js +++ b/src/libs/actions/ReimbursementAccount/index.js @@ -31,6 +31,7 @@ function setWorkspaceIDForReimbursementAccount(workspaceID) { */ function updateReimbursementAccountDraft(bankAccountData) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, bankAccountData); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); } /** diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 27a02b1fc75f..af1b4a0ac1dd 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1,6 +1,7 @@ import {InteractionManager} from 'react-native'; import _ from 'underscore'; import lodashGet from 'lodash/get'; +import lodashDebounce from 'lodash/debounce'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Onyx from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; @@ -1401,9 +1402,15 @@ function updateWriteCapabilityAndNavigate(report, newValue) { /** * Navigates to the 1:1 report with Concierge + * + * @param {Boolean} ignoreConciergeReportID - Flag to ignore conciergeChatReportID during navigation. The default behavior is to not ignore. */ -function navigateToConciergeChat() { - if (!conciergeChatReportID) { +function navigateToConciergeChat(ignoreConciergeReportID = false) { + // If conciergeChatReportID contains a concierge report ID, we navigate to the concierge chat using the stored report ID. + // Otherwise, we would find the concierge chat and navigate to it. + // Now, when user performs sign-out and a sign-in again, conciergeChatReportID may contain a stale value. + // In order to prevent navigation to a stale value, we use ignoreConciergeReportID to forcefully find and navigate to concierge chat. + if (!conciergeChatReportID || ignoreConciergeReportID) { // In order to avoid creating concierge repeatedly, // we need to ensure that the server data has been successfully pulled Welcome.serverDataIsReadyPromise().then(() => { @@ -1423,8 +1430,9 @@ function navigateToConciergeChat() { * @param {String} visibility * @param {Array} policyMembersAccountIDs * @param {String} writeCapability + * @param {String} welcomeMessage */ -function addPolicyReport(policyID, reportName, visibility, policyMembersAccountIDs, writeCapability = CONST.REPORT.WRITE_CAPABILITIES.ALL) { +function addPolicyReport(policyID, reportName, visibility, policyMembersAccountIDs, writeCapability = CONST.REPORT.WRITE_CAPABILITIES.ALL, welcomeMessage = '') { // The participants include the current user (admin), and for restricted rooms, the policy members. Participants must not be empty. const members = visibility === CONST.REPORT.VISIBILITY.RESTRICTED ? policyMembersAccountIDs : []; const participants = _.unique([currentUserAccountID, ...members]); @@ -1441,6 +1449,9 @@ function addPolicyReport(policyID, reportName, visibility, policyMembersAccountI // The room might contain all policy members so notifying always should be opt-in only. CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, + '', + '', + welcomeMessage, ); const createdReportAction = ReportUtils.buildOptimisticCreatedReportAction(CONST.POLICY.OWNER_EMAIL_FAKE); @@ -1505,6 +1516,7 @@ function addPolicyReport(policyID, reportName, visibility, policyMembersAccountI reportID: policyReport.reportID, createdReportActionID: createdReportAction.reportActionID, writeCapability, + welcomeMessage, }, {optimisticData, successData, failureData}, ); @@ -1897,7 +1909,7 @@ function openReportFromDeepLink(url, isAuthenticated) { InteractionManager.runAfterInteractions(() => { Session.waitForUserSignIn().then(() => { if (route === ROUTES.CONCIERGE) { - navigateToConciergeChat(); + navigateToConciergeChat(true); return; } Navigation.navigate(route, CONST.NAVIGATION.TYPE.PUSH); @@ -2211,7 +2223,63 @@ function savePrivateNotesDraft(reportID, note) { Onyx.merge(`${ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT}${reportID}`, note); } +/** + * @private + * @param {string} searchInput + */ +function searchForReports(searchInput) { + // We do not try to make this request while offline because it sets a loading indicator optimistically + if (isNetworkOffline) { + Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false); + return; + } + + API.read( + 'SearchForReports', + {searchInput}, + { + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + value: false, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + value: false, + }, + ], + }, + ); +} + +/** + * @private + * @param {string} searchInput + */ +const debouncedSearchInServer = lodashDebounce(searchForReports, CONST.TIMING.SEARCH_FOR_REPORTS_DEBOUNCE_TIME, {leading: false}); + +/** + * @param {string} searchInput + */ +function searchInServer(searchInput) { + if (isNetworkOffline) { + Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false); + return; + } + + // Why not set this in optimistic data? It won't run until the API request happens and while the API request is debounced + // we want to show the loading state right away. Otherwise, we will see a flashing UI where the client options are sorted and + // tell the user there are no options, then we start searching, and tell them there are no options again. + Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, true); + debouncedSearchInServer(searchInput); +} + export { + searchInServer, addComment, addAttachment, reconnect, diff --git a/src/libs/actions/Session/index.js b/src/libs/actions/Session/index.js index 117a092c3875..3b623a42689d 100644 --- a/src/libs/actions/Session/index.js +++ b/src/libs/actions/Session/index.js @@ -316,7 +316,7 @@ function signInWithShortLivedAuthToken(email, authToken) { // If the user is signing in with a different account from the current app, should not pass the auto-generated login as it may be tied to the old account. // scene 1: the user is transitioning to newDot from a different account on oldDot. // scene 2: the user is transitioning to desktop app from a different account on web app. - const oldPartnerUserID = credentials.login === email ? credentials.autoGeneratedLogin : ''; + const oldPartnerUserID = credentials.login === email && credentials.autoGeneratedLogin ? credentials.autoGeneratedLogin : ''; API.read('SignInWithShortLivedAuthToken', {authToken, oldPartnerUserID, skipReauthentication: true}, {optimisticData, successData, failureData}); } @@ -541,6 +541,10 @@ function clearAccountMessages() { }); } +function setAccountError(error) { + Onyx.merge(ONYXKEYS.ACCOUNT, {errors: ErrorUtils.getMicroSecondOnyxError(error)}); +} + // It's necessary to throttle requests to reauthenticate since calling this multiple times will cause Pusher to // reconnect each time when we only need to reconnect once. This way, if an authToken is expired and we try to // subscribe to a bunch of channels at once we will only reauthenticate and force reconnect Pusher once. @@ -807,6 +811,7 @@ export { unlinkLogin, clearSignInData, clearAccountMessages, + setAccountError, authenticatePusher, reauthenticatePusher, invalidateCredentials, diff --git a/src/libs/actions/Timing.js b/src/libs/actions/Timing.ts similarity index 76% rename from src/libs/actions/Timing.js rename to src/libs/actions/Timing.ts index 2be2cdc6fa63..13f40bab87c9 100644 --- a/src/libs/actions/Timing.js +++ b/src/libs/actions/Timing.ts @@ -4,15 +4,20 @@ import Firebase from '../Firebase'; import * as API from '../API'; import Log from '../Log'; -let timestampData = {}; +type TimestampData = { + startTime: number; + shouldUseFirebase: boolean; +}; + +let timestampData: Record = {}; /** * Start a performance timing measurement * - * @param {String} eventName - * @param {Boolean} shouldUseFirebase - adds an additional trace in Firebase + * @param eventName + * @param shouldUseFirebase - adds an additional trace in Firebase */ -function start(eventName, shouldUseFirebase = false) { +function start(eventName: string, shouldUseFirebase = false) { timestampData[eventName] = {startTime: Date.now(), shouldUseFirebase}; if (!shouldUseFirebase) { @@ -25,11 +30,11 @@ function start(eventName, shouldUseFirebase = false) { /** * End performance timing. Measure the time between event start/end in milliseconds, and push to Grafana * - * @param {String} eventName - event name used as timestamp key - * @param {String} [secondaryName] - optional secondary event name, passed to grafana - * @param {number} [maxExecutionTime] - optional amount of time (ms) to wait before logging a warn + * @param eventName - event name used as timestamp key + * @param [secondaryName] - optional secondary event name, passed to grafana + * @param [maxExecutionTime] - optional amount of time (ms) to wait before logging a warn */ -function end(eventName, secondaryName = '', maxExecutionTime = 0) { +function end(eventName: string, secondaryName = '', maxExecutionTime = 0) { if (!timestampData[eventName]) { return; } diff --git a/src/libs/localFileDownload/index.android.js b/src/libs/localFileDownload/index.android.ts similarity index 88% rename from src/libs/localFileDownload/index.android.js rename to src/libs/localFileDownload/index.android.ts index b3e39e7a7a53..ad13b5c5cfa7 100644 --- a/src/libs/localFileDownload/index.android.js +++ b/src/libs/localFileDownload/index.android.ts @@ -1,15 +1,13 @@ import RNFetchBlob from 'react-native-blob-util'; import * as FileUtils from '../fileDownload/FileUtils'; +import LocalFileDownload from './types'; /** * Writes a local file to the app's internal directory with the given fileName * and textContent, so we're able to copy it to the Android public download dir. * After the file is copied, it is removed from the internal dir. - * - * @param {String} fileName - * @param {String} textContent */ -export default function localFileDownload(fileName, textContent) { +const localFileDownload: LocalFileDownload = (fileName, textContent) => { const newFileName = FileUtils.appendTimeToFileName(fileName); const dir = RNFetchBlob.fs.dirs.DocumentDir; const path = `${dir}/${newFileName}.txt`; @@ -34,4 +32,6 @@ export default function localFileDownload(fileName, textContent) { RNFetchBlob.fs.unlink(path); }); }); -} +}; + +export default localFileDownload; diff --git a/src/libs/localFileDownload/index.ios.js b/src/libs/localFileDownload/index.ios.ts similarity index 82% rename from src/libs/localFileDownload/index.ios.js rename to src/libs/localFileDownload/index.ios.ts index 1241f5a535db..3597ea5f6d3c 100644 --- a/src/libs/localFileDownload/index.ios.js +++ b/src/libs/localFileDownload/index.ios.ts @@ -1,16 +1,14 @@ import {Share} from 'react-native'; import RNFetchBlob from 'react-native-blob-util'; import * as FileUtils from '../fileDownload/FileUtils'; +import LocalFileDownload from './types'; /** * Writes a local file to the app's internal directory with the given fileName * and textContent, so we're able to share it using iOS' share API. * After the file is shared, it is removed from the internal dir. - * - * @param {String} fileName - * @param {String} textContent */ -export default function localFileDownload(fileName, textContent) { +const localFileDownload: LocalFileDownload = (fileName, textContent) => { const newFileName = FileUtils.appendTimeToFileName(fileName); const dir = RNFetchBlob.fs.dirs.DocumentDir; const path = `${dir}/${newFileName}.txt`; @@ -20,4 +18,6 @@ export default function localFileDownload(fileName, textContent) { RNFetchBlob.fs.unlink(path); }); }); -} +}; + +export default localFileDownload; diff --git a/src/libs/localFileDownload/index.js b/src/libs/localFileDownload/index.ts similarity index 77% rename from src/libs/localFileDownload/index.js rename to src/libs/localFileDownload/index.ts index 427928834c9c..7b9b4973d5c1 100644 --- a/src/libs/localFileDownload/index.js +++ b/src/libs/localFileDownload/index.ts @@ -1,18 +1,18 @@ import * as FileUtils from '../fileDownload/FileUtils'; +import LocalFileDownload from './types'; /** * Creates a Blob with the given fileName and textContent, then dynamically * creates a temporary anchor, just to programmatically click it, so the file * is downloaded by the browser. - * - * @param {String} fileName - * @param {String} textContent */ -export default function localFileDownload(fileName, textContent) { +const localFileDownload: LocalFileDownload = (fileName, textContent) => { const blob = new Blob([textContent], {type: 'text/plain'}); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.download = FileUtils.appendTimeToFileName(`${fileName}.txt`); link.href = url; link.click(); -} +}; + +export default localFileDownload; diff --git a/src/libs/localFileDownload/types.ts b/src/libs/localFileDownload/types.ts new file mode 100644 index 000000000000..2086e2334d39 --- /dev/null +++ b/src/libs/localFileDownload/types.ts @@ -0,0 +1,3 @@ +type LocalFileDownload = (fileName: string, textContent: string) => void; + +export default LocalFileDownload; diff --git a/src/pages/DemoSetupPage.js b/src/pages/DemoSetupPage.js index 5d4b99a0daf9..5432bea0c806 100644 --- a/src/pages/DemoSetupPage.js +++ b/src/pages/DemoSetupPage.js @@ -1,9 +1,11 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import PropTypes from 'prop-types'; import {useFocusEffect} from '@react-navigation/native'; import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; +import CONST from '../CONST'; +import * as DemoActions from '../libs/actions/DemoActions'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -18,12 +20,16 @@ const propTypes = { * route that led the user here. Now, it's just used to route the user home so we * don't show them a "Hmm... It's not here" message (which looks broken). */ -function DemoSetupPage() { - useFocusEffect(() => { - Navigation.isNavigationReady().then(() => { - Navigation.goBack(ROUTES.HOME); - }); - }); +function DemoSetupPage(props) { + useFocusEffect( + useCallback(() => { + if (props.route.name === CONST.DEMO_PAGES.MONEY2020) { + DemoActions.runMoney2020Demo(); + } else { + Navigation.goBack(ROUTES.HOME); + } + }, [props.route.name]), + ); return ; } diff --git a/src/pages/EditRequestAmountPage.js b/src/pages/EditRequestAmountPage.js index 9f72c9afbc23..d65fdafb3b59 100644 --- a/src/pages/EditRequestAmountPage.js +++ b/src/pages/EditRequestAmountPage.js @@ -1,13 +1,11 @@ import React, {useCallback, useRef} from 'react'; -import {InteractionManager} from 'react-native'; import {useFocusEffect} from '@react-navigation/native'; import PropTypes from 'prop-types'; +import CONST from '../CONST'; +import useLocalize from '../hooks/useLocalize'; import ScreenWrapper from '../components/ScreenWrapper'; import HeaderWithBackButton from '../components/HeaderWithBackButton'; -import Navigation from '../libs/Navigation/Navigation'; -import useLocalize from '../hooks/useLocalize'; import MoneyRequestAmountForm from './iou/steps/MoneyRequestAmountForm'; -import ROUTES from '../ROUTES'; const propTypes = { /** Transaction default amount value */ @@ -19,36 +17,25 @@ const propTypes = { /** Callback to fire when the Save button is pressed */ onSubmit: PropTypes.func.isRequired, - /** reportID for the transaction thread */ - reportID: PropTypes.string.isRequired, + /** Callback to fire when we press on the currency */ + onNavigateToCurrency: PropTypes.func.isRequired, }; -function EditRequestAmountPage({defaultAmount, defaultCurrency, onSubmit, reportID}) { +function EditRequestAmountPage({defaultAmount, defaultCurrency, onNavigateToCurrency, onSubmit}) { const {translate} = useLocalize(); - const textInput = useRef(null); - const focusTextInput = () => { - // Component may not be initialized due to navigation transitions - // Wait until interactions are complete before trying to focus - InteractionManager.runAfterInteractions(() => { - // Focus text input - if (!textInput.current) { - return; - } - - textInput.current.focus(); - }); - }; - - const navigateToCurrencySelectionPage = () => { - // Remove query from the route and encode it. - const activeRoute = encodeURIComponent(Navigation.getActiveRoute().replace(/\?.*/, '')); - Navigation.navigate(ROUTES.EDIT_CURRENCY_REQUEST.getRoute(reportID, defaultCurrency, activeRoute)); - }; + const textInput = useRef(null); + const focusTimeoutRef = useRef(null); useFocusEffect( useCallback(() => { - focusTextInput(); + focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION); + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; }, []), ); @@ -64,7 +51,7 @@ function EditRequestAmountPage({defaultAmount, defaultCurrency, onSubmit, report currency={defaultCurrency} amount={defaultAmount} ref={(e) => (textInput.current = e)} - onCurrencyButtonPress={navigateToCurrencySelectionPage} + onCurrencyButtonPress={onNavigateToCurrency} onSubmitButtonPress={onSubmit} /> diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 8b19c7cb7b60..a85f490bbb42 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -5,6 +5,7 @@ import lodashValues from 'lodash/values'; import {withOnyx} from 'react-native-onyx'; import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; +import ROUTES from '../ROUTES'; import compose from '../libs/compose'; import Navigation from '../libs/Navigation/Navigation'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; @@ -205,6 +206,10 @@ function EditRequestPage({betas, report, route, parentReport, policy, session, p currency: defaultCurrency, }); }} + onNavigateToCurrency={() => { + const activeRoute = encodeURIComponent(Navigation.getActiveRoute().replace(/\?.*/, '')); + Navigation.navigate(ROUTES.EDIT_CURRENCY_REQUEST.getRoute(report.reportID, defaultCurrency, activeRoute)); + }} /> ); } diff --git a/src/pages/EditRequestReceiptPage.js b/src/pages/EditRequestReceiptPage.js index 6744f027b404..54ed5a8897a4 100644 --- a/src/pages/EditRequestReceiptPage.js +++ b/src/pages/EditRequestReceiptPage.js @@ -1,5 +1,6 @@ import React, {useState} from 'react'; import PropTypes from 'prop-types'; +import {View} from 'react-native'; import ScreenWrapper from '../components/ScreenWrapper'; import HeaderWithBackButton from '../components/HeaderWithBackButton'; import Navigation from '../libs/Navigation/Navigation'; @@ -40,17 +41,21 @@ function EditRequestReceiptPage({route, transactionID}) { testID={EditRequestReceiptPage.displayName} headerGapStyles={isDraggingOver ? [styles.receiptDropHeaderGap] : []} > - - - - + {({safeAreaPaddingBottomStyle}) => ( + + + + + + + )} ); } diff --git a/src/pages/EditSplitBillPage.js b/src/pages/EditSplitBillPage.js new file mode 100644 index 000000000000..d10803cd40ea --- /dev/null +++ b/src/pages/EditSplitBillPage.js @@ -0,0 +1,161 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; +import {withOnyx} from 'react-native-onyx'; +import CONST from '../CONST'; +import ROUTES from '../ROUTES'; +import ONYXKEYS from '../ONYXKEYS'; +import compose from '../libs/compose'; +import transactionPropTypes from '../components/transactionPropTypes'; +import * as ReportUtils from '../libs/ReportUtils'; +import * as IOU from '../libs/actions/IOU'; +import * as CurrencyUtils from '../libs/CurrencyUtils'; +import Navigation from '../libs/Navigation/Navigation'; +import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView'; +import EditRequestDescriptionPage from './EditRequestDescriptionPage'; +import EditRequestMerchantPage from './EditRequestMerchantPage'; +import EditRequestCreatedPage from './EditRequestCreatedPage'; +import EditRequestAmountPage from './EditRequestAmountPage'; + +const propTypes = { + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** The transaction field we are editing */ + field: PropTypes.string, + + /** The chat reportID of the split */ + reportID: PropTypes.string, + + /** reportActionID of the split action */ + reportActionID: PropTypes.string, + }), + }).isRequired, + + /** The current transaction */ + transaction: transactionPropTypes.isRequired, + + /** The draft transaction that holds data to be persisted on the current transaction */ + draftTransaction: transactionPropTypes, +}; + +const defaultProps = { + draftTransaction: undefined, +}; + +function EditSplitBillPage({route, transaction, draftTransaction}) { + const fieldToEdit = lodashGet(route, ['params', 'field'], ''); + const reportID = lodashGet(route, ['params', 'reportID'], ''); + const reportActionID = lodashGet(route, ['params', 'reportActionID'], ''); + + const { + amount: transactionAmount, + currency: transactionCurrency, + comment: transactionDescription, + merchant: transactionMerchant, + created: transactionCreated, + } = draftTransaction ? ReportUtils.getTransactionDetails(draftTransaction) : ReportUtils.getTransactionDetails(transaction); + + const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; + + function navigateBackToSplitDetails() { + Navigation.navigate(ROUTES.SPLIT_BILL_DETAILS.getRoute(reportID, reportActionID)); + } + + function setDraftSplitTransaction(transactionChanges) { + IOU.setDraftSplitTransaction(transaction.transactionID, transactionChanges); + navigateBackToSplitDetails(); + } + + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DESCRIPTION) { + return ( + { + setDraftSplitTransaction({ + comment: transactionChanges.comment.trim(), + }); + }} + /> + ); + } + + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DATE) { + return ( + { + setDraftSplitTransaction({ + created: transactionChanges.created, + }); + }} + /> + ); + } + + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.AMOUNT) { + return ( + { + const amount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(transactionChanges)); + + setDraftSplitTransaction({ + amount, + currency: defaultCurrency, + }); + }} + onNavigateToCurrency={() => { + const activeRoute = encodeURIComponent(Navigation.getActiveRoute().replace(/\?.*/, '')); + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL_CURRENCY.getRoute(reportID, reportActionID, defaultCurrency, activeRoute)); + }} + /> + ); + } + + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.MERCHANT) { + return ( + { + setDraftSplitTransaction({merchant: transactionChanges.merchant.trim()}); + }} + /> + ); + } + + return ; +} + +EditSplitBillPage.displayName = 'EditSplitBillPage'; +EditSplitBillPage.propTypes = propTypes; +EditSplitBillPage.defaultProps = defaultProps; +export default compose( + withOnyx({ + reportActions: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${route.params.reportID}`, + canEvict: false, + }, + }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file + withOnyx({ + transaction: { + key: ({route, reportActions}) => { + const reportAction = reportActions[`${route.params.reportActionID.toString()}`]; + return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(reportAction, 'originalMessage.IOUTransactionID', 0)}`; + }, + }, + draftTransaction: { + key: ({route, reportActions}) => { + const reportAction = reportActions[`${route.params.reportActionID.toString()}`]; + return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${lodashGet(reportAction, 'originalMessage.IOUTransactionID', 0)}`; + }, + }, + }), +)(EditSplitBillPage); diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js index bd068ad9abcc..13091ab3f845 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.js @@ -23,7 +23,6 @@ import DatePicker from '../../components/DatePicker'; import Form from '../../components/Form'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../components/withCurrentUserPersonalDetails'; import * as PersonalDetails from '../../libs/actions/PersonalDetails'; -import OfflineIndicator from '../../components/OfflineIndicator'; const propTypes = { ...withLocalizePropTypes, @@ -148,6 +147,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP if (!_.isEmpty(walletAdditionalDetails.questions)) { return ( - diff --git a/src/pages/EnablePayments/EnablePaymentsPage.js b/src/pages/EnablePayments/EnablePaymentsPage.js index f7ef2a174208..3f179e309a98 100644 --- a/src/pages/EnablePayments/EnablePaymentsPage.js +++ b/src/pages/EnablePayments/EnablePaymentsPage.js @@ -47,6 +47,7 @@ function EnablePaymentsPage({userWallet}) { return ( diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.js b/src/pages/LogInWithShortLivedAuthTokenPage.js index 62eff262611d..875cdf7e8072 100644 --- a/src/pages/LogInWithShortLivedAuthTokenPage.js +++ b/src/pages/LogInWithShortLivedAuthTokenPage.js @@ -12,8 +12,7 @@ import themeColors from '../styles/themes/default'; import Icon from '../components/Icon'; import * as Expensicons from '../components/Icon/Expensicons'; import * as Illustrations from '../components/Icon/Illustrations'; -import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; -import compose from '../libs/compose'; +import useLocalize from '../hooks/useLocalize'; import TextLink from '../components/TextLink'; import ONYXKEYS from '../ONYXKEYS'; @@ -33,8 +32,6 @@ const propTypes = { }), }).isRequired, - ...withLocalizePropTypes, - /** The details about the account that the user is signing in with */ account: PropTypes.shape({ /** Whether a sign is loading */ @@ -49,15 +46,26 @@ const defaultProps = { }; function LogInWithShortLivedAuthTokenPage(props) { + const {translate} = useLocalize(); + useEffect(() => { const email = lodashGet(props, 'route.params.email', ''); // We have to check for both shortLivedAuthToken and shortLivedToken, as the old mobile app uses shortLivedToken, and is not being actively updated. const shortLivedAuthToken = lodashGet(props, 'route.params.shortLivedAuthToken', '') || lodashGet(props, 'route.params.shortLivedToken', ''); - if (shortLivedAuthToken) { + + // Try to authenticate using the shortLivedToken if we're not already trying to load the accounts + if (shortLivedAuthToken && !props.account.isLoading) { Session.signInWithShortLivedAuthToken(email, shortLivedAuthToken); return; } + + // If an error is returned as part of the route, ensure we set it in the onyxData for the account + const error = lodashGet(props, 'route.params.error', ''); + if (error) { + Session.setAccountError(error); + } + const exitTo = lodashGet(props, 'route.params.exitTo', ''); if (exitTo) { Navigation.isNavigationReady().then(() => { @@ -82,10 +90,18 @@ function LogInWithShortLivedAuthTokenPage(props) { src={Illustrations.RocketBlue} /> - {props.translate('deeplinkWrapper.launching')} + {translate('deeplinkWrapper.launching')} - {props.translate('deeplinkWrapper.expired')} Navigation.navigate()}>{props.translate('deeplinkWrapper.signIn')} + {translate('deeplinkWrapper.expired')}{' '} + { + Session.clearSignInData(); + Navigation.navigate(); + }} + > + {translate('deeplinkWrapper.signIn')} + @@ -105,9 +121,7 @@ LogInWithShortLivedAuthTokenPage.propTypes = propTypes; LogInWithShortLivedAuthTokenPage.defaultProps = defaultProps; LogInWithShortLivedAuthTokenPage.displayName = 'LogInWithShortLivedAuthTokenPage'; -export default compose( - withLocalize, - withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, - }), -)(LogInWithShortLivedAuthTokenPage); +export default withOnyx({ + account: {key: ONYXKEYS.ACCOUNT}, + session: {key: ONYXKEYS.SESSION}, +})(LogInWithShortLivedAuthTokenPage); diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 565f36d69e54..64bff8655403 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {useState, useEffect, useMemo} from 'react'; +import React, {useState, useEffect, useMemo, useCallback} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; @@ -20,6 +20,7 @@ import compose from '../libs/compose'; import personalDetailsPropType from './personalDetailsPropType'; import reportPropTypes from './reportPropTypes'; import variables from '../styles/variables'; +import useNetwork from '../hooks/useNetwork'; const propTypes = { /** Beta features list */ @@ -34,22 +35,27 @@ const propTypes = { ...windowDimensionsPropTypes, ...withLocalizePropTypes, + + /** Whether we are searching for reports in the server */ + isSearchingForReports: PropTypes.bool, }; const defaultProps = { betas: [], personalDetails: {}, reports: {}, + isSearchingForReports: false, }; const excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE); -function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) { +function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, isSearchingForReports}) { const [searchTerm, setSearchTerm] = useState(''); const [filteredRecentReports, setFilteredRecentReports] = useState([]); const [filteredPersonalDetails, setFilteredPersonalDetails] = useState([]); const [filteredUserToInvite, setFilteredUserToInvite] = useState(); const [selectedOptions, setSelectedOptions] = useState([]); + const {isOffline} = useNetwork(); const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const headerMessage = OptionsListUtils.getHeaderMessage( @@ -167,6 +173,13 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) // eslint-disable-next-line react-hooks/exhaustive-deps }, [reports, personalDetails, searchTerm]); + // When search term updates we will fetch any reports + const setSearchTermAndSearchInServer = useCallback((text = '') => { + if (text.length) { + Report.searchInServer(text); + } + setSearchTerm(text); + }, []); return ( createChat(option)} - onChangeText={setSearchTerm} + onChangeText={setSearchTermAndSearchInServer} headerMessage={headerMessage} boldStyle shouldPreventDefaultFocusOnSelectRow={!Browser.isMobile()} shouldShowOptions={isOptionsDataReady} shouldShowConfirmButton confirmButtonText={selectedOptions.length > 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')} + textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''} onConfirmSelection={createGroup} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} + isLoadingNewOptions={isSearchingForReports} /> @@ -230,5 +245,9 @@ export default compose( betas: { key: ONYXKEYS.BETAS, }, + isSearchingForReports: { + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + initWithStoredValues: false, + }, }), )(NewChatPage); diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js index d8fbc0290136..5ddea09c6f4e 100644 --- a/src/pages/ReimbursementAccount/AddressForm.js +++ b/src/pages/ReimbursementAccount/AddressForm.js @@ -145,6 +145,7 @@ function AddressForm(props) { errorText={props.errors.zipCode ? props.translate('bankAccount.error.zipCode') : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} hint={props.translate('common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples})} + containerStyles={[styles.mt2]} /> ); diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index c6f53c2474f4..a99e3d7332a0 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -105,10 +105,12 @@ class ReimbursementAccountPage extends React.Component { this.goBack = this.goBack.bind(this); this.requestorStepRef = React.createRef(); - // The first time we open this page, the props.reimbursementAccount has not been loaded from the server. - // Calculating shouldShowContinueSetupButton on the default data doesn't make sense, and we should recalculate + // The first time we open this page, props.reimbursementAccount is either not available in Onyx + // or only partial data loaded where props.reimbursementAccount.achData.currentStep is not available + // Calculating shouldShowContinueSetupButton on first page open doesn't make sense, and we should recalculate // it once we get the response from the server the first time in componentDidUpdate. - const hasACHDataBeenLoaded = this.props.reimbursementAccount !== ReimbursementAccountProps.reimbursementAccountDefaultProps; + const hasACHDataBeenLoaded = + this.props.reimbursementAccount !== ReimbursementAccountProps.reimbursementAccountDefaultProps && _.has(this.props.reimbursementAccount, 'achData.currentStep'); this.state = { hasACHDataBeenLoaded, shouldShowContinueSetupButton: hasACHDataBeenLoaded ? this.getShouldShowContinueSetupButtonInitialValue() : false, @@ -157,6 +159,12 @@ class ReimbursementAccountPage extends React.Component { return; } + // Update the data that is returned from back-end to draft value + const draftStep = this.props.reimbursementAccount.draftStep; + if (draftStep) { + BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsForStep(draftStep))); + } + const currentStepRouteParam = this.getStepToOpenFromRouteParams(); if (currentStepRouteParam === currentStep) { // The route is showing the correct step, no need to update the route param or clear errors. @@ -177,6 +185,46 @@ class ReimbursementAccountPage extends React.Component { Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(this.getRouteForCurrentStep(currentStep), policyId, backTo)); } + componentWillUnmount() { + BankAccounts.clearReimbursementAccount(); + } + + getFieldsForStep(step) { + switch (step) { + case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: + return ['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']; + case CONST.BANK_ACCOUNT.STEP.COMPANY: + return [ + 'companyName', + 'addressStreet', + 'addressZipCode', + 'addressCity', + 'addressState', + 'companyPhone', + 'website', + 'companyTaxID', + 'incorporationType', + 'incorporationDate', + 'incorporationState', + ]; + case CONST.BANK_ACCOUNT.STEP.REQUESTOR: + return ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode']; + default: + return []; + } + } + + /** + * @param {Array} fieldNames + * + * @returns {*} + */ + getBankAccountFields(fieldNames) { + return { + ..._.pick(lodashGet(this.props.reimbursementAccount, 'achData'), ...fieldNames), + }; + } + /* * Calculates the state used to show the "Continue with setup" view. If a bank account setup is already in progress and * no specific further step was passed in the url we'll show the workspace bank account reset modal if the user wishes to start over diff --git a/src/pages/ReimbursementAccount/RequestorOnfidoStep.js b/src/pages/ReimbursementAccount/RequestorOnfidoStep.js index 72e3850aabd0..3405ad2d80f8 100644 --- a/src/pages/ReimbursementAccount/RequestorOnfidoStep.js +++ b/src/pages/ReimbursementAccount/RequestorOnfidoStep.js @@ -4,10 +4,8 @@ import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; import styles from '../../styles/styles'; -import withLocalize from '../../components/withLocalize'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import Onfido from '../../components/Onfido'; -import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import Growl from '../../libs/Growl'; import CONST from '../../CONST'; @@ -15,6 +13,7 @@ import FullPageOfflineBlockingView from '../../components/BlockingViews/FullPage import StepPropTypes from './StepPropTypes'; import HeaderWithBackButton from '../../components/HeaderWithBackButton'; import ScreenWrapper from '../../components/ScreenWrapper'; +import useLocalize from '../../hooks/useLocalize'; const propTypes = { ...StepPropTypes, @@ -27,65 +26,62 @@ const defaultProps = { onfidoToken: null, }; -class RequestorOnfidoStep extends React.Component { - constructor(props) { - super(props); - this.submit = this.submit.bind(this); - } +const HEADER_STEP_COUNTER = {step: 3, total: 5}; +const ONFIDO_ERROR_DISPLAY_DURATION = 10000; - submit(onfidoData) { - BankAccounts.verifyIdentityForBankAccount(lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0, onfidoData); +function RequestorOnfidoStep({onBackButtonPress, reimbursementAccount, onfidoToken}) { + const {translate} = useLocalize(); + const submitOnfidoData = (onfidoData) => { + BankAccounts.verifyIdentityForBankAccount(lodashGet(reimbursementAccount, 'achData.bankAccountID', 0), onfidoData); BankAccounts.updateReimbursementAccountDraft({isOnfidoSetupComplete: true}); - } + }; - render() { - return ( - - - - - { - BankAccounts.clearOnfidoToken(); - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }} - onError={() => { - // In case of any unexpected error we log it to the server, show a growl, and return the user back to the requestor step so they can try again. - Growl.error(this.props.translate('onfidoStep.genericError'), 10000); - BankAccounts.clearOnfidoToken(); - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }} - onSuccess={(onfidoData) => { - this.submit(onfidoData); - }} - /> - - - - ); - } + const handleOnfidoError = () => { + // In case of any unexpected error we log it to the server, show a growl, and return the user back to the requestor step so they can try again. + Growl.error(translate('onfidoStep.genericError'), ONFIDO_ERROR_DISPLAY_DURATION); + BankAccounts.clearOnfidoToken(); + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); + }; + + const handleOnfidoUserExit = () => { + BankAccounts.clearOnfidoToken(); + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); + }; + + return ( + + + + + + + + + ); } +RequestorOnfidoStep.displayName = 'RequestorOnfidoStep'; RequestorOnfidoStep.propTypes = propTypes; RequestorOnfidoStep.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - onfidoToken: { - key: ONYXKEYS.ONFIDO_TOKEN, - }, - }), -)(RequestorOnfidoStep); +export default withOnyx({ + onfidoToken: { + key: ONYXKEYS.ONFIDO_TOKEN, + }, +})(RequestorOnfidoStep); diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 141f4e841853..272fb30de858 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -20,6 +20,8 @@ import compose from '../libs/compose'; import personalDetailsPropType from './personalDetailsPropType'; import reportPropTypes from './reportPropTypes'; import Performance from '../libs/Performance'; +import networkPropTypes from '../components/networkPropTypes'; +import {withNetwork} from '../components/OnyxProvider'; const propTypes = { /* Onyx Props */ @@ -37,12 +39,20 @@ const propTypes = { ...windowDimensionsPropTypes, ...withLocalizePropTypes, + + /** Network info */ + network: networkPropTypes, + + /** Whether we are searching for reports in the server */ + isSearchingForReports: PropTypes.bool, }; const defaultProps = { betas: [], personalDetails: {}, reports: {}, + network: {}, + isSearchingForReports: false, }; class SearchPage extends Component { @@ -75,6 +85,10 @@ class SearchPage extends Component { } onChangeText(searchValue = '') { + if (searchValue.length) { + Report.searchInServer(searchValue); + } + this.setState({searchValue}, this.debouncedUpdateOptions); } @@ -187,9 +201,13 @@ class SearchPage extends Component { showTitleTooltip shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} + textInputAlert={ + this.props.network.isOffline ? `${this.props.translate('common.youAppearToBeOffline')} ${this.props.translate('search.resultsAreLimited')}` : '' + } onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus + isLoadingNewOptions={this.props.isSearchingForReports} /> @@ -205,6 +223,7 @@ SearchPage.defaultProps = defaultProps; export default compose( withLocalize, withWindowDimensions, + withNetwork(), withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, @@ -215,5 +234,9 @@ export default compose( betas: { key: ONYXKEYS.BETAS, }, + isSearchingForReports: { + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + initWithStoredValues: false, + }, }), )(SearchPage); diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 4e48a965b095..d7f8c3605564 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -23,11 +23,11 @@ import participantPropTypes from '../../components/participantPropTypes'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; +import * as HeaderUtils from '../../libs/HeaderUtils'; import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; import * as ReportUtils from '../../libs/ReportUtils'; import * as Link from '../../libs/actions/Link'; import * as Report from '../../libs/actions/Report'; -import * as Session from '../../libs/actions/Session'; import * as Task from '../../libs/actions/Task'; import compose from '../../libs/compose'; import styles from '../../styles/styles'; @@ -100,7 +100,6 @@ function HeaderView(props) { if (ReportUtils.isCompletedTaskReport(props.report) && canModifyTask) { threeDotMenuItems.push({ icon: Expensicons.Checkmark, - iconFill: themeColors.icon, text: props.translate('task.markAsIncomplete'), onSelected: () => Task.reopenTask(props.report), }); @@ -110,7 +109,6 @@ function HeaderView(props) { if (props.report.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED && props.report.statusNum !== CONST.REPORT.STATUS.CLOSED && canModifyTask) { threeDotMenuItems.push({ icon: Expensicons.Trashcan, - iconFill: themeColors.icon, text: props.translate('common.cancel'), onSelected: () => Task.cancelTask(props.report.reportID, props.report.reportName, props.report.stateNum, props.report.statusNum), }); @@ -121,40 +119,23 @@ function HeaderView(props) { if (props.report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { threeDotMenuItems.push({ icon: Expensicons.ChatBubbles, - iconFill: themeColors.icon, text: props.translate('common.joinThread'), onSelected: () => Report.updateNotificationPreference(props.report.reportID, props.report.notificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false), }); } else if (props.report.notificationPreference.length) { threeDotMenuItems.push({ icon: Expensicons.ChatBubbles, - iconFill: themeColors.icon, text: props.translate('common.leaveThread'), onSelected: () => Report.leaveRoom(props.report.reportID), }); } } - if (!props.report.isPinned) { - threeDotMenuItems.push({ - icon: Expensicons.Pin, - iconFill: themeColors.icon, - text: props.translate('common.pin'), - onSelected: Session.checkIfActionIsAllowed(() => Report.togglePinnedState(props.report.reportID, props.report.isPinned)), - }); - } else { - threeDotMenuItems.push({ - icon: Expensicons.Pin, - iconFill: themeColors.icon, - text: props.translate('common.unPin'), - onSelected: Session.checkIfActionIsAllowed(() => Report.togglePinnedState(props.report.reportID, props.report.isPinned)), - }); - } + threeDotMenuItems.push(HeaderUtils.getPinMenuItem(props.report)); if (isConcierge && props.guideCalendarLink) { threeDotMenuItems.push({ icon: Expensicons.Phone, - iconFill: themeColors.icon, text: props.translate('videoChatButtonAndMenu.tooltip'), onSelected: () => { Link.openExternalLink(props.guideCalendarLink); @@ -163,7 +144,6 @@ function HeaderView(props) { } else if (!isAutomatedExpensifyAccount && !isTaskReport && !isArchivedRoom) { threeDotMenuItems.push({ icon: ZoomIcon, - iconFill: themeColors.icon, text: props.translate('videoChatButtonAndMenu.zoom'), onSelected: () => { Link.openExternalLink(CONST.NEW_ZOOM_MEETING_URL); @@ -171,7 +151,6 @@ function HeaderView(props) { }); threeDotMenuItems.push({ icon: GoogleMeetIcon, - iconFill: themeColors.icon, text: props.translate('videoChatButtonAndMenu.googleMeet'), onSelected: () => { Link.openExternalLink(CONST.NEW_GOOGLE_MEET_MEETING_URL); diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 6dba940f0ecb..4903ab2160a5 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -127,7 +127,7 @@ const defaultProps = { * @returns {String} */ function getReportID(route) { - return String(lodashGet(route, 'params.reportID', null)); + return String(lodashGet(route, 'params.reportID', '')); } function ReportScreen({ @@ -156,6 +156,7 @@ function ReportScreen({ const prevReport = usePrevious(report); const prevUserLeavingStatus = usePrevious(userLeavingStatus); const [isBannerVisible, setIsBannerVisible] = useState(true); + const [listHeight, setListHeight] = useState(0); const reportID = getReportID(route); const {addWorkspaceRoomOrChatPendingAction, addWorkspaceRoomOrChatErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report); @@ -171,7 +172,6 @@ function ReportScreen({ const isLoading = !reportID || !isSidebarLoaded || _.isEmpty(personalDetails); const parentReportAction = ReportActionsUtils.getParentReportAction(report); - const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction); const isSingleTransactionView = ReportUtils.isMoneyRequest(report); const policy = policies[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`] || {}; @@ -188,7 +188,7 @@ function ReportScreen({ /> ); - if (isSingleTransactionView && !isDeletedParentAction) { + if (isSingleTransactionView) { headerView = ( { + if (!ReportUtils.isValidReportIDFromPath(reportID)) { + return; + } // Ensures subscription event succeeds when the report/workspace room is created optimistically. // Check if the optimistic `OpenReport` or `AddWorkspaceRoom` has succeeded by confirming // any `pendingFields.createChat` or `pendingFields.addWorkspaceRoom` fields are set to null. @@ -351,7 +354,8 @@ function ReportScreen({ } }, [report, didSubscribeToReportLeavingEvents, reportID]); - const onListLayout = useCallback(() => { + const onListLayout = useCallback((e) => { + setListHeight((prev) => lodashGet(e, 'nativeEvent.layout.height', prev)); if (!markReadyForHydration) { return; } @@ -438,6 +442,7 @@ function ReportScreen({ isComposerFullSize={isComposerFullSize} onSubmitComment={onSubmitComment} policies={policies} + listHeight={listHeight} personalDetails={personalDetails} /> ) : ( diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index faa710d2cd6b..849db381a549 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -22,6 +22,7 @@ import usePrevious from '../../../../hooks/usePrevious'; import * as EmojiUtils from '../../../../libs/EmojiUtils'; import * as User from '../../../../libs/actions/User'; import * as ReportUtils from '../../../../libs/ReportUtils'; +import * as SuggestionUtils from '../../../../libs/SuggestionUtils'; import * as ReportActionsUtils from '../../../../libs/ReportActionsUtils'; import canFocusInputOnScreenFocus from '../../../../libs/canFocusInputOnScreenFocus'; import SilentCommentUpdater from './SilentCommentUpdater'; @@ -33,6 +34,7 @@ import withKeyboardState from '../../../../components/withKeyboardState'; import {propTypes, defaultProps} from './composerWithSuggestionsProps'; import focusWithDelay from '../../../../libs/focusWithDelay'; import useDebounce from '../../../../hooks/useDebounce'; +import * as InputFocus from '../../../../libs/actions/InputFocus'; const {RNTextInputReset} = NativeModules; @@ -93,11 +95,13 @@ function ComposerWithSuggestions({ handleSendMessage, shouldShowComposeInput, measureParentContainer, + listHeight, // Refs suggestionsRef, animatedRef, forwardedRef, isNextModalWillOpenRef, + editFocused, }) { const {preferredLocale} = useLocalize(); const isFocused = useIsFocused(); @@ -133,6 +137,11 @@ function ComposerWithSuggestions({ // A flag to indicate whether the onScroll callback is likely triggered by a layout change (caused by text change) or not const isScrollLikelyLayoutTriggered = useRef(false); + const suggestions = lodashGet(suggestionsRef, 'current.getSuggestions', () => [])(); + + const hasEnoughSpaceForLargeSuggestion = SuggestionUtils.hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, suggestions.length); + + const isAutoSuggestionPickerLarge = !isSmallScreenWidth || (isSmallScreenWidth && hasEnoughSpaceForLargeSuggestion); /** * Update frequently used emojis list. We debounce this method in the constructor so that UpdateFrequentlyUsedEmojis @@ -373,6 +382,7 @@ function ComposerWithSuggestions({ if (!suggestionsRef.current) { return false; } + InputFocus.inputFocusChange(false); return suggestionsRef.current.setShouldBlockSuggestionCalc(false); }, [suggestionsRef]); @@ -479,9 +489,12 @@ function ComposerWithSuggestions({ return; } + if (editFocused) { + InputFocus.inputFocusChange(false); + return; + } focus(); - }, [focus, prevIsFocused, prevIsModalVisible, isFocused, modal.isVisible, isNextModalWillOpenRef]); - + }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal.isVisible, isNextModalWillOpenRef]); useEffect(() => { if (value.length === 0) { return; @@ -552,6 +565,7 @@ function ComposerWithSuggestions({ updateComment={updateComment} composerHeight={composerHeight} measureParentContainer={measureParentContainer} + isAutoSuggestionPickerLarge={isAutoSuggestionPickerLarge} // Input value={value} setValue={setValue} @@ -591,6 +605,9 @@ export default compose( key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, selector: EmojiUtils.getPreferredSkinToneIndex, }, + editFocused: { + key: ONYXKEYS.INPUT_FOCUSED, + }, parentReportActions: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, canEvict: false, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index b43400287269..dd4d51653546 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -61,6 +61,9 @@ const propTypes = { /** Whether user interactions should be disabled */ disabled: PropTypes.bool, + /** Height of the list which the composer is part of */ + listHeight: PropTypes.number, + // The NVP describing a user's block status blockedFromConcierge: PropTypes.shape({ // The date that the user will be unblocked @@ -86,6 +89,7 @@ const defaultProps = { isComposerFullSize: false, pendingAction: null, shouldShowComposeInput: true, + listHeight: 0, isReportReadyForDisplay: true, ...withCurrentUserPersonalDetailsDefaultProps, }; @@ -108,6 +112,7 @@ function ReportActionCompose({ report, reportID, reportActions, + listHeight, shouldShowComposeInput, isReportReadyForDisplay, }) { @@ -408,6 +413,7 @@ function ReportActionCompose({ onFocus={onFocus} onBlur={onBlur} measureParentContainer={measureContainer} + listHeight={listHeight} /> { diff --git a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.js b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.js index 8ffed01f1068..857b7d5e52c2 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.js +++ b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.js @@ -199,6 +199,8 @@ function SuggestionEmoji({ [shouldBlockCalc], ); + const getSuggestions = useCallback(() => suggestionValues.suggestedEmojis, [suggestionValues]); + useImperativeHandle( forwardedRef, () => ({ @@ -207,8 +209,9 @@ function SuggestionEmoji({ triggerHotkeyActions, setShouldBlockSuggestionCalc, updateShouldShowSuggestionMenuToFalse, + getSuggestions, }), - [onSelectionChange, resetSuggestions, setShouldBlockSuggestionCalc, triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse], + [onSelectionChange, resetSuggestions, setShouldBlockSuggestionCalc, triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse, getSuggestions], ); if (!isEmojiSuggestionsMenuVisible) { diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.js b/src/pages/home/report/ReportActionCompose/SuggestionMention.js index 6c08b68cdc78..84bee9c80c7f 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.js +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.js @@ -256,6 +256,8 @@ function SuggestionMention({ setSuggestionValues((prevState) => ({...prevState, suggestedMentions: []})); }, []); + const getSuggestions = useCallback(() => suggestionValues.suggestedMentions, [suggestionValues]); + useImperativeHandle( forwardedRef, () => ({ @@ -263,8 +265,9 @@ function SuggestionMention({ triggerHotkeyActions, setShouldBlockSuggestionCalc, updateShouldShowSuggestionMenuToFalse, + getSuggestions, }), - [resetSuggestions, setShouldBlockSuggestionCalc, triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse], + [resetSuggestions, setShouldBlockSuggestionCalc, triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse, getSuggestions], ); if (!isMentionSuggestionsMenuVisible) { diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.js b/src/pages/home/report/ReportActionCompose/Suggestions.js index 0e98e69d31d1..60c31efb1446 100644 --- a/src/pages/home/report/ReportActionCompose/Suggestions.js +++ b/src/pages/home/report/ReportActionCompose/Suggestions.js @@ -2,7 +2,6 @@ import React, {useRef, useCallback, useImperativeHandle} from 'react'; import PropTypes from 'prop-types'; import SuggestionMention from './SuggestionMention'; import SuggestionEmoji from './SuggestionEmoji'; -import useWindowDimensions from '../../../../hooks/useWindowDimensions'; import * as SuggestionProps from './suggestionProps'; const propTypes = { @@ -12,11 +11,15 @@ const propTypes = { /** Function to clear the input */ resetKeyboardInput: PropTypes.func.isRequired, + /** Is auto suggestion picker large */ + isAutoSuggestionPickerLarge: PropTypes.bool, + ...SuggestionProps.baseProps, }; const defaultProps = { forwardedRef: null, + isAutoSuggestionPickerLarge: true, }; /** @@ -25,10 +28,24 @@ const defaultProps = { * * @returns {React.Component} */ -function Suggestions({isComposerFullSize, value, setValue, selection, setSelection, updateComment, composerHeight, forwardedRef, resetKeyboardInput, measureParentContainer}) { +function Suggestions({ + isComposerFullSize, + value, + setValue, + selection, + setSelection, + updateComment, + composerHeight, + forwardedRef, + resetKeyboardInput, + measureParentContainer, + isAutoSuggestionPickerLarge, +}) { const suggestionEmojiRef = useRef(null); const suggestionMentionRef = useRef(null); + const getSuggestions = useCallback(() => suggestionEmojiRef.current.getSuggestions() || suggestionMentionRef.current.getSuggestions(), []); + /** * Clean data related to EmojiSuggestions */ @@ -71,16 +88,11 @@ function Suggestions({isComposerFullSize, value, setValue, selection, setSelecti triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse, setShouldBlockSuggestionCalc, + getSuggestions, }), - [onSelectionChange, resetSuggestions, setShouldBlockSuggestionCalc, triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse], + [onSelectionChange, resetSuggestions, setShouldBlockSuggestionCalc, triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse, getSuggestions], ); - const {windowHeight, isSmallScreenWidth} = useWindowDimensions(); - - // the larger composerHeight the less space for EmojiPicker, Pixel 2 has pretty small screen and this value equal 5.3 - const hasEnoughSpaceForLargeSuggestion = windowHeight / composerHeight >= 6.8; - const isAutoSuggestionPickerLarge = !isSmallScreenWidth || (isSmallScreenWidth && hasEnoughSpaceForLargeSuggestion); - const baseProps = { value, setValue, diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index cb756ee40491..f76f884dca52 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -35,6 +35,9 @@ import useReportScrollManager from '../../../hooks/useReportScrollManager'; import * as EmojiPickerAction from '../../../libs/actions/EmojiPickerAction'; import focusWithDelay from '../../../libs/focusWithDelay'; import * as Browser from '../../../libs/Browser'; +import * as InputFocus from '../../../libs/actions/InputFocus'; +import onyxSubscribe from '../../../libs/onyxSubscribe'; +import ONYXKEYS from '../../../ONYXKEYS'; const propTypes = { /** All the data of the action */ @@ -111,6 +114,8 @@ function ReportActionItemMessageEdit(props) { const [selection, setSelection] = useState(getInitialSelection()); const [isFocused, setIsFocused] = useState(false); const [hasExceededMaxCommentLength, setHasExceededMaxCommentLength] = useState(false); + const [modal, setModal] = useState(false); + const [onyxFocused, setOnyxFocused] = useState(false); const textInputRef = useRef(null); const isFocusedRef = useRef(false); @@ -128,6 +133,36 @@ function ReportActionItemMessageEdit(props) { isFocusedRef.current = isFocused; }, [isFocused]); + useEffect(() => { + InputFocus.composerFocusKeepFocusOn(textInputRef.current, isFocused, modal, onyxFocused); + }, [isFocused, modal, onyxFocused]); + + useEffect(() => { + const unsubscribeOnyxModal = onyxSubscribe({ + key: ONYXKEYS.MODAL, + callback: (modalArg) => { + if (_.isNull(modalArg)) { + return; + } + setModal(modalArg); + }, + }); + + const unsubscribeOnyxFocused = onyxSubscribe({ + key: ONYXKEYS.INPUT_FOCUSED, + callback: (modalArg) => { + if (_.isNull(modalArg)) { + return; + } + setOnyxFocused(modalArg); + }, + }); + return () => { + unsubscribeOnyxModal(); + unsubscribeOnyxFocused(); + }; + }, []); + // We consider the report action active if it's focused, its emoji picker is open or its context menu is open const isActive = useCallback( () => isFocusedRef.current || EmojiPickerAction.isActive(props.action.reportActionID) || ReportActionContextMenu.isActiveReportAction(props.action.reportActionID), @@ -211,16 +246,16 @@ function ReportActionItemMessageEdit(props) { } } emojisPresentBefore.current = emojis; - setDraft((prevDraft) => { - if (newDraftInput !== newDraft) { - const remainder = ComposerUtils.getCommonSuffixLength(prevDraft, newDraft); - setSelection({ - start: newDraft.length - remainder, - end: newDraft.length - remainder, - }); - } - return newDraft; - }); + + setDraft(newDraft); + + if (newDraftInput !== newDraft) { + const remainder = ComposerUtils.getCommonSuffixLength(newDraftInput, newDraft); + setSelection({ + start: newDraft.length - remainder, + end: newDraft.length - remainder, + }); + } // This component is rendered only when draft is set to a non-empty string. In order to prevent component // unmount when user deletes content of textarea, we set previous message instead of empty string. @@ -238,6 +273,8 @@ function ReportActionItemMessageEdit(props) { * Delete the draft of the comment being edited. This will take the comment out of "edit mode" with the old content. */ const deleteDraft = useCallback(() => { + InputFocus.callback(() => setIsFocused(false)); + InputFocus.inputFocusChange(false); debouncedSaveDraft.cancel(); Report.saveReportActionDraft(props.reportID, props.action, ''); diff --git a/src/pages/home/report/ReportAttachments.js b/src/pages/home/report/ReportAttachments.js index 4146be0d3971..5f194d10bce3 100644 --- a/src/pages/home/report/ReportAttachments.js +++ b/src/pages/home/report/ReportAttachments.js @@ -30,7 +30,7 @@ function ReportAttachments(props) { defaultOpen report={report} source={source} - onModalHide={() => Navigation.dismissModal(reportID)} + onModalHide={() => Navigation.dismissModal()} onCarouselAttachmentChange={(attachment) => { const route = ROUTES.REPORT_ATTACHMENTS.getRoute(reportID, attachment.source); Navigation.navigate(route); diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index 0711cd181964..2237e6448504 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -42,6 +42,9 @@ const propTypes = { /** Whether user interactions should be disabled */ shouldDisableCompose: PropTypes.bool, + /** Height of the list which the composer is part of */ + listHeight: PropTypes.number, + /** Whetjer the report is ready for display */ isReportReadyForDisplay: PropTypes.bool, @@ -56,6 +59,7 @@ const defaultProps = { personalDetails: {}, shouldShowComposeInput: true, shouldDisableCompose: false, + listHeight: 0, isReportReadyForDisplay: true, }; @@ -96,6 +100,7 @@ function ReportFooter(props) { pendingAction={props.pendingAction} isComposerFullSize={props.isComposerFullSize} disabled={props.shouldDisableCompose} + listHeight={props.listHeight} isReportReadyForDisplay={props.isReportReadyForDisplay} /> @@ -111,7 +116,9 @@ ReportFooter.defaultProps = defaultProps; export default compose( withWindowDimensions, withOnyx({ - shouldShowComposeInput: {key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT}, - initialValue: false, + shouldShowComposeInput: { + key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, + initialValue: false, + }, }), )(ReportFooter); diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 6b232cf31f40..d3ff3070466f 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -70,7 +70,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority const unsubscribeOnyxModal = onyxSubscribe({ key: ONYXKEYS.MODAL, callback: (modalArg) => { - if (_.isNull(modalArg)) { + if (_.isNull(modalArg) || typeof modalArg !== 'object') { return; } modal.current = modalArg; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 80fd1d39239d..c87d4f06e1f4 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -62,6 +62,13 @@ const propTypes = { /** Forwarded ref to FloatingActionButtonAndPopover */ innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + + /** Information about any currently running demos */ + demoInfo: PropTypes.shape({ + money2020: PropTypes.shape({ + isBeginningDemo: PropTypes.bool, + }), + }), }; const defaultProps = { onHideCreateMenu: () => {}, @@ -70,6 +77,7 @@ const defaultProps = { betas: [], isLoading: false, innerRef: null, + demoInfo: {}, }; /** @@ -152,6 +160,9 @@ function FloatingActionButtonAndPopover(props) { if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) { return; } + if (lodashGet(props.demoInfo, 'money2020.isBeginningDemo', false)) { + return; + } Welcome.show({routes, showCreateMenu}); // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.isLoading]); @@ -262,6 +273,9 @@ export default compose( isLoading: { key: ONYXKEYS.IS_LOADING_REPORT_DATA, }, + demoInfo: { + key: ONYXKEYS.DEMO_INFO, + }, }), )( forwardRef((props, ref) => ( diff --git a/src/pages/iou/NewDistanceRequestPage.js b/src/pages/iou/NewDistanceRequestPage.js index 562ea66453a1..c6ac7d72d5f8 100644 --- a/src/pages/iou/NewDistanceRequestPage.js +++ b/src/pages/iou/NewDistanceRequestPage.js @@ -49,7 +49,7 @@ function NewDistanceRequestPage({iou, report, route}) { if (iou.transactionID) { return; } - IOU.createEmptyTransaction(); + IOU.setUpDistanceTransaction(); }, [iou.transactionID]); return ( diff --git a/src/pages/iou/ReceiptSelector/NavigationAwareCamera.js b/src/pages/iou/ReceiptSelector/NavigationAwareCamera.js index 9289fc6a2434..e9cb81003979 100644 --- a/src/pages/iou/ReceiptSelector/NavigationAwareCamera.js +++ b/src/pages/iou/ReceiptSelector/NavigationAwareCamera.js @@ -22,7 +22,7 @@ const defaultProps = { }; // Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused. -function NavigationAwareCamera({torchOn, onTorchAvailability, ...props}, ref) { +const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, ...props}, ref) => { const trackRef = useRef(null); const isCameraActive = useIsFocused(); @@ -66,10 +66,10 @@ function NavigationAwareCamera({torchOn, onTorchAvailability, ...props}, ref) { /> ); -} +}); NavigationAwareCamera.propTypes = propTypes; NavigationAwareCamera.displayName = 'NavigationAwareCamera'; NavigationAwareCamera.defaultProps = defaultProps; -export default React.forwardRef(NavigationAwareCamera); +export default NavigationAwareCamera; diff --git a/src/pages/iou/ReceiptSelector/NavigationAwareCamera.native.js b/src/pages/iou/ReceiptSelector/NavigationAwareCamera.native.js index 8fa153550cbe..9fb420791539 100644 --- a/src/pages/iou/ReceiptSelector/NavigationAwareCamera.native.js +++ b/src/pages/iou/ReceiptSelector/NavigationAwareCamera.native.js @@ -3,27 +3,33 @@ import {Camera} from 'react-native-vision-camera'; import {useTabAnimation} from '@react-navigation/material-top-tabs'; import {useNavigation} from '@react-navigation/native'; import PropTypes from 'prop-types'; -import refPropTypes from '../../../components/refPropTypes'; const propTypes = { /* The index of the tab that contains this camera */ cameraTabIndex: PropTypes.number.isRequired, - /* Forwarded ref */ - forwardedRef: refPropTypes.isRequired, + /* Whether we're in a tab navigator */ + isInTabNavigator: PropTypes.bool.isRequired, }; // Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused. -function NavigationAwareCamera({cameraTabIndex, forwardedRef, ...props}) { +const NavigationAwareCamera = React.forwardRef(({cameraTabIndex, isInTabNavigator, ...props}, ref) => { // Get navigation to get initial isFocused value (only needed once during init!) const navigation = useNavigation(); const [isCameraActive, setIsCameraActive] = useState(navigation.isFocused()); // Retrieve the animation value from the tab navigator, which ranges from 0 to the total number of pages displayed. // Even a minimal scroll towards the camera page (e.g., a value of 0.001 at start) should activate the camera for immediate responsiveness. - const tabPositionAnimation = useTabAnimation(); + + // STOP!!!!!!! This is not a pattern to be followed! We are conditionally rendering this hook becase when used in the edit flow we'll never be inside a tab navigator. + // eslint-disable-next-line react-hooks/rules-of-hooks + const tabPositionAnimation = isInTabNavigator ? useTabAnimation() : null; useEffect(() => { + if (!isInTabNavigator) { + return; + } + const listenerId = tabPositionAnimation.addListener(({value}) => { // Activate camera as soon the index is animating towards the `cameraTabIndex` setIsCameraActive(value > cameraTabIndex - 1 && value < cameraTabIndex + 1); @@ -32,7 +38,7 @@ function NavigationAwareCamera({cameraTabIndex, forwardedRef, ...props}) { return () => { tabPositionAnimation.removeListener(listenerId); }; - }, [cameraTabIndex, tabPositionAnimation]); + }, [cameraTabIndex, tabPositionAnimation, isInTabNavigator]); // Note: The useEffect can be removed once VisionCamera V3 is used. // Its only needed for android, because there is a native cameraX android bug. With out this flow would break the camera: @@ -56,21 +62,15 @@ function NavigationAwareCamera({cameraTabIndex, forwardedRef, ...props}) { return ( ); -} +}); NavigationAwareCamera.propTypes = propTypes; NavigationAwareCamera.displayName = 'NavigationAwareCamera'; -export default React.forwardRef((props, ref) => ( - -)); +export default NavigationAwareCamera; diff --git a/src/pages/iou/ReceiptSelector/TabNavigationAwareCamera.js b/src/pages/iou/ReceiptSelector/TabNavigationAwareCamera.js deleted file mode 100644 index 744a10376237..000000000000 --- a/src/pages/iou/ReceiptSelector/TabNavigationAwareCamera.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, {useEffect, useState} from 'react'; -import {Camera} from 'react-native-vision-camera'; -import {useTabAnimation} from '@react-navigation/material-top-tabs'; -import {useNavigation} from '@react-navigation/native'; -import PropTypes from 'prop-types'; -import refPropTypes from '../../../components/refPropTypes'; - -const propTypes = { - /* The index of the tab that contains this camera */ - cameraTabIndex: PropTypes.number.isRequired, - - /* Forwarded ref */ - forwardedRef: refPropTypes.isRequired, -}; - -// Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused. -function TabNavigationAwareCamera({cameraTabIndex, forwardedRef, ...props}) { - // Get navigation to get initial isFocused value (only needed once during init!) - const navigation = useNavigation(); - const [isCameraActive, setIsCameraActive] = useState(navigation.isFocused()); - - // Get the animation value from the tab navigator. Its a value between 0 and the - // number of pages we render in the tab navigator. When we even just slightly start to scroll to the camera page, - // (value is e.g. 0.001 on animation start) we want to activate the camera, so its as fast as possible active. - const tabPositionAnimation = useTabAnimation(); - - useEffect(() => { - const listenerId = tabPositionAnimation.addListener(({value}) => { - // Activate camera as soon the index is animating towards the `cameraTabIndex` - setIsCameraActive(value > cameraTabIndex - 1 && value < cameraTabIndex + 1); - }); - - return () => { - tabPositionAnimation.removeListener(listenerId); - }; - }, [cameraTabIndex, tabPositionAnimation]); - - // Note: The useEffect can be removed once VisionCamera V3 is used. - // Its only needed for android, because there is a native cameraX android bug. With out this flow would break the camera: - // 1. Open camera tab - // 2. Take a picture - // 3. Go back from the opened screen - // 4. The camera is not working anymore - useEffect(() => { - const removeBlurListener = navigation.addListener('blur', () => { - setIsCameraActive(false); - }); - const removeFocusListener = navigation.addListener('focus', () => { - setIsCameraActive(true); - }); - - return () => { - removeBlurListener(); - removeFocusListener(); - }; - }, [navigation]); - - return ( - - ); -} - -TabNavigationAwareCamera.propTypes = propTypes; -TabNavigationAwareCamera.displayName = 'TabNavigationAwareCamera'; - -export default React.forwardRef((props, ref) => ( - -)); diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index fba792029914..ca9fe90575e7 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -165,7 +165,6 @@ function ReceiptSelector({route, transactionID, iou, report}) { const panResponder = useRef( PanResponder.create({ - onMoveShouldSetPanResponder: () => true, onPanResponderTerminationRequest: () => false, }), ).current; @@ -181,7 +180,7 @@ function ReceiptSelector({route, transactionID, iou, report}) { /> )} {cameraPermissionState === 'denied' && ( - + diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index f2654a9faefb..8bf13422f70c 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -25,7 +25,6 @@ import {iouPropTypes, iouDefaultProps} from '../propTypes'; import NavigationAwareCamera from './NavigationAwareCamera'; import Navigation from '../../../libs/Navigation/Navigation'; import * as FileUtils from '../../../libs/fileDownload/FileUtils'; -import TabNavigationAwareCamera from './TabNavigationAwareCamera'; const propTypes = { /** React Navigation route */ @@ -76,8 +75,6 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator}) const {translate} = useLocalize(); - const CameraComponent = isInTabNavigator ? TabNavigationAwareCamera : NavigationAwareCamera; - useEffect(() => { const refreshCameraPermissionStatus = () => { CameraPermission.getCameraPermissionStatus() @@ -162,7 +159,7 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator}) return ( {cameraPermissionStatus !== RESULTS.GRANTED && ( - + )} {cameraPermissionStatus === RESULTS.GRANTED && device != null && ( - )} diff --git a/src/pages/iou/SplitBillDetailsPage.js b/src/pages/iou/SplitBillDetailsPage.js index 4b5037c5eca3..e29fc8ce017e 100644 --- a/src/pages/iou/SplitBillDetailsPage.js +++ b/src/pages/iou/SplitBillDetailsPage.js @@ -1,26 +1,28 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import _ from 'underscore'; -import lodashGet from 'lodash/get'; import {View} from 'react-native'; import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import styles from '../../styles/styles'; import ONYXKEYS from '../../ONYXKEYS'; +import CONST from '../../CONST'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; -import ScreenWrapper from '../../components/ScreenWrapper'; -import MoneyRequestConfirmationList from '../../components/MoneyRequestConfirmationList'; import personalDetailsPropType from '../personalDetailsPropType'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import compose from '../../libs/compose'; import reportActionPropTypes from '../home/report/reportActionPropTypes'; import reportPropTypes from '../reportPropTypes'; +import transactionPropTypes from '../../components/transactionPropTypes'; import withReportAndReportActionOrNotFound from '../home/report/withReportAndReportActionOrNotFound'; -import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView'; -import CONST from '../../CONST'; -import HeaderWithBackButton from '../../components/HeaderWithBackButton'; import * as TransactionUtils from '../../libs/TransactionUtils'; import * as ReportUtils from '../../libs/ReportUtils'; +import * as IOU from '../../libs/actions/IOU'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import MoneyRequestConfirmationList from '../../components/MoneyRequestConfirmationList'; +import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView'; +import HeaderWithBackButton from '../../components/HeaderWithBackButton'; import MoneyRequestHeaderStatusBar from '../../components/MoneyRequestHeaderStatusBar'; +import useLocalize from '../../hooks/useLocalize'; const propTypes = { /* Onyx Props */ @@ -34,6 +36,12 @@ const propTypes = { /** Array of report actions for this report */ reportActions: PropTypes.shape(reportActionPropTypes), + /** The current transaction */ + transaction: transactionPropTypes.isRequired, + + /** The draft transaction that holds data to be persisited on the current transaction */ + draftTransaction: transactionPropTypes, + /** Route params */ route: PropTypes.shape({ params: PropTypes.shape({ @@ -45,17 +53,26 @@ const propTypes = { }), }).isRequired, - ...withLocalizePropTypes, + /** Session info for the currently logged in user. */ + session: PropTypes.shape({ + /** Currently logged in user accountID */ + accountID: PropTypes.number, + + /** Currently logged in user email */ + email: PropTypes.string, + }).isRequired, }; const defaultProps = { personalDetails: {}, reportActions: {}, + draftTransaction: undefined, }; function SplitBillDetailsPage(props) { + const {reportID} = props.report; + const {translate} = useLocalize(); const reportAction = props.reportActions[`${props.route.params.reportActionID.toString()}`]; - const transaction = TransactionUtils.getLinkedTransaction(reportAction); const participantAccountIDs = reportAction.originalMessage.participantAccountIDs; // In case this is workspace split bill, we manually add the workspace as the second participant of the split bill @@ -71,25 +88,40 @@ function SplitBillDetailsPage(props) { } const payeePersonalDetails = props.personalDetails[reportAction.actorAccountID]; const participantsExcludingPayee = _.filter(participants, (participant) => participant.accountID !== reportAction.actorAccountID); + + const isScanning = TransactionUtils.hasReceipt(props.transaction) && TransactionUtils.isReceiptBeingScanned(props.transaction); + const hasSmartScanFailed = TransactionUtils.hasReceipt(props.transaction) && props.transaction.receipt.state === CONST.IOU.RECEIPT_STATE.SCANFAILED; + const isEditingSplitBill = props.session.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(props.transaction); + const { amount: splitAmount, currency: splitCurrency, + comment: splitComment, merchant: splitMerchant, created: splitCreated, - comment: splitComment, category: splitCategory, - } = ReportUtils.getTransactionDetails(transaction); - const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); + } = isEditingSplitBill && props.draftTransaction ? ReportUtils.getTransactionDetails(props.draftTransaction) : ReportUtils.getTransactionDetails(props.transaction); + + const onConfirm = useCallback( + () => IOU.completeSplitBill(reportID, reportAction, props.draftTransaction, props.session.accountID, props.session.email), + [reportID, reportAction, props.draftTransaction, props.session.accountID, props.session.email], + ); return ( - - + + - {isScanning && } + {isScanning && ( + + )} {Boolean(participants.length) && ( )} @@ -121,11 +158,35 @@ SplitBillDetailsPage.defaultProps = defaultProps; SplitBillDetailsPage.displayName = 'SplitBillDetailsPage'; export default compose( - withLocalize, withReportAndReportActionOrNotFound, withOnyx({ + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, + }, + reportActions: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${route.params.reportID}`, + canEvict: false, + }, personalDetails: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, + session: { + key: ONYXKEYS.SESSION, + }, + }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file + withOnyx({ + transaction: { + key: ({route, reportActions}) => { + const reportAction = reportActions[`${route.params.reportActionID.toString()}`]; + return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(reportAction, 'originalMessage.IOUTransactionID', 0)}`; + }, + }, + draftTransaction: { + key: ({route, reportActions}) => { + const reportAction = reportActions[`${route.params.reportActionID.toString()}`]; + return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${lodashGet(reportAction, 'originalMessage.IOUTransactionID', 0)}`; + }, + }, }), )(SplitBillDetailsPage); diff --git a/src/pages/iou/propTypes/index.js b/src/pages/iou/propTypes/index.js index 586f8424a2c2..d41b4cbf1724 100644 --- a/src/pages/iou/propTypes/index.js +++ b/src/pages/iou/propTypes/index.js @@ -21,6 +21,9 @@ const iouPropTypes = PropTypes.shape({ /** The category name */ category: PropTypes.string, + /** Whether the request is billable */ + billable: PropTypes.bool, + /** The tag */ tag: PropTypes.string, @@ -42,6 +45,7 @@ const iouDefaultProps = { merchant: '', category: '', tag: '', + billable: false, created: '', participants: [], receiptPath: '', diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index d007e168de79..46367e275af4 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -1,5 +1,5 @@ import React, {useCallback, useEffect, useMemo, useRef} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -25,7 +25,6 @@ import * as FileUtils from '../../../libs/fileDownload/FileUtils'; import * as Policy from '../../../libs/actions/Policy'; import useNetwork from '../../../hooks/useNetwork'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; -import * as StyleUtils from '../../../styles/StyleUtils'; import {iouPropTypes, iouDefaultProps} from '../propTypes'; import * as Expensicons from '../../../components/Icon/Expensicons'; @@ -62,10 +61,11 @@ const defaultProps = { function MoneyRequestConfirmPage(props) { const {isOffline} = useNetwork(); - const {windowHeight, windowWidth} = useWindowDimensions(); + const {windowWidth} = useWindowDimensions(); const prevMoneyRequestId = useRef(props.iou.id); const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType.current, props.selectedTab); + const isScanRequest = MoneyRequestUtils.isScanRequest(props.selectedTab); const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); const participants = useMemo( () => @@ -304,6 +304,10 @@ function MoneyRequestConfirmPage(props) { return props.translate('iou.split'); } + if (iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.SEND) { + return props.translate('common.send'); + } + return props.translate('tabSelector.manual'); }; @@ -327,58 +331,47 @@ function MoneyRequestConfirmPage(props) { }, ]} /> - {/* - * The MoneyRequestConfirmationList component uses a SectionList which uses a VirtualizedList internally. - * VirtualizedList cannot be directly nested within ScrollViews of the same orientation. - * To work around this, we wrap the MoneyRequestConfirmationList component with a horizontal ScrollView. - */} - - - { - const newParticipants = _.map(props.iou.participants, (participant) => { - if (participant.accountID === option.accountID) { - return {...participant, selected: !participant.selected}; - } - return participant; - }); - IOU.setMoneyRequestParticipants(newParticipants); - }} - receiptPath={props.iou.receiptPath} - receiptFilename={props.iou.receiptFilename} - iouType={iouType.current} - reportID={reportID.current} - isPolicyExpenseChat={isPolicyExpenseChat} - // The participants can only be modified when the action is initiated from directly within a group chat and not the floating-action-button. - // This is because when there is a group of people, say they are on a trip, and you have some shared expenses with some of the people, - // but not all of them (maybe someone skipped out on dinner). Then it's nice to be able to select/deselect people from the group chat bill - // split rather than forcing the user to create a new group, just for that expense. The reportID is empty, when the action was initiated from - // the floating-action-button (since it is something that exists outside the context of a report). - canModifyParticipants={!_.isEmpty(reportID.current)} - policyID={props.report.policyID} - bankAccountRoute={ReportUtils.getBankAccountRoute(props.report)} - iouMerchant={props.iou.merchant} - iouCreated={props.iou.created} - isDistanceRequest={isDistanceRequest} - listStyles={[StyleUtils.getMaximumHeight(windowHeight / 3)]} - /> - - + { + const newParticipants = _.map(props.iou.participants, (participant) => { + if (participant.accountID === option.accountID) { + return {...participant, selected: !participant.selected}; + } + return participant; + }); + IOU.setMoneyRequestParticipants(newParticipants); + }} + receiptPath={props.iou.receiptPath} + receiptFilename={props.iou.receiptFilename} + iouType={iouType.current} + reportID={reportID.current} + isPolicyExpenseChat={isPolicyExpenseChat} + // The participants can only be modified when the action is initiated from directly within a group chat and not the floating-action-button. + // This is because when there is a group of people, say they are on a trip, and you have some shared expenses with some of the people, + // but not all of them (maybe someone skipped out on dinner). Then it's nice to be able to select/deselect people from the group chat bill + // split rather than forcing the user to create a new group, just for that expense. The reportID is empty, when the action was initiated from + // the floating-action-button (since it is something that exists outside the context of a report). + canModifyParticipants={!_.isEmpty(reportID.current)} + policyID={props.report.policyID} + bankAccountRoute={ReportUtils.getBankAccountRoute(props.report)} + iouMerchant={props.iou.merchant} + iouCreated={props.iou.created} + isScanRequest={isScanRequest} + isDistanceRequest={isDistanceRequest} + shouldShowSmartScanFields={_.isEmpty(props.iou.receiptPath)} + /> )} diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index bd3ea8a50402..25e41ba78556 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -50,6 +50,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { const iouType = useRef(lodashGet(route, 'params.iouType', '')); const reportID = useRef(lodashGet(route, 'params.reportID', '')); const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType.current, selectedTab); + const isSendRequest = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.SEND; const isScanRequest = MoneyRequestUtils.isScanRequest(selectedTab); const isSplitRequest = iou.id === CONST.IOU.MONEY_REQUEST_TYPE.SPLIT; const [headerTitle, setHeaderTitle] = useState(); @@ -60,8 +61,13 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { return; } + if (isSendRequest) { + setHeaderTitle(translate('common.send')); + return; + } + setHeaderTitle(_.isEmpty(iou.participants) ? translate('tabSelector.manual') : translate('iou.split')); - }, [iou.participants, isDistanceRequest, translate]); + }, [iou.participants, isDistanceRequest, isSendRequest, translate]); const navigateToConfirmationStep = (moneyRequestType) => { IOU.setMoneyRequestId(moneyRequestType); diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index bedca1a10c35..547d2b7c363a 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -240,7 +240,7 @@ function MoneyRequestParticipantsSelector({ // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowConfirmButton = !(participants.length > 1 && hasPolicyExpenseChatParticipant); - const isAllowedToSplit = !isDistanceRequest; + const isAllowedToSplit = !isDistanceRequest && iouType !== CONST.IOU.MONEY_REQUEST_TYPE.SEND; return ( 0 ? safeAreaPaddingBottomStyle : {}]}> diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index b029a2085877..b8d91b4769fc 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -166,7 +166,7 @@ function AddressPage({privatePersonalDetails, route}) { testID={AddressPage.displayName} > Navigation.goBack(ROUTES.SETTINGS_PERSONAL_DETAILS)} /> diff --git a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js index f639a7eebc15..3b695de3fcb7 100644 --- a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js +++ b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js @@ -91,7 +91,7 @@ function PersonalDetailsInitialPage(props) { /> Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS)} /> diff --git a/src/pages/settings/Report/NotificationPreferencePage.js b/src/pages/settings/Report/NotificationPreferencePage.js index 7dc9ff7773de..64e6bdfb4b5b 100644 --- a/src/pages/settings/Report/NotificationPreferencePage.js +++ b/src/pages/settings/Report/NotificationPreferencePage.js @@ -3,8 +3,6 @@ import _ from 'underscore'; import ScreenWrapper from '../../../components/ScreenWrapper'; import HeaderWithBackButton from '../../../components/HeaderWithBackButton'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -import styles from '../../../styles/styles'; -import OptionsList from '../../../components/OptionsList'; import Navigation from '../../../libs/Navigation/Navigation'; import compose from '../../../libs/compose'; import withReportOrNotFound from '../../home/report/withReportOrNotFound'; @@ -14,8 +12,7 @@ import ROUTES from '../../../ROUTES'; import CONST from '../../../CONST'; import * as Report from '../../../libs/actions/Report'; import * as ReportUtils from '../../../libs/ReportUtils'; -import * as Expensicons from '../../../components/Icon/Expensicons'; -import themeColors from '../../../styles/themes/default'; +import SelectionList from '../../../components/SelectionList'; const propTypes = { ...withLocalizePropTypes, @@ -23,7 +20,6 @@ const propTypes = { /** The report for which we are setting notification preferences */ report: reportPropTypes.isRequired, }; -const greenCheckmark = {src: Expensicons.Checkmark, color: themeColors.success}; function NotificationPreferencePage(props) { const shouldDisableNotificationPreferences = ReportUtils.isArchivedRoom(props.report); @@ -33,12 +29,7 @@ function NotificationPreferencePage(props) { value: preference, text: props.translate(`notificationPreferencesPage.notificationPreferences.${preference}`), keyForList: preference, - - // Include the green checkmark icon to indicate the currently selected value - customIcon: preference === props.report.notificationPreference ? greenCheckmark : null, - - // This property will make the currently selected value have bold text - boldStyle: preference === props.report.notificationPreference, + isSelected: preference === props.report.notificationPreference, }), ); @@ -52,18 +43,10 @@ function NotificationPreferencePage(props) { title={props.translate('notificationPreferencesPage.header')} onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(props.report.reportID))} /> - Report.updateNotificationPreference(props.report.reportID, props.report.notificationPreference, option.value, true)} - hideSectionHeaders - optionHoveredStyle={{ - ...styles.hoveredComponentBG, - ...styles.mhn5, - ...styles.ph5, - }} - shouldHaveOptionSeparator - shouldDisableRowInnerPadding - contentContainerStyles={[styles.ph5]} + initiallyFocusedOptionKey={_.find(notificationPreferenceOptions, (locale) => locale.isSelected).keyForList} /> diff --git a/src/pages/settings/Report/WriteCapabilityPage.js b/src/pages/settings/Report/WriteCapabilityPage.js index 9c4814902117..1558d98a830a 100644 --- a/src/pages/settings/Report/WriteCapabilityPage.js +++ b/src/pages/settings/Report/WriteCapabilityPage.js @@ -6,20 +6,17 @@ import CONST from '../../../CONST'; import ScreenWrapper from '../../../components/ScreenWrapper'; import HeaderWithBackButton from '../../../components/HeaderWithBackButton'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -import styles from '../../../styles/styles'; -import OptionsList from '../../../components/OptionsList'; import Navigation from '../../../libs/Navigation/Navigation'; import compose from '../../../libs/compose'; import withReportOrNotFound from '../../home/report/withReportOrNotFound'; import reportPropTypes from '../../reportPropTypes'; import ROUTES from '../../../ROUTES'; import * as Report from '../../../libs/actions/Report'; -import * as Expensicons from '../../../components/Icon/Expensicons'; -import themeColors from '../../../styles/themes/default'; import * as ReportUtils from '../../../libs/ReportUtils'; import FullPageNotFoundView from '../../../components/BlockingViews/FullPageNotFoundView'; import * as PolicyUtils from '../../../libs/PolicyUtils'; import {policyPropTypes, policyDefaultProps} from '../../workspace/withPolicy'; +import SelectionList from '../../../components/SelectionList'; const propTypes = { ...withLocalizePropTypes, @@ -33,19 +30,12 @@ const defaultProps = { ...policyDefaultProps, }; -const greenCheckmark = {src: Expensicons.Checkmark, color: themeColors.success}; - function WriteCapabilityPage(props) { const writeCapabilityOptions = _.map(CONST.REPORT.WRITE_CAPABILITIES, (value) => ({ value, text: props.translate(`writeCapabilityPage.writeCapability.${value}`), keyForList: value, - - // Include the green checkmark icon to indicate the currently selected value - customIcon: value === (props.report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL) ? greenCheckmark : null, - - // This property will make the currently selected value have bold text - boldStyle: value === (props.report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL), + isSelected: value === (props.report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL), })); const isAbleToEdit = !ReportUtils.isAdminRoom(props.report) && PolicyUtils.isPolicyAdmin(props.policy) && !ReportUtils.isArchivedRoom(props.report); @@ -61,18 +51,10 @@ function WriteCapabilityPage(props) { shouldShowBackButton onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(props.report.reportID))} /> - Report.updateWriteCapabilityAndNavigate(props.report, option.value)} - hideSectionHeaders - optionHoveredStyle={{ - ...styles.hoveredComponentBG, - ...styles.mhn5, - ...styles.ph5, - }} - shouldHaveOptionSeparator - shouldDisableRowInnerPadding - contentContainerStyles={[styles.ph5]} + initiallyFocusedOptionKey={_.find(writeCapabilityOptions, (locale) => locale.isSelected).keyForList} /> diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js index 7783b6c58ace..560a395e6844 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js @@ -39,6 +39,9 @@ function VerifyStep({account, session}) { useEffect(() => { Session.clearAccountMessages(); + return () => { + Session.clearAccountMessages(); + }; }, []); useEffect(() => { diff --git a/src/pages/settings/Wallet/ActivatePhysicalCardPage.js b/src/pages/settings/Wallet/ActivatePhysicalCardPage.js index e7198c009a44..0175f2ceac1f 100644 --- a/src/pages/settings/Wallet/ActivatePhysicalCardPage.js +++ b/src/pages/settings/Wallet/ActivatePhysicalCardPage.js @@ -78,7 +78,7 @@ function ActivatePhysicalCardPage({ return; } - Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAINCARDS.getRoute(domain)); + Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAINCARD.getRoute(domain)); }, [cardID, cardList, domain, physicalCard.isLoading]); useEffect( @@ -131,7 +131,7 @@ function ActivatePhysicalCardPage({ return ( Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAINCARDS.getRoute(domain))} + onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAINCARD.getRoute(domain))} backgroundColor={themeColors.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.PREFERENCES]} illustration={LottieAnimations.Magician} scrollViewContainerStyles={[styles.mnh100]} diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.js b/src/pages/settings/Wallet/ExpensifyCardPage.js index cfbd26133ced..c9ee7ece8fa9 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.js +++ b/src/pages/settings/Wallet/ExpensifyCardPage.js @@ -92,6 +92,7 @@ function ExpensifyCardPage({ pan="1234123412341234" expiration="11/02/2024" cvv="321" + domain={domain} /> ) : ( {}, onListContentSizeChange: () => {}, + shouldEnableScroll: true, + style: {}, }; /** @@ -143,11 +172,46 @@ function shouldShowDefaultBadge(filteredPaymentMethods, isDefault = false) { function isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod) { return paymentMethod.accountType === actionPaymentMethodType && paymentMethod.methodID === activePaymentMethodID; } -function PaymentMethodList(props) { - const {actionPaymentMethodType, activePaymentMethodID, bankAccountList, fundList, filterType, network, onPress, shouldShowSelectedState, selectedMethodID, translate} = props; - +function PaymentMethodList({ + actionPaymentMethodType, + activePaymentMethodID, + bankAccountList, + buttonRef, + cardList, + fundList, + filterType, + isLoadingPaymentMethods, + listHeaderComponent, + network, + onListContentSizeChange, + onPress, + shouldEnableScroll, + shouldShowSelectedState, + shouldShowAddPaymentMethodButton, + shouldShowAddBankAccount, + shouldShowEmptyListMessage, + shouldShowAssignedCards, + selectedMethodID, + style, + translate, +}) { const filteredPaymentMethods = useMemo(() => { const paymentCardList = fundList || {}; + + if (shouldShowAssignedCards) { + const assignedCards = _.filter(cardList, (card) => CONST.EXPENSIFY_CARD.ACTIVE_STATES.includes(card.state)); + return _.map(assignedCards, (card) => { + const icon = getBankIcon(card.bank); + return { + key: card.key, + title: translate('walletPage.expensifyCard'), + description: card.domainName, + onPress: () => Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAINCARDS.getRoute(card.domainName)), + ...icon, + }; + }); + } + // Hide any billing cards that are not P2P debit cards for now because you cannot make them your default method, or delete them const filteredCardList = _.filter(paymentCardList, (card) => card.accountData.additionalData.isP2PDebitCard); let combinedPaymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList, filteredCardList); @@ -174,14 +238,26 @@ function PaymentMethodList(props) { })); return combinedPaymentMethods; - }, [actionPaymentMethodType, activePaymentMethodID, bankAccountList, filterType, network, onPress, fundList]); + }, [fundList, shouldShowAssignedCards, bankAccountList, filterType, network.isOffline, cardList, translate, actionPaymentMethodType, activePaymentMethodID, onPress]); /** * Render placeholder when there are no payments methods * * @return {React.Component} */ - const renderListEmptyComponent = useCallback(() => {translate('paymentMethodList.addFirstPaymentMethod')}, [translate]); + const renderListEmptyComponent = () => {translate('paymentMethodList.addFirstPaymentMethod')}; + + const renderListFooterComponent = useCallback( + () => ( + + ), + [onPress, translate], + ); /** * Create a menuItem for each passed paymentMethod @@ -209,13 +285,14 @@ function PaymentMethodList(props) { iconHeight={item.iconSize} iconWidth={item.iconSize} badgeText={shouldShowDefaultBadge(filteredPaymentMethods, item.isDefault) ? translate('paymentMethodList.defaultPaymentMethod') : null} - wrapperStyle={item.wrapperStyle} + wrapperStyle={styles.paymentMethod} + shouldShowRightIcon={shouldShowAssignedCards} shouldShowSelectedState={shouldShowSelectedState} isSelected={selectedMethodID === item.methodID} /> ), - [shouldShowSelectedState, selectedMethodID, filteredPaymentMethods, translate], + [filteredPaymentMethods, translate, shouldShowAssignedCards, shouldShowSelectedState, selectedMethodID], ); return ( @@ -224,25 +301,28 @@ function PaymentMethodList(props) { data={filteredPaymentMethods} renderItem={renderItem} keyExtractor={(item) => item.key} - ListEmptyComponent={renderListEmptyComponent(translate)} - ListHeaderComponent={props.listHeaderComponent} - onContentSizeChange={props.onListContentSizeChange} + ListEmptyComponent={shouldShowEmptyListMessage ? renderListEmptyComponent : null} + ListHeaderComponent={listHeaderComponent} + ListFooterComponent={shouldShowAddBankAccount ? renderListFooterComponent : null} + onContentSizeChange={onListContentSizeChange} + scrollEnabled={shouldEnableScroll} + style={style} /> - {props.shouldShowAddPaymentMethodButton && ( + {shouldShowAddPaymentMethodButton && ( {(isOffline) => (