diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index f3a5c8dc4314..cd19a46b6a88 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -2,4 +2,5 @@ self-hosted-runner: labels: - ubuntu-latest-xl + - macos-12-xl - macos-13-xlarge diff --git a/.github/actions/composite/setupGitForOSBotifyApp/action.yml b/.github/actions/composite/setupGitForOSBotifyApp/action.yml index 328bc7f9b50c..52fb097d254e 100644 --- a/.github/actions/composite/setupGitForOSBotifyApp/action.yml +++ b/.github/actions/composite/setupGitForOSBotifyApp/action.yml @@ -24,6 +24,21 @@ outputs: runs: using: composite steps: + - name: Check if gpg encrypted private key is present + id: key_check + shell: bash + run: | + if [[ -f .github/workflows/OSBotify-private-key.asc.gpg ]]; then + echo "::set-output name=key_exists::true" + fi + + - name: Checkout + uses: actions/checkout@v3 + if: steps.key_check.outputs.key_exists != 'true' + with: + sparse-checkout: | + .github + - 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 diff --git a/.github/workflows/authorChecklist.yml b/.github/workflows/authorChecklist.yml index 33916d7bd10d..6cd881d18c29 100644 --- a/.github/workflows/authorChecklist.yml +++ b/.github/workflows/authorChecklist.yml @@ -9,7 +9,7 @@ jobs: # then you also need to go into PHP and update the name of this job in the GH_JOB_NAME_CHECKLIST constant checklist: runs-on: ubuntu-latest - if: github.actor != 'OSBotify' + if: github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' steps: - name: authorChecklist.js uses: Expensify/App/.github/actions/javascript/authorChecklist@main diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 54ae1048b57b..4031d6c0c119 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest # This job only runs for pull request comments or pull request target events (not issue comments) # It does not run for pull requests created by OSBotify - if: ${{ github.event.issue.pull_request || (github.event_name == 'pull_request_target' && github.event.pull_request.user.login != 'OSBotify') }} + if: ${{ github.event.issue.pull_request || (github.event_name == 'pull_request_target' && github.event.pull_request.user.login != 'OSBotify' && github.event.pull_request.user.login != 'imgbot[bot]') }} steps: - name: CLA comment check uses: actions-ecosystem/action-regex-match@9c35fe9ac1840239939c59e5db8839422eed8a73 diff --git a/.github/workflows/imgbot.yml b/.github/workflows/imgbot.yml new file mode 100644 index 000000000000..5247fad8349e --- /dev/null +++ b/.github/workflows/imgbot.yml @@ -0,0 +1,25 @@ +name: imgbot Image Optimization + +on: pull_request + +permissions: + pull-requests: write + # The two permissions below are supposedly needed to allow a pull request to be merged. + # See https://github.com/cli/cli/discussions/6379 + issues: write + contents: write + +jobs: + approveAndMerge: + runs-on: ubuntu-latest + if: ${{ github.actor == 'imgbot[bot]' }} + steps: + - name: Approve imgbot PR + run: gh pr review --approve "${{ github.event.pull_request.html_url }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Merge imgbot PR + run: gh pr merge --auto --merge "${{ github.event.pull_request.html_url }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b403a1eb737c..9d7efc7d4c29 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,6 +5,7 @@ on: pull_request: types: [opened, synchronize] branches-ignore: [staging, production] + paths: ['**.js', '**.ts', '**.tsx'] jobs: lint: @@ -12,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + uses: actions/checkout@v3 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main @@ -22,9 +23,6 @@ jobs: env: CI: true - - name: Lint shell scripts with ShellCheck - run: npm run shellcheck - - name: Verify there's no Prettier diff run: | npm run prettier -- --loglevel silent diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index a18961b24389..d18a0a383ed6 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -139,7 +139,7 @@ jobs: name: Build and deploy Desktop needs: validateActor if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} - runs-on: macos-13-xlarge + runs-on: macos-12-xl steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index d7d372aa7948..bae843e74709 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -2,8 +2,8 @@ name: Process new code merged to main on: push: - branches: - - main + branches: [main] + paths-ignore: [docs/**, contributingGuides/**, jest/**, tests/**, workflow_tests/**] jobs: typecheck: @@ -112,71 +112,6 @@ jobs: with: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - # Check if actor is member of Expensify organization by looking for Expensify/expensify team - isExpensifyEmployee: - runs-on: ubuntu-latest - - outputs: - IS_EXPENSIFY_EMPLOYEE: ${{ fromJSON(steps.checkAuthor.outputs.IS_EXPENSIFY_EMPLOYEE) }} - - steps: - - name: Get merged pull request - id: getMergedPullRequest - uses: roryabraham/action-get-merged-pull-request@7a7a194f6ff8f3eef58c822083695a97314ebec1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - - - name: Check whether the PR author is member of Expensify/expensify team - id: checkAuthor - run: | - if gh api /orgs/Expensify/teams/expensify-expensify/memberships/${{ steps.getMergedPullRequest.outputs.author }} --silent; then - echo "IS_EXPENSIFY_EMPLOYEE=true" >> "$GITHUB_OUTPUT" - else - echo "IS_EXPENSIFY_EMPLOYEE=false" >> "$GITHUB_OUTPUT" - fi - env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - - newContributorWelcomeMessage: - runs-on: ubuntu-latest - needs: isExpensifyEmployee - if: ${{ github.actor != 'OSBotify' && !fromJSON(needs.isExpensifyEmployee.outputs.IS_EXPENSIFY_EMPLOYEE) }} - steps: - # Version: 2.3.4 - - name: Checkout - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - with: - token: ${{ secrets.OS_BOTIFY_TOKEN }} - - - name: Get merged pull request - id: getMergedPullRequest - # TODO: Point back action actions-ecosystem after https://github.com/actions-ecosystem/action-get-merged-pull-request/pull/223 is merged - uses: roryabraham/action-get-merged-pull-request@7a7a194f6ff8f3eef58c822083695a97314ebec1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - - - name: Get PR count for ${{ steps.getMergedPullRequest.outputs.author }} - run: echo "PR_COUNT=$(gh pr list --author ${{ steps.getMergedPullRequest.outputs.author }} --state any | grep -c '')" >> "$GITHUB_ENV" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Comment on ${{ steps.getMergedPullRequest.outputs.author }}\'s first pull request! - if: ${{ fromJSON(env.PR_COUNT) == 1 }} - uses: actions-ecosystem/action-create-comment@cd098164398331c50e7dfdd0dfa1b564a1873fac - with: - github_token: ${{ secrets.OS_BOTIFY_TOKEN }} - number: ${{ steps.getMergedPullRequest.outputs.number }} - body: | - @${{ steps.getMergedPullRequest.outputs.author }}, Great job getting your first Expensify/App pull request over the finish line! :tada: - - I know there's a lot of information in our [contributing guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md), so here are some points to take note of :memo:: - - 1. Now that your first PR has been merged, you can be hired for another issue. Once you've completed a few issues, you may be eligible to work on more than one job at a time. - 2. Once your PR is deployed to our staging servers, it will undergo quality assurance (QA) testing. If we find that it doesn't work as expected or causes a regression, you'll be responsible for fixing it. Typically, we would revert this PR and give you another chance to create a similar PR without causing a regression. - 3. Once your PR is deployed to _production_, we start a 7-day timer :alarm_clock:. After it has been on production for 7 days without causing any regressions, then we pay out the Upwork job. :moneybag: - - So it might take a while before you're paid for your work, but we typically post multiple new jobs every day, so there's plenty of opportunity. I hope you've had a positive experience contributing to this repo! :blush: - e2ePerformanceTests: needs: [chooseDeployActions] if: ${{ needs.chooseDeployActions.outputs.SHOULD_DEPLOY }} diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml index ab5e1d06e5a4..b259ff9052b6 100644 --- a/.github/workflows/reassurePerformanceTests.yml +++ b/.github/workflows/reassurePerformanceTests.yml @@ -4,6 +4,7 @@ on: pull_request: types: [opened, synchronize] branches-ignore: [staging, production] + paths-ignore: [docs/**, .github/**, contributingGuides/**, tests/**, workflow_tests/**, '**.md', '**.sh'] jobs: perf-tests: diff --git a/.github/workflows/reviewerChecklist.yml b/.github/workflows/reviewerChecklist.yml index 413fc88ff403..e86e08375269 100644 --- a/.github/workflows/reviewerChecklist.yml +++ b/.github/workflows/reviewerChecklist.yml @@ -7,7 +7,7 @@ jobs: # then you also need to go into PHP and update the name of this job in the GH_JOB_NAME_CHECKLIST constant checklist: runs-on: ubuntu-latest - if: github.actor != 'OSBotify' + if: github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' steps: - name: reviewerChecklist.js uses: Expensify/App/.github/actions/javascript/reviewerChecklist@main diff --git a/.github/workflows/shellCheck.yml b/.github/workflows/shellCheck.yml new file mode 100644 index 000000000000..609541e9a660 --- /dev/null +++ b/.github/workflows/shellCheck.yml @@ -0,0 +1,19 @@ +name: Lint shell code + +on: + workflow_call: + pull_request: + types: [opened, synchronize] + branches-ignore: [staging, production] + paths: ['**.sh'] + +jobs: + lint: + if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Lint shell scripts with ShellCheck + run: npm run shellcheck diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 72bdd0468fd2..fa47a2f61d4a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,10 +5,11 @@ on: pull_request: types: [opened, synchronize] branches-ignore: [staging, production] + paths: ['**.js', '**.ts', '**.tsx', '**.sh', 'package.json', 'package-lock.json'] jobs: jest: - if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} + if: ${{ github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' || github.event_name == 'workflow_call' }} runs-on: ubuntu-latest env: CI: true @@ -39,7 +40,7 @@ jobs: run: npx jest --silent --shard=${{ fromJSON(matrix.chunk) }}/${{ strategy.job-total }} --max-workers ${{ steps.cpu-cores.outputs.count }} storybookTests: - if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} + if: ${{ github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' || github.event_name == 'workflow_call' }} runs-on: ubuntu-latest name: Storybook tests steps: @@ -51,7 +52,7 @@ jobs: run: npm run storybook -- --smoke-test --ci shellTests: - if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} + if: ${{ github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' || github.event_name == 'workflow_call' }} runs-on: ubuntu-latest name: Shell tests steps: diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index 7bd7e13bc82b..beb5d4e2f530 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -218,7 +218,7 @@ jobs: if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} env: PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} - runs-on: macos-13-xlarge + runs-on: macos-12-xl steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index de433b2ae88a..776169fc6058 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -5,6 +5,7 @@ on: pull_request: types: [opened, synchronize] branches-ignore: [staging, production] + paths: ['**.ts', '**.tsx', 'package.json', 'package-lock.json'] jobs: typecheck: diff --git a/.github/workflows/validateDocsRoutes.yml b/.github/workflows/validateDocsRoutes.yml index 717560e19f5f..14c08e087565 100644 --- a/.github/workflows/validateDocsRoutes.yml +++ b/.github/workflows/validateDocsRoutes.yml @@ -8,7 +8,7 @@ on: jobs: verify: - if: github.actor != 'OSBotify' + if: github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/validateGithubActions.yml b/.github/workflows/validateGithubActions.yml index bcda941e1b05..2d3216fefe75 100644 --- a/.github/workflows/validateGithubActions.yml +++ b/.github/workflows/validateGithubActions.yml @@ -9,7 +9,7 @@ on: jobs: verify: - if: github.actor != 'OSBotify' + if: github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/verifyPodfile.yml b/.github/workflows/verifyPodfile.yml index d8d931e476d1..d98780e3e829 100644 --- a/.github/workflows/verifyPodfile.yml +++ b/.github/workflows/verifyPodfile.yml @@ -11,7 +11,7 @@ on: jobs: verify: - if: github.actor != 'OSBotify' + if: github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' runs-on: macos-latest steps: - name: Checkout diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml new file mode 100644 index 000000000000..43e0e1650381 --- /dev/null +++ b/.github/workflows/welcome.yml @@ -0,0 +1,55 @@ +name: Post new contributor welcome message + +on: + push: + branches: [main] + +jobs: + newContributorWelcomeMessage: + runs-on: ubuntu-latest + if: ${{ github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get merged pull request + id: getMergedPullRequest + run: | + read -r number author < <(gh pr list --search ${{ github.sha }} --state merged --json 'number,author' | jq -r '.[0] | [.number, .author.login] | join(" ")') + echo "number=$number" >> "$GITHUB_OUTPUT" + echo "author=$author" >> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: Check whether the PR author is member of Expensify/expensify team + id: isExpensifyEmployee + run: | + if gh api /orgs/Expensify/teams/expensify-expensify/memberships/${{ steps.getMergedPullRequest.outputs.author }} --silent; then + echo "IS_EXPENSIFY_EMPLOYEE=true" >> "$GITHUB_OUTPUT" + else + echo "IS_EXPENSIFY_EMPLOYEE=false" >> "$GITHUB_OUTPUT" + fi + env: + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + + - name: Get PR count for ${{ steps.getMergedPullRequest.outputs.author }} + id: getPRCount + run: echo "PR_COUNT=$(gh pr list --author ${{ steps.getMergedPullRequest.outputs.author }} --state any | grep -c '')" >> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: Comment on ${{ steps.getMergedPullRequest.outputs.author }}\'s first pull request! + if: ${{ fromJSON(steps.getPRCount.outputs.PR_COUNT) == 1 }} + run: | + gh pr comment ${{ steps.getMergedPullRequest.outputs.number }} --body \ + "@${{ steps.getMergedPullRequest.outputs.author }}, Great job getting your first Expensify/App pull request over the finish line! :tada: + + I know there's a lot of information in our [contributing guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md), so here are some points to take note of :memo:: + + 1. Now that your first PR has been merged, you can be hired for another issue. Once you've completed a few issues, you may be eligible to work on more than one job at a time. + 2. Once your PR is deployed to our staging servers, it will undergo quality assurance (QA) testing. If we find that it doesn't work as expected or causes a regression, you'll be responsible for fixing it. Typically, we would revert this PR and give you another chance to create a similar PR without causing a regression. + 3. Once your PR is deployed to _production_, we start a 7-day timer :alarm_clock:. After it has been on production for 7 days without causing any regressions, then we pay out the Upwork job. :moneybag: + + So it might take a while before you're paid for your work, but we typically post multiple new jobs every day, so there's plenty of opportunity. I hope you've had a positive experience contributing to this repo! :blush:" + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.storybook/public/logo.png b/.storybook/public/logo.png index 23c909e83f0b..5ba694bac764 100644 Binary files a/.storybook/public/logo.png and b/.storybook/public/logo.png differ diff --git a/.storybook/public/logomark.svg b/.storybook/public/logomark.svg index d1ca5ca84d06..ae263af72c12 100644 --- a/.storybook/public/logomark.svg +++ b/.storybook/public/logomark.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/README.md b/README.md index 9aad797ebb51..998f185939fa 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,7 @@ For an M1 Mac, read this [SO](https://stackoverflow.com/questions/64901180/how-t ## Running the Android app 🤖 * Before installing Android dependencies, you need to obtain a token from Mapbox to download their SDKs. Please run `npm run configure-mapbox` and follow the instructions. If you already did this step for iOS, there is no need to repeat this step. -* Go through the instructions on [this SO post](https://stackoverflow.com/c/expensify/questions/13283/13284#13284) to start running the app on android. -* For more information, go through the official React-Native instructions on [this page](https://reactnative.dev/docs/environment-setup#development-os) for "React Native CLI Quickstart" > Mac OS > Android +* Go through the official React-Native instructions on [this page](https://reactnative.dev/docs/environment-setup?guide=native&platform=android) to start running the app on android. * If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://stackoverflow.com/c/expensify/questions/7699) * To run a on a **Development Emulator**: `npm run android` * Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile diff --git a/android/app/build.gradle b/android/app/build.gradle index 1959dbbdc0dd..cf8097adc18f 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 1001039002 - versionName "1.3.90-2" + versionCode 1001039200 + versionName "1.3.92-0" } flavorDimensions "default" diff --git a/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher_foreground.png index ecf9a8d7648a..f0cf22860e16 100644 Binary files a/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher_foreground.png and b/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher_foreground.png index ba8a2086138c..8070c42691fe 100644 Binary files a/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher_foreground.png and b/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher_foreground.png index d1e6dbf34a18..06f3ee842084 100644 Binary files a/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher_foreground.png and b/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher.png index fe443ce3f696..7a69e882941e 100644 Binary files a/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_foreground.png index 2bb80c3d622b..692e5d1fac3f 100644 Binary files a/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_round.png index fe443ce3f696..7a69e882941e 100644 Binary files a/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher.png index 576097130442..d7d01c5f0c0a 100644 Binary files a/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_foreground.png index 576550530857..5b1e8667481b 100644 Binary files a/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_round.png index 576097130442..d7d01c5f0c0a 100644 Binary files a/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png index 474e8eca239a..a241d41d34fe 100644 Binary files a/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png index 474e8eca239a..a241d41d34fe 100644 Binary files a/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png index 37c8716987f7..996266b64f6e 100644 Binary files a/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png index 37c8716987f7..996266b64f6e 100644 Binary files a/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_notification.png b/android/app/src/main/res/drawable-hdpi/ic_notification.png index 7612112d1bc5..1d7e563f77ad 100644 Binary files a/android/app/src/main/res/drawable-hdpi/ic_notification.png and b/android/app/src/main/res/drawable-hdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_notification.png b/android/app/src/main/res/drawable-xhdpi/ic_notification.png index a01f2c5e0dc9..e7fe54ede5d3 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/ic_notification.png and b/android/app/src/main/res/drawable-xhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/bootsplash_logo.png b/android/app/src/main/res/drawable-xxhdpi/bootsplash_logo.png index 06b2bfc8447b..f123d07f59c2 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/bootsplash_logo.png and b/android/app/src/main/res/drawable-xxhdpi/bootsplash_logo.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_notification.png b/android/app/src/main/res/drawable-xxhdpi/ic_notification.png index 3bb969329c79..c27e58558a92 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/ic_notification.png and b/android/app/src/main/res/drawable-xxhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png b/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png index 697922b1e689..da1caf86e66f 100644 Binary files a/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png and b/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 630ffa310345..f533b4e8d230 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 630ffa310345..f533b4e8d230 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index d157a530d098..ee08d91ae4a7 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index d157a530d098..ee08d91ae4a7 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/assets/images/MCCGroupIcons/MCC-Airlines.svg b/assets/images/MCCGroupIcons/MCC-Airlines.svg index b707faf9857e..a316bfbc0a8a 100644 --- a/assets/images/MCCGroupIcons/MCC-Airlines.svg +++ b/assets/images/MCCGroupIcons/MCC-Airlines.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Commuter.svg b/assets/images/MCCGroupIcons/MCC-Commuter.svg index d8f808cf463b..88ad3085b37a 100644 --- a/assets/images/MCCGroupIcons/MCC-Commuter.svg +++ b/assets/images/MCCGroupIcons/MCC-Commuter.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Gas.svg b/assets/images/MCCGroupIcons/MCC-Gas.svg index b13e657a1af4..efc417df2133 100644 --- a/assets/images/MCCGroupIcons/MCC-Gas.svg +++ b/assets/images/MCCGroupIcons/MCC-Gas.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Goods.svg b/assets/images/MCCGroupIcons/MCC-Goods.svg index e3ea39f77344..fe137a8fae18 100644 --- a/assets/images/MCCGroupIcons/MCC-Goods.svg +++ b/assets/images/MCCGroupIcons/MCC-Goods.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Groceries.svg b/assets/images/MCCGroupIcons/MCC-Groceries.svg index 349154ca5496..0b766abe1ef8 100644 --- a/assets/images/MCCGroupIcons/MCC-Groceries.svg +++ b/assets/images/MCCGroupIcons/MCC-Groceries.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Hotel.svg b/assets/images/MCCGroupIcons/MCC-Hotel.svg index 04be004b24bb..94f97659fd5d 100644 --- a/assets/images/MCCGroupIcons/MCC-Hotel.svg +++ b/assets/images/MCCGroupIcons/MCC-Hotel.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Mail.svg b/assets/images/MCCGroupIcons/MCC-Mail.svg index e554fa44f37f..96dff88527e0 100644 --- a/assets/images/MCCGroupIcons/MCC-Mail.svg +++ b/assets/images/MCCGroupIcons/MCC-Mail.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Meals.svg b/assets/images/MCCGroupIcons/MCC-Meals.svg index df3672cf52a6..6cc1ac414062 100644 --- a/assets/images/MCCGroupIcons/MCC-Meals.svg +++ b/assets/images/MCCGroupIcons/MCC-Meals.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Misc.svg b/assets/images/MCCGroupIcons/MCC-Misc.svg index a4ef1615d146..d8fd071043e5 100644 --- a/assets/images/MCCGroupIcons/MCC-Misc.svg +++ b/assets/images/MCCGroupIcons/MCC-Misc.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-RentalCar.svg b/assets/images/MCCGroupIcons/MCC-RentalCar.svg index 789cb5bc3fe3..e8dd3f59954c 100644 --- a/assets/images/MCCGroupIcons/MCC-RentalCar.svg +++ b/assets/images/MCCGroupIcons/MCC-RentalCar.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Services.svg b/assets/images/MCCGroupIcons/MCC-Services.svg index 25c67065c105..265491c36eff 100644 --- a/assets/images/MCCGroupIcons/MCC-Services.svg +++ b/assets/images/MCCGroupIcons/MCC-Services.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Taxi.svg b/assets/images/MCCGroupIcons/MCC-Taxi.svg index 2cc31e4db079..6eb88b43c725 100644 --- a/assets/images/MCCGroupIcons/MCC-Taxi.svg +++ b/assets/images/MCCGroupIcons/MCC-Taxi.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/MCCGroupIcons/MCC-Utilities.svg b/assets/images/MCCGroupIcons/MCC-Utilities.svg index 27e7290bf4e5..36b7fb392d79 100644 --- a/assets/images/MCCGroupIcons/MCC-Utilities.svg +++ b/assets/images/MCCGroupIcons/MCC-Utilities.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/add-reaction.svg b/assets/images/add-reaction.svg index a576e2c84622..d70d0acec190 100644 --- a/assets/images/add-reaction.svg +++ b/assets/images/add-reaction.svg @@ -1,5 +1 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/assets/images/android.svg b/assets/images/android.svg index 0ee0daa9cc37..2599b0aed9fa 100644 --- a/assets/images/android.svg +++ b/assets/images/android.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/apple.svg b/assets/images/apple.svg index 44e7f309f7ce..69e8d3b535c1 100644 --- a/assets/images/apple.svg +++ b/assets/images/apple.svg @@ -1,21 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/arrow-right-long.svg b/assets/images/arrow-right-long.svg index 99be81fa9b36..6ab65ef779b8 100644 --- a/assets/images/arrow-right-long.svg +++ b/assets/images/arrow-right-long.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/arrow-right.svg b/assets/images/arrow-right.svg index c3bb5345d3f9..df13c75ca414 100644 --- a/assets/images/arrow-right.svg +++ b/assets/images/arrow-right.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/arrow-up.svg b/assets/images/arrow-up.svg index 60d03289d446..9183b18402d0 100644 --- a/assets/images/arrow-up.svg +++ b/assets/images/arrow-up.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/arrows-updown.svg b/assets/images/arrows-updown.svg index a1aa2c92ef87..2c8d2f788d33 100644 --- a/assets/images/arrows-updown.svg +++ b/assets/images/arrows-updown.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/admin-room.svg b/assets/images/avatars/admin-room.svg index aa25fe5bbb1d..486137d825dc 100644 --- a/assets/images/avatars/admin-room.svg +++ b/assets/images/avatars/admin-room.svg @@ -1,17 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/announce-room.svg b/assets/images/avatars/announce-room.svg index 772a113fbc33..538d78d6a8f6 100644 --- a/assets/images/avatars/announce-room.svg +++ b/assets/images/avatars/announce-room.svg @@ -1,17 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/concierge-avatar.svg b/assets/images/avatars/concierge-avatar.svg index d2a7cf31ac98..eb374a9a5a68 100644 --- a/assets/images/avatars/concierge-avatar.svg +++ b/assets/images/avatars/concierge-avatar.svg @@ -1,39 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/deleted-room.svg b/assets/images/avatars/deleted-room.svg index a39c55d0e2b5..dafcb9d95a84 100644 --- a/assets/images/avatars/deleted-room.svg +++ b/assets/images/avatars/deleted-room.svg @@ -1,15 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/domain-room.svg b/assets/images/avatars/domain-room.svg index 1f9903539049..66d1d5ab96fc 100644 --- a/assets/images/avatars/domain-room.svg +++ b/assets/images/avatars/domain-room.svg @@ -1,15 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/fallback-avatar.svg b/assets/images/avatars/fallback-avatar.svg index 0d71e0fbc210..b4584d910190 100644 --- a/assets/images/avatars/fallback-avatar.svg +++ b/assets/images/avatars/fallback-avatar.svg @@ -1,12 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/fallback-workspace-avatar.svg b/assets/images/avatars/fallback-workspace-avatar.svg index 518627fafc19..74edba02f4b5 100644 --- a/assets/images/avatars/fallback-workspace-avatar.svg +++ b/assets/images/avatars/fallback-workspace-avatar.svg @@ -1,14 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/room.png b/assets/images/avatars/room.png index dca457fbfdf7..ef5073a9a6e1 100644 Binary files a/assets/images/avatars/room.png and b/assets/images/avatars/room.png differ diff --git a/assets/images/avatars/room.svg b/assets/images/avatars/room.svg index 0db866ad9160..f3b59a00bfbd 100644 --- a/assets/images/avatars/room.svg +++ b/assets/images/avatars/room.svg @@ -1,14 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_1.svg b/assets/images/avatars/user/default-avatar_1.svg index dd4d59b26158..971389c480cf 100644 --- a/assets/images/avatars/user/default-avatar_1.svg +++ b/assets/images/avatars/user/default-avatar_1.svg @@ -1,168 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_10.svg b/assets/images/avatars/user/default-avatar_10.svg index 85979a5d1414..0f0484833f4b 100644 --- a/assets/images/avatars/user/default-avatar_10.svg +++ b/assets/images/avatars/user/default-avatar_10.svg @@ -1,192 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_11.svg b/assets/images/avatars/user/default-avatar_11.svg index eb1ce99f43c9..1e1b3ca44e82 100644 --- a/assets/images/avatars/user/default-avatar_11.svg +++ b/assets/images/avatars/user/default-avatar_11.svg @@ -1,156 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_12.svg b/assets/images/avatars/user/default-avatar_12.svg index fd5635c4e9c4..b066595be3ea 100644 --- a/assets/images/avatars/user/default-avatar_12.svg +++ b/assets/images/avatars/user/default-avatar_12.svg @@ -1,163 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_13.svg b/assets/images/avatars/user/default-avatar_13.svg index b6850c8585ca..5e9779467fe0 100644 --- a/assets/images/avatars/user/default-avatar_13.svg +++ b/assets/images/avatars/user/default-avatar_13.svg @@ -1,229 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_14.svg b/assets/images/avatars/user/default-avatar_14.svg index f888b5960b09..aa8d3b39327e 100644 --- a/assets/images/avatars/user/default-avatar_14.svg +++ b/assets/images/avatars/user/default-avatar_14.svg @@ -1,210 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_15.svg b/assets/images/avatars/user/default-avatar_15.svg index 4a20f351c0dc..1748cda659ae 100644 --- a/assets/images/avatars/user/default-avatar_15.svg +++ b/assets/images/avatars/user/default-avatar_15.svg @@ -1,173 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_16.svg b/assets/images/avatars/user/default-avatar_16.svg index a8edcb5540e1..8ca55805cec6 100644 --- a/assets/images/avatars/user/default-avatar_16.svg +++ b/assets/images/avatars/user/default-avatar_16.svg @@ -1,269 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_17.svg b/assets/images/avatars/user/default-avatar_17.svg index b1fffeb4f508..1ce8f204c2ab 100644 --- a/assets/images/avatars/user/default-avatar_17.svg +++ b/assets/images/avatars/user/default-avatar_17.svg @@ -1,164 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_18.svg b/assets/images/avatars/user/default-avatar_18.svg index d1a6c8dd2ddc..770095ecc654 100644 --- a/assets/images/avatars/user/default-avatar_18.svg +++ b/assets/images/avatars/user/default-avatar_18.svg @@ -1,155 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_19.svg b/assets/images/avatars/user/default-avatar_19.svg index 10d1a864088c..9049a504514c 100644 --- a/assets/images/avatars/user/default-avatar_19.svg +++ b/assets/images/avatars/user/default-avatar_19.svg @@ -1,171 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_2.svg b/assets/images/avatars/user/default-avatar_2.svg index 9b2b7a72f37a..dacfc5467b59 100644 --- a/assets/images/avatars/user/default-avatar_2.svg +++ b/assets/images/avatars/user/default-avatar_2.svg @@ -1,124 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_20.svg b/assets/images/avatars/user/default-avatar_20.svg index 6b5751726843..b0adfa9fc311 100644 --- a/assets/images/avatars/user/default-avatar_20.svg +++ b/assets/images/avatars/user/default-avatar_20.svg @@ -1,179 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_21.svg b/assets/images/avatars/user/default-avatar_21.svg index 6afea275de75..4764672b9e0c 100644 --- a/assets/images/avatars/user/default-avatar_21.svg +++ b/assets/images/avatars/user/default-avatar_21.svg @@ -1,163 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_22.svg b/assets/images/avatars/user/default-avatar_22.svg index da9ab029d62b..428ae4207f76 100644 --- a/assets/images/avatars/user/default-avatar_22.svg +++ b/assets/images/avatars/user/default-avatar_22.svg @@ -1,206 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_23.svg b/assets/images/avatars/user/default-avatar_23.svg index f2afe88238a7..5bef9f1c2221 100644 --- a/assets/images/avatars/user/default-avatar_23.svg +++ b/assets/images/avatars/user/default-avatar_23.svg @@ -1,196 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_24.svg b/assets/images/avatars/user/default-avatar_24.svg index 1ea472d24d83..4400546c356f 100644 --- a/assets/images/avatars/user/default-avatar_24.svg +++ b/assets/images/avatars/user/default-avatar_24.svg @@ -1,241 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_3.svg b/assets/images/avatars/user/default-avatar_3.svg index 6ddd44d51cb2..11657fcbb2b1 100644 --- a/assets/images/avatars/user/default-avatar_3.svg +++ b/assets/images/avatars/user/default-avatar_3.svg @@ -1,230 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_4.svg b/assets/images/avatars/user/default-avatar_4.svg index 203c2f990d28..cda04362b4e5 100644 --- a/assets/images/avatars/user/default-avatar_4.svg +++ b/assets/images/avatars/user/default-avatar_4.svg @@ -1,216 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_5.svg b/assets/images/avatars/user/default-avatar_5.svg index 7be508c86c6d..17b662838d43 100644 --- a/assets/images/avatars/user/default-avatar_5.svg +++ b/assets/images/avatars/user/default-avatar_5.svg @@ -1,185 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_6.svg b/assets/images/avatars/user/default-avatar_6.svg index 67774100ca12..8f5575557154 100644 --- a/assets/images/avatars/user/default-avatar_6.svg +++ b/assets/images/avatars/user/default-avatar_6.svg @@ -1,193 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_7.svg b/assets/images/avatars/user/default-avatar_7.svg index b1aad6c7c3f1..1136f086ade0 100644 --- a/assets/images/avatars/user/default-avatar_7.svg +++ b/assets/images/avatars/user/default-avatar_7.svg @@ -1,215 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_8.svg b/assets/images/avatars/user/default-avatar_8.svg index 55a4828cc824..f0e7d8f288e6 100644 --- a/assets/images/avatars/user/default-avatar_8.svg +++ b/assets/images/avatars/user/default-avatar_8.svg @@ -1,184 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/user/default-avatar_9.svg b/assets/images/avatars/user/default-avatar_9.svg index 1c65c8cc2916..4bbb156b7899 100644 --- a/assets/images/avatars/user/default-avatar_9.svg +++ b/assets/images/avatars/user/default-avatar_9.svg @@ -1,117 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_0.svg b/assets/images/avatars/workspace/default-avatar_0.svg index c942281517f4..8ebc0bbe6001 100644 --- a/assets/images/avatars/workspace/default-avatar_0.svg +++ b/assets/images/avatars/workspace/default-avatar_0.svg @@ -1,14 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_1.svg b/assets/images/avatars/workspace/default-avatar_1.svg index e6b0c71b53d4..ba5e41639b4f 100644 --- a/assets/images/avatars/workspace/default-avatar_1.svg +++ b/assets/images/avatars/workspace/default-avatar_1.svg @@ -1,14 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_2.svg b/assets/images/avatars/workspace/default-avatar_2.svg index c7d469d662ba..4257049a1146 100644 --- a/assets/images/avatars/workspace/default-avatar_2.svg +++ b/assets/images/avatars/workspace/default-avatar_2.svg @@ -1,16 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_3.svg b/assets/images/avatars/workspace/default-avatar_3.svg index 04c5d6d99372..ce049452aa83 100644 --- a/assets/images/avatars/workspace/default-avatar_3.svg +++ b/assets/images/avatars/workspace/default-avatar_3.svg @@ -1,15 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_4.svg b/assets/images/avatars/workspace/default-avatar_4.svg index e547481faefd..28c5b936cf1a 100644 --- a/assets/images/avatars/workspace/default-avatar_4.svg +++ b/assets/images/avatars/workspace/default-avatar_4.svg @@ -1,14 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_5.svg b/assets/images/avatars/workspace/default-avatar_5.svg index 6b8c654ee784..3821dd80bd89 100644 --- a/assets/images/avatars/workspace/default-avatar_5.svg +++ b/assets/images/avatars/workspace/default-avatar_5.svg @@ -1,15 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_6.svg b/assets/images/avatars/workspace/default-avatar_6.svg index c8f155995263..0eebd4c55280 100644 --- a/assets/images/avatars/workspace/default-avatar_6.svg +++ b/assets/images/avatars/workspace/default-avatar_6.svg @@ -1,15 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_7.svg b/assets/images/avatars/workspace/default-avatar_7.svg index 714725c62f2f..5563ae3941cc 100644 --- a/assets/images/avatars/workspace/default-avatar_7.svg +++ b/assets/images/avatars/workspace/default-avatar_7.svg @@ -1,14 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_8.svg b/assets/images/avatars/workspace/default-avatar_8.svg index edca9a464610..5cb8e1b6a500 100644 --- a/assets/images/avatars/workspace/default-avatar_8.svg +++ b/assets/images/avatars/workspace/default-avatar_8.svg @@ -1,15 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_9.svg b/assets/images/avatars/workspace/default-avatar_9.svg index 0cc78a5dae7e..a15970c7f27d 100644 --- a/assets/images/avatars/workspace/default-avatar_9.svg +++ b/assets/images/avatars/workspace/default-avatar_9.svg @@ -1,15 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_a.svg b/assets/images/avatars/workspace/default-avatar_a.svg index 5ea6d7a1dd0e..c29a681165d1 100644 --- a/assets/images/avatars/workspace/default-avatar_a.svg +++ b/assets/images/avatars/workspace/default-avatar_a.svg @@ -1,16 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_b.svg b/assets/images/avatars/workspace/default-avatar_b.svg index dfdef9451455..e376df224c1a 100644 --- a/assets/images/avatars/workspace/default-avatar_b.svg +++ b/assets/images/avatars/workspace/default-avatar_b.svg @@ -1,16 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_building.svg b/assets/images/avatars/workspace/default-avatar_building.svg index 69c0f0e73dc4..6175ce27d1af 100644 --- a/assets/images/avatars/workspace/default-avatar_building.svg +++ b/assets/images/avatars/workspace/default-avatar_building.svg @@ -1,14 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_c.svg b/assets/images/avatars/workspace/default-avatar_c.svg index d09cd384d458..cd8f585e4ce3 100644 --- a/assets/images/avatars/workspace/default-avatar_c.svg +++ b/assets/images/avatars/workspace/default-avatar_c.svg @@ -1,14 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_d.svg b/assets/images/avatars/workspace/default-avatar_d.svg index 2d8fb68adf58..98a87ceb2a59 100644 --- a/assets/images/avatars/workspace/default-avatar_d.svg +++ b/assets/images/avatars/workspace/default-avatar_d.svg @@ -1,15 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_e.svg b/assets/images/avatars/workspace/default-avatar_e.svg index a21981f244c5..1b8453ad6063 100644 --- a/assets/images/avatars/workspace/default-avatar_e.svg +++ b/assets/images/avatars/workspace/default-avatar_e.svg @@ -1,16 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_f.svg b/assets/images/avatars/workspace/default-avatar_f.svg index 2f1323e2c7f9..9bf793bf49dd 100644 --- a/assets/images/avatars/workspace/default-avatar_f.svg +++ b/assets/images/avatars/workspace/default-avatar_f.svg @@ -1,15 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_g.svg b/assets/images/avatars/workspace/default-avatar_g.svg index 51ff96cdfb7b..fd9212faef4e 100644 --- a/assets/images/avatars/workspace/default-avatar_g.svg +++ b/assets/images/avatars/workspace/default-avatar_g.svg @@ -1,15 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_h.svg b/assets/images/avatars/workspace/default-avatar_h.svg index ddabaa42d06b..3fd678ddb2f9 100644 --- a/assets/images/avatars/workspace/default-avatar_h.svg +++ b/assets/images/avatars/workspace/default-avatar_h.svg @@ -1,16 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_i.svg b/assets/images/avatars/workspace/default-avatar_i.svg index e9bc60254e2e..f565ea0bd01d 100644 --- a/assets/images/avatars/workspace/default-avatar_i.svg +++ b/assets/images/avatars/workspace/default-avatar_i.svg @@ -1,13 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_j.svg b/assets/images/avatars/workspace/default-avatar_j.svg index 9daf382ca8ef..35f709340cc0 100644 --- a/assets/images/avatars/workspace/default-avatar_j.svg +++ b/assets/images/avatars/workspace/default-avatar_j.svg @@ -1,13 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_k.svg b/assets/images/avatars/workspace/default-avatar_k.svg index 6018a9912f2c..b9b15c6b1b7e 100644 --- a/assets/images/avatars/workspace/default-avatar_k.svg +++ b/assets/images/avatars/workspace/default-avatar_k.svg @@ -1,16 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_l.svg b/assets/images/avatars/workspace/default-avatar_l.svg index 4fd4e62ff93a..3cd5b6ef2b19 100644 --- a/assets/images/avatars/workspace/default-avatar_l.svg +++ b/assets/images/avatars/workspace/default-avatar_l.svg @@ -1,14 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_m.svg b/assets/images/avatars/workspace/default-avatar_m.svg index e2eea24f1af9..389f8dd12f89 100644 --- a/assets/images/avatars/workspace/default-avatar_m.svg +++ b/assets/images/avatars/workspace/default-avatar_m.svg @@ -1,16 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_n.svg b/assets/images/avatars/workspace/default-avatar_n.svg index d1f036614c08..6abbde1c8b2b 100644 --- a/assets/images/avatars/workspace/default-avatar_n.svg +++ b/assets/images/avatars/workspace/default-avatar_n.svg @@ -1,15 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_o.svg b/assets/images/avatars/workspace/default-avatar_o.svg index 5f2c8e89fc89..2257cb9cd3ce 100644 --- a/assets/images/avatars/workspace/default-avatar_o.svg +++ b/assets/images/avatars/workspace/default-avatar_o.svg @@ -1,13 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_p.svg b/assets/images/avatars/workspace/default-avatar_p.svg index 4729bbc0afb9..4d09abfb08a1 100644 --- a/assets/images/avatars/workspace/default-avatar_p.svg +++ b/assets/images/avatars/workspace/default-avatar_p.svg @@ -1,15 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_q.svg b/assets/images/avatars/workspace/default-avatar_q.svg index ebdd318460e9..267e8a431346 100644 --- a/assets/images/avatars/workspace/default-avatar_q.svg +++ b/assets/images/avatars/workspace/default-avatar_q.svg @@ -1,14 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_r.svg b/assets/images/avatars/workspace/default-avatar_r.svg index 015869a190ad..76fb0e3dbebd 100644 --- a/assets/images/avatars/workspace/default-avatar_r.svg +++ b/assets/images/avatars/workspace/default-avatar_r.svg @@ -1,18 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_s.svg b/assets/images/avatars/workspace/default-avatar_s.svg index 334af3a15636..1fe3d7dd20f8 100644 --- a/assets/images/avatars/workspace/default-avatar_s.svg +++ b/assets/images/avatars/workspace/default-avatar_s.svg @@ -1,16 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_t.svg b/assets/images/avatars/workspace/default-avatar_t.svg index 9ab5962f9ba1..e4aad4cbdd4e 100644 --- a/assets/images/avatars/workspace/default-avatar_t.svg +++ b/assets/images/avatars/workspace/default-avatar_t.svg @@ -1,15 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_u.svg b/assets/images/avatars/workspace/default-avatar_u.svg index 04daa1bd4b04..b7cc5a0d50be 100644 --- a/assets/images/avatars/workspace/default-avatar_u.svg +++ b/assets/images/avatars/workspace/default-avatar_u.svg @@ -1,17 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_v.svg b/assets/images/avatars/workspace/default-avatar_v.svg index a94242b787e0..fdb2d5e3030f 100644 --- a/assets/images/avatars/workspace/default-avatar_v.svg +++ b/assets/images/avatars/workspace/default-avatar_v.svg @@ -1,17 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_w.svg b/assets/images/avatars/workspace/default-avatar_w.svg index a0756f443422..a8a04f28f976 100644 --- a/assets/images/avatars/workspace/default-avatar_w.svg +++ b/assets/images/avatars/workspace/default-avatar_w.svg @@ -1,20 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_x.svg b/assets/images/avatars/workspace/default-avatar_x.svg index 516da8c1b563..16a125595699 100644 --- a/assets/images/avatars/workspace/default-avatar_x.svg +++ b/assets/images/avatars/workspace/default-avatar_x.svg @@ -1,20 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_y.svg b/assets/images/avatars/workspace/default-avatar_y.svg index 435d0d79bc79..7bb286561588 100644 --- a/assets/images/avatars/workspace/default-avatar_y.svg +++ b/assets/images/avatars/workspace/default-avatar_y.svg @@ -1,18 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/avatars/workspace/default-avatar_z.svg b/assets/images/avatars/workspace/default-avatar_z.svg index 48bb76f4a72a..5966eb43c74b 100644 --- a/assets/images/avatars/workspace/default-avatar_z.svg +++ b/assets/images/avatars/workspace/default-avatar_z.svg @@ -1,18 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/back-left.svg b/assets/images/back-left.svg index c6730b492228..51164100ff59 100644 --- a/assets/images/back-left.svg +++ b/assets/images/back-left.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/bank.svg b/assets/images/bank.svg index 87a75b644a3a..c23f578a708a 100644 --- a/assets/images/bank.svg +++ b/assets/images/bank.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/american-express.svg b/assets/images/bankicons/american-express.svg index 0ab8383d46ed..57696764f6cf 100644 --- a/assets/images/bankicons/american-express.svg +++ b/assets/images/bankicons/american-express.svg @@ -1,23 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/bank-of-america.svg b/assets/images/bankicons/bank-of-america.svg index e4f87be611fc..7a8d43c545a1 100644 --- a/assets/images/bankicons/bank-of-america.svg +++ b/assets/images/bankicons/bank-of-america.svg @@ -1,22 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/bb-t.svg b/assets/images/bankicons/bb-t.svg index 7e7bf1f29ee4..af36426d11f3 100644 --- a/assets/images/bankicons/bb-t.svg +++ b/assets/images/bankicons/bb-t.svg @@ -1,25 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/capital-one.svg b/assets/images/bankicons/capital-one.svg index c37c8e3ca582..be25c3120d2d 100644 --- a/assets/images/bankicons/capital-one.svg +++ b/assets/images/bankicons/capital-one.svg @@ -1,53 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/charles-schwab.svg b/assets/images/bankicons/charles-schwab.svg index 181a668965da..369f17822511 100644 --- a/assets/images/bankicons/charles-schwab.svg +++ b/assets/images/bankicons/charles-schwab.svg @@ -1,58 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/chase.svg b/assets/images/bankicons/chase.svg index 70f0b911f147..2b73256b6427 100644 --- a/assets/images/bankicons/chase.svg +++ b/assets/images/bankicons/chase.svg @@ -1,13 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/citibank.svg b/assets/images/bankicons/citibank.svg index b03e1efe9bb6..e0bc5d44d9ba 100644 --- a/assets/images/bankicons/citibank.svg +++ b/assets/images/bankicons/citibank.svg @@ -1,18 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/citizens-bank.svg b/assets/images/bankicons/citizens-bank.svg index a0cdc6c1df2b..76f650f59629 100644 --- a/assets/images/bankicons/citizens-bank.svg +++ b/assets/images/bankicons/citizens-bank.svg @@ -1,47 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/discover.svg b/assets/images/bankicons/discover.svg index 75db16e4d1c1..52ead095ed48 100644 --- a/assets/images/bankicons/discover.svg +++ b/assets/images/bankicons/discover.svg @@ -1,47 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/expensify-background.png b/assets/images/bankicons/expensify-background.png index ab7b71d34e11..c06ff4c2725f 100644 Binary files a/assets/images/bankicons/expensify-background.png and b/assets/images/bankicons/expensify-background.png differ diff --git a/assets/images/bankicons/expensify.svg b/assets/images/bankicons/expensify.svg index b61773e8d838..95c07b9de619 100644 --- a/assets/images/bankicons/expensify.svg +++ b/assets/images/bankicons/expensify.svg @@ -1,18 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/fidelity.svg b/assets/images/bankicons/fidelity.svg index d49eca17c12d..bd865f145200 100644 --- a/assets/images/bankicons/fidelity.svg +++ b/assets/images/bankicons/fidelity.svg @@ -1,17 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/generic-bank-account.svg b/assets/images/bankicons/generic-bank-account.svg index 493f06b335d8..52ed3f7a1dea 100644 --- a/assets/images/bankicons/generic-bank-account.svg +++ b/assets/images/bankicons/generic-bank-account.svg @@ -1,14 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/huntington-bank.svg b/assets/images/bankicons/huntington-bank.svg index 40909a273e19..de38785b6c4e 100644 --- a/assets/images/bankicons/huntington-bank.svg +++ b/assets/images/bankicons/huntington-bank.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/navy-federal-credit-union.svg b/assets/images/bankicons/navy-federal-credit-union.svg index 898cd03768f0..4f57cedc5a6e 100644 --- a/assets/images/bankicons/navy-federal-credit-union.svg +++ b/assets/images/bankicons/navy-federal-credit-union.svg @@ -1,85 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/pnc.svg b/assets/images/bankicons/pnc.svg index 3f78dbe94f47..bf4301979bba 100644 --- a/assets/images/bankicons/pnc.svg +++ b/assets/images/bankicons/pnc.svg @@ -1,17 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/regions-bank.svg b/assets/images/bankicons/regions-bank.svg index bff045f0eb5a..f3d970e3a8a8 100644 --- a/assets/images/bankicons/regions-bank.svg +++ b/assets/images/bankicons/regions-bank.svg @@ -1,38 +1 @@ - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/suntrust.svg b/assets/images/bankicons/suntrust.svg index b5b94c105b14..184d04f1e5dd 100644 --- a/assets/images/bankicons/suntrust.svg +++ b/assets/images/bankicons/suntrust.svg @@ -1,217 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/td-bank.svg b/assets/images/bankicons/td-bank.svg index 84675de5f2bf..63dba67711a7 100644 --- a/assets/images/bankicons/td-bank.svg +++ b/assets/images/bankicons/td-bank.svg @@ -1,14 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/us-bank.svg b/assets/images/bankicons/us-bank.svg index e091ba0a6f50..cf212c8f6464 100644 --- a/assets/images/bankicons/us-bank.svg +++ b/assets/images/bankicons/us-bank.svg @@ -1,27 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bankicons/usaa.svg b/assets/images/bankicons/usaa.svg index 1e137fab626f..547209084ef4 100644 --- a/assets/images/bankicons/usaa.svg +++ b/assets/images/bankicons/usaa.svg @@ -1,36 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bill.svg b/assets/images/bill.svg index 6d9e7bd74ee6..c60dbfcc2bfe 100644 --- a/assets/images/bill.svg +++ b/assets/images/bill.svg @@ -1,11 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/bolt.svg b/assets/images/bolt.svg index c54c044a898e..c8ea05c0b447 100644 --- a/assets/images/bolt.svg +++ b/assets/images/bolt.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/briefcase.svg b/assets/images/briefcase.svg index c73734c6b124..eb35954cbb15 100644 --- a/assets/images/briefcase.svg +++ b/assets/images/briefcase.svg @@ -1,11 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/bug.svg b/assets/images/bug.svg index 8a33c1c17437..ca405caea9ac 100644 --- a/assets/images/bug.svg +++ b/assets/images/bug.svg @@ -1,12 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/building.svg b/assets/images/building.svg index 27efe759f4e4..0254da81f38f 100644 --- a/assets/images/building.svg +++ b/assets/images/building.svg @@ -1,12 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/calendar.svg b/assets/images/calendar.svg index 18885029a7c8..f855bbad61eb 100644 --- a/assets/images/calendar.svg +++ b/assets/images/calendar.svg @@ -1,16 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/camera.svg b/assets/images/camera.svg index 966a185ae5d9..b40af157c275 100644 --- a/assets/images/camera.svg +++ b/assets/images/camera.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/car.svg b/assets/images/car.svg index 7172eb1db01e..6c765f34c2da 100644 --- a/assets/images/car.svg +++ b/assets/images/car.svg @@ -1,15 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/american-express.svg b/assets/images/cardicons/american-express.svg index 9e31f7c8a08e..201cc9262394 100644 --- a/assets/images/cardicons/american-express.svg +++ b/assets/images/cardicons/american-express.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/bank-of-america.svg b/assets/images/cardicons/bank-of-america.svg index 62dd510b0649..20f180ebfdd6 100644 --- a/assets/images/cardicons/bank-of-america.svg +++ b/assets/images/cardicons/bank-of-america.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/bb-t.svg b/assets/images/cardicons/bb-t.svg index ad3676458d21..f37d1fda7f8d 100644 --- a/assets/images/cardicons/bb-t.svg +++ b/assets/images/cardicons/bb-t.svg @@ -1,33 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/capital-one.svg b/assets/images/cardicons/capital-one.svg index ee4f756e2600..6ac463b4193e 100644 --- a/assets/images/cardicons/capital-one.svg +++ b/assets/images/cardicons/capital-one.svg @@ -1,67 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/charles-schwab.svg b/assets/images/cardicons/charles-schwab.svg index 39c894042cd3..b38ac495d779 100644 --- a/assets/images/cardicons/charles-schwab.svg +++ b/assets/images/cardicons/charles-schwab.svg @@ -1,76 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/chase.svg b/assets/images/cardicons/chase.svg index 8e8ddb6d5378..7f0b8dfe62c2 100644 --- a/assets/images/cardicons/chase.svg +++ b/assets/images/cardicons/chase.svg @@ -1,15 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/citibank.svg b/assets/images/cardicons/citibank.svg index f9869aee7146..5bf890363378 100644 --- a/assets/images/cardicons/citibank.svg +++ b/assets/images/cardicons/citibank.svg @@ -1,22 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/citizens.svg b/assets/images/cardicons/citizens.svg index 3b4bf9ea1af3..ec758b3c0935 100644 --- a/assets/images/cardicons/citizens.svg +++ b/assets/images/cardicons/citizens.svg @@ -1,57 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/discover.svg b/assets/images/cardicons/discover.svg index 668e5634339d..a035727d3578 100644 --- a/assets/images/cardicons/discover.svg +++ b/assets/images/cardicons/discover.svg @@ -1,53 +1 @@ - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/expensify-card-dark.svg b/assets/images/cardicons/expensify-card-dark.svg index 4a65afeeda9d..7591a8abc29a 100644 --- a/assets/images/cardicons/expensify-card-dark.svg +++ b/assets/images/cardicons/expensify-card-dark.svg @@ -1,78 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/fidelity.svg b/assets/images/cardicons/fidelity.svg index c87f9c4aa56c..385370d00060 100644 --- a/assets/images/cardicons/fidelity.svg +++ b/assets/images/cardicons/fidelity.svg @@ -1,21 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/generic-bank-card.svg b/assets/images/cardicons/generic-bank-card.svg index f700691ac29b..6facb98577cb 100644 --- a/assets/images/cardicons/generic-bank-card.svg +++ b/assets/images/cardicons/generic-bank-card.svg @@ -1,14 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/huntington-bank.svg b/assets/images/cardicons/huntington-bank.svg index c108c7039898..6a1d50d998a6 100644 --- a/assets/images/cardicons/huntington-bank.svg +++ b/assets/images/cardicons/huntington-bank.svg @@ -1,26 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/navy-federal-credit-union.svg b/assets/images/cardicons/navy-federal-credit-union.svg index 5abc1103cce1..b67aec1a1a5f 100644 --- a/assets/images/cardicons/navy-federal-credit-union.svg +++ b/assets/images/cardicons/navy-federal-credit-union.svg @@ -1,105 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/pnc.svg b/assets/images/cardicons/pnc.svg index ae4d4aac8e41..839e630a1b85 100644 --- a/assets/images/cardicons/pnc.svg +++ b/assets/images/cardicons/pnc.svg @@ -1,18 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/regions-bank.svg b/assets/images/cardicons/regions-bank.svg index 1837ad2be41b..1f660de06ea1 100644 --- a/assets/images/cardicons/regions-bank.svg +++ b/assets/images/cardicons/regions-bank.svg @@ -1,45 +1 @@ - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/suntrust.svg b/assets/images/cardicons/suntrust.svg index 32ea5096f876..32adeea64cc6 100644 --- a/assets/images/cardicons/suntrust.svg +++ b/assets/images/cardicons/suntrust.svg @@ -1,237 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/td-bank.svg b/assets/images/cardicons/td-bank.svg index 19988e35bbbe..f75f610816d3 100644 --- a/assets/images/cardicons/td-bank.svg +++ b/assets/images/cardicons/td-bank.svg @@ -1,17 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/us-bank.svg b/assets/images/cardicons/us-bank.svg index 321b4cb755b0..9a1af062c720 100644 --- a/assets/images/cardicons/us-bank.svg +++ b/assets/images/cardicons/us-bank.svg @@ -1,32 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cardicons/usaa.svg b/assets/images/cardicons/usaa.svg index bb634f64e658..d58db9e750c8 100644 --- a/assets/images/cardicons/usaa.svg +++ b/assets/images/cardicons/usaa.svg @@ -1,40 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/cash.svg b/assets/images/cash.svg index 7780e9be9eec..ff4f3fc69664 100644 --- a/assets/images/cash.svg +++ b/assets/images/cash.svg @@ -1,10 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/chair.svg b/assets/images/chair.svg index d8864d205b33..4ee59941e204 100644 --- a/assets/images/chair.svg +++ b/assets/images/chair.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/chatbubble.svg b/assets/images/chatbubble.svg index 23bc4b429ea0..e6863cbd502a 100644 --- a/assets/images/chatbubble.svg +++ b/assets/images/chatbubble.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/chatbubbles.svg b/assets/images/chatbubbles.svg index 6194c43e631e..9aca0a7dd8ed 100644 --- a/assets/images/chatbubbles.svg +++ b/assets/images/chatbubbles.svg @@ -1,12 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/checkmark.svg b/assets/images/checkmark.svg index 7ac28068ff56..8b50271e4419 100644 --- a/assets/images/checkmark.svg +++ b/assets/images/checkmark.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/close.svg b/assets/images/close.svg index f36b0714385a..849fce4abb8e 100644 --- a/assets/images/close.svg +++ b/assets/images/close.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/closed-sign.svg b/assets/images/closed-sign.svg index 6454e31cd35e..653abe86d2fc 100644 --- a/assets/images/closed-sign.svg +++ b/assets/images/closed-sign.svg @@ -1,12 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/collapse.svg b/assets/images/collapse.svg index 9b254182dbe2..ea917e4ee827 100644 --- a/assets/images/collapse.svg +++ b/assets/images/collapse.svg @@ -1,8 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/concierge.svg b/assets/images/concierge.svg index 2ed0becb61da..70c91bf3fbb5 100644 --- a/assets/images/concierge.svg +++ b/assets/images/concierge.svg @@ -1,12 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/connect.svg b/assets/images/connect.svg index e30231e46840..3460bafd5596 100644 --- a/assets/images/connect.svg +++ b/assets/images/connect.svg @@ -1,12 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/copy.svg b/assets/images/copy.svg index 87707f0be6c1..fa53b2b20ffc 100644 --- a/assets/images/copy.svg +++ b/assets/images/copy.svg @@ -1,7 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/creditcard.svg b/assets/images/creditcard.svg index f174472a63c4..d2fd84adaf58 100644 --- a/assets/images/creditcard.svg +++ b/assets/images/creditcard.svg @@ -1,7 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/document.svg b/assets/images/document.svg index 8ef6c5ec10ce..a4f3c248c266 100644 --- a/assets/images/document.svg +++ b/assets/images/document.svg @@ -1,8 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/dot-indicator-unfilled.svg b/assets/images/dot-indicator-unfilled.svg index ae131b1c2cba..d74c4dff1592 100644 --- a/assets/images/dot-indicator-unfilled.svg +++ b/assets/images/dot-indicator-unfilled.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/dot-indicator.svg b/assets/images/dot-indicator.svg index 9d808d91babe..b6565407807f 100644 --- a/assets/images/dot-indicator.svg +++ b/assets/images/dot-indicator.svg @@ -1,6 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/down.svg b/assets/images/down.svg index e4bb5bea9b4d..28d80840f322 100644 --- a/assets/images/down.svg +++ b/assets/images/down.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/download.svg b/assets/images/download.svg index 581f504611cc..d3d3b554b990 100644 --- a/assets/images/download.svg +++ b/assets/images/download.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/drag-and-drop.svg b/assets/images/drag-and-drop.svg index 8e9251ff3ae5..154ad34f061d 100644 --- a/assets/images/drag-and-drop.svg +++ b/assets/images/drag-and-drop.svg @@ -1,17 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/drag-handles.svg b/assets/images/drag-handles.svg index ec4fc4ccc672..9951d95cb612 100644 --- a/assets/images/drag-handles.svg +++ b/assets/images/drag-handles.svg @@ -1,7 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/eReceipt-BGImage.svg b/assets/images/eReceipt-BGImage.svg index 48aa548ad6ee..bf89dd853ac8 100644 --- a/assets/images/eReceipt-BGImage.svg +++ b/assets/images/eReceipt-BGImage.svg @@ -1,314 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/eReceiptBGs/eReceiptBG_blue.png b/assets/images/eReceiptBGs/eReceiptBG_blue.png index f317b72dc4fc..602efe533162 100644 Binary files a/assets/images/eReceiptBGs/eReceiptBG_blue.png and b/assets/images/eReceiptBGs/eReceiptBG_blue.png differ diff --git a/assets/images/eReceiptBGs/eReceiptBG_green.png b/assets/images/eReceiptBGs/eReceiptBG_green.png index 55fe8886bca9..cf7ae9c58d87 100644 Binary files a/assets/images/eReceiptBGs/eReceiptBG_green.png and b/assets/images/eReceiptBGs/eReceiptBG_green.png differ diff --git a/assets/images/eReceiptBGs/eReceiptBG_navy.png b/assets/images/eReceiptBGs/eReceiptBG_navy.png index 2b9616d42c11..0f9449bb26a0 100644 Binary files a/assets/images/eReceiptBGs/eReceiptBG_navy.png and b/assets/images/eReceiptBGs/eReceiptBG_navy.png differ diff --git a/assets/images/eReceiptBGs/eReceiptBG_pink.png b/assets/images/eReceiptBGs/eReceiptBG_pink.png index 41b6492c3a35..0fd2ebdd5c6e 100644 Binary files a/assets/images/eReceiptBGs/eReceiptBG_pink.png and b/assets/images/eReceiptBGs/eReceiptBG_pink.png differ diff --git a/assets/images/eReceiptBGs/eReceiptBG_tangerine.png b/assets/images/eReceiptBGs/eReceiptBG_tangerine.png index 00a8cd6dd612..3f7e61b04969 100644 Binary files a/assets/images/eReceiptBGs/eReceiptBG_tangerine.png and b/assets/images/eReceiptBGs/eReceiptBG_tangerine.png differ diff --git a/assets/images/eReceiptBGs/eReceiptBG_yellow.png b/assets/images/eReceiptBGs/eReceiptBG_yellow.png index 7eb9d1f87fa6..c0c6df3d5748 100644 Binary files a/assets/images/eReceiptBGs/eReceiptBG_yellow.png and b/assets/images/eReceiptBGs/eReceiptBG_yellow.png differ diff --git a/assets/images/eReceiptIcon.svg b/assets/images/eReceiptIcon.svg index e54c3a106a48..9b0612a03231 100644 --- a/assets/images/eReceiptIcon.svg +++ b/assets/images/eReceiptIcon.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/eReceipt_background.svg b/assets/images/eReceipt_background.svg index 5070ed3b2f24..6fadeb352d9b 100644 --- a/assets/images/eReceipt_background.svg +++ b/assets/images/eReceipt_background.svg @@ -1,1635 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/emoji.svg b/assets/images/emoji.svg index 431f46962bd7..3b419b40fdf2 100644 --- a/assets/images/emoji.svg +++ b/assets/images/emoji.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/add-emoji.svg b/assets/images/emojiCategoryIcons/add-emoji.svg index 5cec67508e4b..e7dd57a0c282 100644 --- a/assets/images/emojiCategoryIcons/add-emoji.svg +++ b/assets/images/emojiCategoryIcons/add-emoji.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/calendar.svg b/assets/images/emojiCategoryIcons/calendar.svg index 18885029a7c8..f855bbad61eb 100644 --- a/assets/images/emojiCategoryIcons/calendar.svg +++ b/assets/images/emojiCategoryIcons/calendar.svg @@ -1,16 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/car.svg b/assets/images/emojiCategoryIcons/car.svg index e5cde58b2615..6c765f34c2da 100644 --- a/assets/images/emojiCategoryIcons/car.svg +++ b/assets/images/emojiCategoryIcons/car.svg @@ -1,15 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/flag.svg b/assets/images/emojiCategoryIcons/flag.svg index e72787c3665b..5a57ac004991 100644 --- a/assets/images/emojiCategoryIcons/flag.svg +++ b/assets/images/emojiCategoryIcons/flag.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/hamburger.svg b/assets/images/emojiCategoryIcons/hamburger.svg index 52945988effc..adc0ff0ed089 100644 --- a/assets/images/emojiCategoryIcons/hamburger.svg +++ b/assets/images/emojiCategoryIcons/hamburger.svg @@ -1,8 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/heart.svg b/assets/images/emojiCategoryIcons/heart.svg index 95e73f329cfa..761bf2770a1e 100644 --- a/assets/images/emojiCategoryIcons/heart.svg +++ b/assets/images/emojiCategoryIcons/heart.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/light-bulb.svg b/assets/images/emojiCategoryIcons/light-bulb.svg index 0e6a33c041df..b3b8927786c3 100644 --- a/assets/images/emojiCategoryIcons/light-bulb.svg +++ b/assets/images/emojiCategoryIcons/light-bulb.svg @@ -1,12 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/peace-sign.svg b/assets/images/emojiCategoryIcons/peace-sign.svg index ab76642fc48d..1015298d7b2c 100644 --- a/assets/images/emojiCategoryIcons/peace-sign.svg +++ b/assets/images/emojiCategoryIcons/peace-sign.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/plane.svg b/assets/images/emojiCategoryIcons/plane.svg index 17aca931f8a3..7c989b2e802b 100644 --- a/assets/images/emojiCategoryIcons/plane.svg +++ b/assets/images/emojiCategoryIcons/plane.svg @@ -1,8 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/plant.svg b/assets/images/emojiCategoryIcons/plant.svg index a17ed231e1df..b28f95109299 100644 --- a/assets/images/emojiCategoryIcons/plant.svg +++ b/assets/images/emojiCategoryIcons/plant.svg @@ -1,8 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/emojiCategoryIcons/soccer-ball.svg b/assets/images/emojiCategoryIcons/soccer-ball.svg index 40fa05516a11..d5eeb4bf9b7a 100644 --- a/assets/images/emojiCategoryIcons/soccer-ball.svg +++ b/assets/images/emojiCategoryIcons/soccer-ball.svg @@ -1,11 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/empty-state_background-fade.png b/assets/images/empty-state_background-fade.png index 816ff7343310..e449ca1efa23 100644 Binary files a/assets/images/empty-state_background-fade.png and b/assets/images/empty-state_background-fade.png differ diff --git a/assets/images/emptystate__routepending.svg b/assets/images/emptystate__routepending.svg index 7646917046cc..90f3296d37d6 100644 --- a/assets/images/emptystate__routepending.svg +++ b/assets/images/emptystate__routepending.svg @@ -1,43 +1 @@ - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/example-check-image-en.png b/assets/images/example-check-image-en.png index 2a4f32ade69a..903618776cdf 100644 Binary files a/assets/images/example-check-image-en.png and b/assets/images/example-check-image-en.png differ diff --git a/assets/images/example-check-image-es.png b/assets/images/example-check-image-es.png index 435151525a7e..de695a43833d 100644 Binary files a/assets/images/example-check-image-es.png and b/assets/images/example-check-image-es.png differ diff --git a/assets/images/exclamation.svg b/assets/images/exclamation.svg index ab70c6f3ca8f..8c55e9667a46 100644 --- a/assets/images/exclamation.svg +++ b/assets/images/exclamation.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/exit.svg b/assets/images/exit.svg index 07792548c8a5..8e466eea3608 100644 --- a/assets/images/exit.svg +++ b/assets/images/exit.svg @@ -1,13 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/expand.svg b/assets/images/expand.svg index 0d5d520e444a..25d79d849b58 100644 --- a/assets/images/expand.svg +++ b/assets/images/expand.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/expensify-app-icon.svg b/assets/images/expensify-app-icon.svg index a0adfe7dd952..f7ae8b487027 100644 --- a/assets/images/expensify-app-icon.svg +++ b/assets/images/expensify-app-icon.svg @@ -1,18 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/expensify-card.svg b/assets/images/expensify-card.svg index f95e3ed20288..52f55778b2bd 100644 --- a/assets/images/expensify-card.svg +++ b/assets/images/expensify-card.svg @@ -1,69 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/expensify-footer-logo-vertical.svg b/assets/images/expensify-footer-logo-vertical.svg index 58dc05ad2944..9cd5e26cc8f2 100644 --- a/assets/images/expensify-footer-logo-vertical.svg +++ b/assets/images/expensify-footer-logo-vertical.svg @@ -1,30 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/expensify-footer-logo.svg b/assets/images/expensify-footer-logo.svg index e664651b84fd..9e3f837f7365 100644 --- a/assets/images/expensify-footer-logo.svg +++ b/assets/images/expensify-footer-logo.svg @@ -1,30 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/expensify-logo--adhoc.svg b/assets/images/expensify-logo--adhoc.svg index 63b6f896e3a5..70d9526ee5f8 100644 --- a/assets/images/expensify-logo--adhoc.svg +++ b/assets/images/expensify-logo--adhoc.svg @@ -1,47 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/expensify-logo--dev.svg b/assets/images/expensify-logo--dev.svg index f68fafbad806..92dad9f6d530 100644 --- a/assets/images/expensify-logo--dev.svg +++ b/assets/images/expensify-logo--dev.svg @@ -1,42 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/expensify-logo--staging.svg b/assets/images/expensify-logo--staging.svg index 12b0b9bf6e79..22cbba2368e5 100644 --- a/assets/images/expensify-logo--staging.svg +++ b/assets/images/expensify-logo--staging.svg @@ -1,45 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/expensify-logo-round-clearspace.png b/assets/images/expensify-logo-round-clearspace.png index da0f19dda770..2ccb9a39752d 100644 Binary files a/assets/images/expensify-logo-round-clearspace.png and b/assets/images/expensify-logo-round-clearspace.png differ diff --git a/assets/images/expensify-logo-round-dark.png b/assets/images/expensify-logo-round-dark.png index 106d28274fd3..a1de022315d9 100644 Binary files a/assets/images/expensify-logo-round-dark.png and b/assets/images/expensify-logo-round-dark.png differ diff --git a/assets/images/expensify-logo-round-transparent.png b/assets/images/expensify-logo-round-transparent.png index cfad187e38ef..b2ff00112590 100644 Binary files a/assets/images/expensify-logo-round-transparent.png and b/assets/images/expensify-logo-round-transparent.png differ diff --git a/assets/images/expensify-logo-round.png b/assets/images/expensify-logo-round.png index 59d29ed09530..bc97e70dd83b 100644 Binary files a/assets/images/expensify-logo-round.png and b/assets/images/expensify-logo-round.png differ diff --git a/assets/images/expensify-wordmark.svg b/assets/images/expensify-wordmark.svg index 69fbcbae6743..da9c93f21e8c 100644 --- a/assets/images/expensify-wordmark.svg +++ b/assets/images/expensify-wordmark.svg @@ -1,23 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/expensify_wordmark_white.svg b/assets/images/expensify_wordmark_white.svg index 1ad7640b2602..008cfe625591 100644 --- a/assets/images/expensify_wordmark_white.svg +++ b/assets/images/expensify_wordmark_white.svg @@ -1,26 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/expensifycard.svg b/assets/images/expensifycard.svg index c146a4709e94..8e20d27af48c 100644 --- a/assets/images/expensifycard.svg +++ b/assets/images/expensifycard.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/eye-disabled.svg b/assets/images/eye-disabled.svg index 4055ba7e78a0..2537205a19da 100644 --- a/assets/images/eye-disabled.svg +++ b/assets/images/eye-disabled.svg @@ -1,13 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/eye.svg b/assets/images/eye.svg index dd0a4fd532b8..79c9f18e68f9 100644 --- a/assets/images/eye.svg +++ b/assets/images/eye.svg @@ -1,12 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/flag.svg b/assets/images/flag.svg index 9b6737459fbd..5a57ac004991 100644 --- a/assets/images/flag.svg +++ b/assets/images/flag.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/flag_level_01.svg b/assets/images/flag_level_01.svg index a4259deb0d2c..e77b84819777 100644 --- a/assets/images/flag_level_01.svg +++ b/assets/images/flag_level_01.svg @@ -1,13 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/flag_level_02.svg b/assets/images/flag_level_02.svg index 9d7010dbb7f9..c31ac07e2886 100644 --- a/assets/images/flag_level_02.svg +++ b/assets/images/flag_level_02.svg @@ -1,13 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/flag_level_03.svg b/assets/images/flag_level_03.svg index 14fc80792cc2..a156e13c2c74 100644 --- a/assets/images/flag_level_03.svg +++ b/assets/images/flag_level_03.svg @@ -1,13 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/gallery.svg b/assets/images/gallery.svg index 21eb78059329..6d061e77c658 100644 --- a/assets/images/gallery.svg +++ b/assets/images/gallery.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/gear.svg b/assets/images/gear.svg index f7090075e0a4..234d60a31ae5 100644 --- a/assets/images/gear.svg +++ b/assets/images/gear.svg @@ -1,18 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/globe.svg b/assets/images/globe.svg index f057bd36379b..b6f802d72435 100644 --- a/assets/images/globe.svg +++ b/assets/images/globe.svg @@ -1,13 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/google-meet.svg b/assets/images/google-meet.svg index 980cd102f67a..8def88aa6edc 100644 --- a/assets/images/google-meet.svg +++ b/assets/images/google-meet.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/hand.svg b/assets/images/hand.svg index e9a56d260ed0..047af1d67ed8 100644 --- a/assets/images/hand.svg +++ b/assets/images/hand.svg @@ -1,196 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/hashtag.svg b/assets/images/hashtag.svg index 86324ffcdba8..00d0a253659a 100644 --- a/assets/images/hashtag.svg +++ b/assets/images/hashtag.svg @@ -1,16 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/heart.svg b/assets/images/heart.svg index 95e73f329cfa..761bf2770a1e 100644 --- a/assets/images/heart.svg +++ b/assets/images/heart.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/history.svg b/assets/images/history.svg index 5eefb04b480d..09be03108312 100644 --- a/assets/images/history.svg +++ b/assets/images/history.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/home-background--desktop.svg b/assets/images/home-background--desktop.svg index c577609efb3b..4d1c18fb3ba7 100644 --- a/assets/images/home-background--desktop.svg +++ b/assets/images/home-background--desktop.svg @@ -1,8835 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/home-background--mobile.svg b/assets/images/home-background--mobile.svg index 0af3aac59146..7c4d4d8289b7 100644 --- a/assets/images/home-background--mobile.svg +++ b/assets/images/home-background--mobile.svg @@ -1,6556 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/home-fade-gradient--mobile.svg b/assets/images/home-fade-gradient--mobile.svg index ca03eb3323af..0b24b678a2e6 100644 --- a/assets/images/home-fade-gradient--mobile.svg +++ b/assets/images/home-fade-gradient--mobile.svg @@ -1,14 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/home-fade-gradient.svg b/assets/images/home-fade-gradient.svg index 6aada6633a8b..bfe04d545364 100644 --- a/assets/images/home-fade-gradient.svg +++ b/assets/images/home-fade-gradient.svg @@ -1,14 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/hourglass.svg b/assets/images/hourglass.svg index b04dc3589d73..a04084dca1f5 100644 --- a/assets/images/hourglass.svg +++ b/assets/images/hourglass.svg @@ -1,13 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/image-crop-circle-mask.svg b/assets/images/image-crop-circle-mask.svg index 8edded23218d..491adb0a7248 100644 --- a/assets/images/image-crop-circle-mask.svg +++ b/assets/images/image-crop-circle-mask.svg @@ -1,23 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/image-crop-square-mask.svg b/assets/images/image-crop-square-mask.svg index 050998d576f8..947c99987f9b 100644 --- a/assets/images/image-crop-square-mask.svg +++ b/assets/images/image-crop-square-mask.svg @@ -1,24 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/info.svg b/assets/images/info.svg index 7446622ab044..da3fa688b44e 100644 --- a/assets/images/info.svg +++ b/assets/images/info.svg @@ -1,12 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/invoice.svg b/assets/images/invoice.svg index 618aba9be614..0ea93b1b8e2b 100644 --- a/assets/images/invoice.svg +++ b/assets/images/invoice.svg @@ -1,11 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/key.svg b/assets/images/key.svg index 595a1541ce5e..b25879191ac9 100644 --- a/assets/images/key.svg +++ b/assets/images/key.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/keyboard.svg b/assets/images/keyboard.svg index 16df73f0026b..f6040430fcb4 100644 --- a/assets/images/keyboard.svg +++ b/assets/images/keyboard.svg @@ -1,20 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/link-copy.svg b/assets/images/link-copy.svg index e153fbc49795..a23593e79eab 100644 --- a/assets/images/link-copy.svg +++ b/assets/images/link-copy.svg @@ -1,13 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/link.svg b/assets/images/link.svg index a284470a5c79..abfac3a2d180 100644 --- a/assets/images/link.svg +++ b/assets/images/link.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/location.svg b/assets/images/location.svg index ad8102051e26..fa00dafbcbfe 100644 --- a/assets/images/location.svg +++ b/assets/images/location.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/lock.svg b/assets/images/lock.svg index ab4bafc4724d..9b4c17c6dea9 100644 --- a/assets/images/lock.svg +++ b/assets/images/lock.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/luggage.svg b/assets/images/luggage.svg index 65edc1f31fb3..46453be7c392 100644 --- a/assets/images/luggage.svg +++ b/assets/images/luggage.svg @@ -1,13 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/magnifying-glass.svg b/assets/images/magnifying-glass.svg index c0e2465f0308..d8457b870bc8 100644 --- a/assets/images/magnifying-glass.svg +++ b/assets/images/magnifying-glass.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/mail.svg b/assets/images/mail.svg index 1a3d788288b9..9963b0cfb84a 100644 --- a/assets/images/mail.svg +++ b/assets/images/mail.svg @@ -1,8 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/megaphone.svg b/assets/images/megaphone.svg index a10a6d838558..45e905d3f3e3 100644 --- a/assets/images/megaphone.svg +++ b/assets/images/megaphone.svg @@ -1,10 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/menu.svg b/assets/images/menu.svg index 9995bb6d521b..c0a7e3aa7b68 100644 --- a/assets/images/menu.svg +++ b/assets/images/menu.svg @@ -1,8 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/money-bag.svg b/assets/images/money-bag.svg index e691635f9544..e02865e5aff9 100644 --- a/assets/images/money-bag.svg +++ b/assets/images/money-bag.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/money-circle.svg b/assets/images/money-circle.svg index f6c66e0a6dfb..28783d0f78a7 100644 --- a/assets/images/money-circle.svg +++ b/assets/images/money-circle.svg @@ -1,13 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/money-stack.svg b/assets/images/money-stack.svg index b9a93c76198c..587180bb11d6 100644 --- a/assets/images/money-stack.svg +++ b/assets/images/money-stack.svg @@ -1,125 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/monitor.svg b/assets/images/monitor.svg index a8b99721a9cc..d5c74474b524 100644 --- a/assets/images/monitor.svg +++ b/assets/images/monitor.svg @@ -1,11 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/new-expensify-adhoc.svg b/assets/images/new-expensify-adhoc.svg index d3a926a097ec..f2603555fc38 100644 --- a/assets/images/new-expensify-adhoc.svg +++ b/assets/images/new-expensify-adhoc.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/new-expensify-dark.svg b/assets/images/new-expensify-dark.svg index ad34f1d9dfce..01967175139b 100644 --- a/assets/images/new-expensify-dark.svg +++ b/assets/images/new-expensify-dark.svg @@ -1,10 +1 @@ - - - + \ No newline at end of file diff --git a/assets/images/new-expensify-dev.svg b/assets/images/new-expensify-dev.svg index 423fe56c98b7..9c11ed02433c 100644 --- a/assets/images/new-expensify-dev.svg +++ b/assets/images/new-expensify-dev.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/new-expensify-stg.svg b/assets/images/new-expensify-stg.svg index 61852bbb1932..f151d7c4c130 100644 --- a/assets/images/new-expensify-stg.svg +++ b/assets/images/new-expensify-stg.svg @@ -1,48 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/new-expensify.svg b/assets/images/new-expensify.svg index dc8273e6aa99..38276ecd9385 100644 --- a/assets/images/new-expensify.svg +++ b/assets/images/new-expensify.svg @@ -1,29 +1 @@ - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/new-window.svg b/assets/images/new-window.svg index c1b9991558b7..5f8212a20197 100644 --- a/assets/images/new-window.svg +++ b/assets/images/new-window.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/new-workspace.svg b/assets/images/new-workspace.svg index 62f4717108e9..e50136e34355 100644 --- a/assets/images/new-workspace.svg +++ b/assets/images/new-workspace.svg @@ -1,19 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/offline-cloud.svg b/assets/images/offline-cloud.svg index ae8305e52934..cb789b6ad5be 100644 --- a/assets/images/offline-cloud.svg +++ b/assets/images/offline-cloud.svg @@ -1,8 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/offline.svg b/assets/images/offline.svg index 21cb29d382c0..daf4370ab7c1 100644 --- a/assets/images/offline.svg +++ b/assets/images/offline.svg @@ -1,10 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/paperclip.svg b/assets/images/paperclip.svg index 29760284c687..6421040301a0 100644 --- a/assets/images/paperclip.svg +++ b/assets/images/paperclip.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/paycheck.svg b/assets/images/paycheck.svg index d54602d8b11b..f607e34833a1 100644 --- a/assets/images/paycheck.svg +++ b/assets/images/paycheck.svg @@ -1,15 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/paypal.svg b/assets/images/paypal.svg index 370a55b41284..4aaccc60c21c 100644 --- a/assets/images/paypal.svg +++ b/assets/images/paypal.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/pencil.svg b/assets/images/pencil.svg index 8673005d818a..2f1f8354d3b5 100644 --- a/assets/images/pencil.svg +++ b/assets/images/pencil.svg @@ -1,9 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/phone.svg b/assets/images/phone.svg index 38cc8c1319dc..22492a8a81c2 100644 --- a/assets/images/phone.svg +++ b/assets/images/phone.svg @@ -1,8 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/pin.svg b/assets/images/pin.svg index 048f95cbc929..7d2e62687344 100644 --- a/assets/images/pin.svg +++ b/assets/images/pin.svg @@ -1,9 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/plus.svg b/assets/images/plus.svg index 59a0b803dde4..9d658b6bbea7 100644 --- a/assets/images/plus.svg +++ b/assets/images/plus.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/printer.svg b/assets/images/printer.svg index ce0d725d4251..b231412ddebe 100644 --- a/assets/images/printer.svg +++ b/assets/images/printer.svg @@ -1,12 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/abracadabra.svg b/assets/images/product-illustrations/abracadabra.svg index dba7336cd11d..3eb20add6066 100644 --- a/assets/images/product-illustrations/abracadabra.svg +++ b/assets/images/product-illustrations/abracadabra.svg @@ -1,710 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/bank-arrow--pink.svg b/assets/images/product-illustrations/bank-arrow--pink.svg index c561bfd2790d..a7d6668d4d9d 100644 --- a/assets/images/product-illustrations/bank-arrow--pink.svg +++ b/assets/images/product-illustrations/bank-arrow--pink.svg @@ -1,43 +1 @@ - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/bank-mouse--green.svg b/assets/images/product-illustrations/bank-mouse--green.svg index 99dfd1718c1e..b7cfc91bbd46 100644 --- a/assets/images/product-illustrations/bank-mouse--green.svg +++ b/assets/images/product-illustrations/bank-mouse--green.svg @@ -1,50 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/bank-user--green.svg b/assets/images/product-illustrations/bank-user--green.svg index 676d05c3bc0c..35029902801c 100644 --- a/assets/images/product-illustrations/bank-user--green.svg +++ b/assets/images/product-illustrations/bank-user--green.svg @@ -1,48 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/concierge--blue.svg b/assets/images/product-illustrations/concierge--blue.svg index d1d3fede1f64..facba4991d05 100644 --- a/assets/images/product-illustrations/concierge--blue.svg +++ b/assets/images/product-illustrations/concierge--blue.svg @@ -1,20 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/concierge--exclamation.svg b/assets/images/product-illustrations/concierge--exclamation.svg index ed4b8fd3f533..8033d84b1a5a 100644 --- a/assets/images/product-illustrations/concierge--exclamation.svg +++ b/assets/images/product-illustrations/concierge--exclamation.svg @@ -1,26 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/credit-cards--blue.svg b/assets/images/product-illustrations/credit-cards--blue.svg index 008dbd20be30..51f18537af1a 100644 --- a/assets/images/product-illustrations/credit-cards--blue.svg +++ b/assets/images/product-illustrations/credit-cards--blue.svg @@ -1,31 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/gps-track--orange.svg b/assets/images/product-illustrations/gps-track--orange.svg index 400958af31ca..1c13895e27fb 100644 --- a/assets/images/product-illustrations/gps-track--orange.svg +++ b/assets/images/product-illustrations/gps-track--orange.svg @@ -1,24 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/home-illustration-hands.svg b/assets/images/product-illustrations/home-illustration-hands.svg index 9a70d8cc6363..75ee67189126 100644 --- a/assets/images/product-illustrations/home-illustration-hands.svg +++ b/assets/images/product-illustrations/home-illustration-hands.svg @@ -1,545 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/invoice--orange.svg b/assets/images/product-illustrations/invoice--orange.svg index aebd50660662..0512cfd2959f 100644 --- a/assets/images/product-illustrations/invoice--orange.svg +++ b/assets/images/product-illustrations/invoice--orange.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/jewel-box--blue.svg b/assets/images/product-illustrations/jewel-box--blue.svg index b9d6a084bcb9..c137a0063b5f 100644 --- a/assets/images/product-illustrations/jewel-box--blue.svg +++ b/assets/images/product-illustrations/jewel-box--blue.svg @@ -1,45 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/jewel-box--green.svg b/assets/images/product-illustrations/jewel-box--green.svg index ba1cade3dcc3..c4c73385b636 100644 --- a/assets/images/product-illustrations/jewel-box--green.svg +++ b/assets/images/product-illustrations/jewel-box--green.svg @@ -1,45 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/jewel-box--pink.svg b/assets/images/product-illustrations/jewel-box--pink.svg index dd58151c9132..d42baf0c5d8b 100644 --- a/assets/images/product-illustrations/jewel-box--pink.svg +++ b/assets/images/product-illustrations/jewel-box--pink.svg @@ -1,45 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/jewel-box--yellow.svg b/assets/images/product-illustrations/jewel-box--yellow.svg index 858d5b666886..3f40365bd0f1 100644 --- a/assets/images/product-illustrations/jewel-box--yellow.svg +++ b/assets/images/product-illustrations/jewel-box--yellow.svg @@ -1,45 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/magic-code.svg b/assets/images/product-illustrations/magic-code.svg index 7f26cf51874c..f623857f1546 100644 --- a/assets/images/product-illustrations/magic-code.svg +++ b/assets/images/product-illustrations/magic-code.svg @@ -1,931 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/money-envelope--blue.svg b/assets/images/product-illustrations/money-envelope--blue.svg index 199489af882f..78ac4032daf5 100644 --- a/assets/images/product-illustrations/money-envelope--blue.svg +++ b/assets/images/product-illustrations/money-envelope--blue.svg @@ -1,30 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/money-mouse--pink.svg b/assets/images/product-illustrations/money-mouse--pink.svg index 72c21fc46754..ae67d1f8c2b6 100644 --- a/assets/images/product-illustrations/money-mouse--pink.svg +++ b/assets/images/product-illustrations/money-mouse--pink.svg @@ -1,30 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/receipt--yellow.svg b/assets/images/product-illustrations/receipt--yellow.svg index f40f3e0a5aa9..c5e2ea9c07e3 100644 --- a/assets/images/product-illustrations/receipt--yellow.svg +++ b/assets/images/product-illustrations/receipt--yellow.svg @@ -1,20 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/receipts-search--yellow.svg b/assets/images/product-illustrations/receipts-search--yellow.svg index 9db0cc47c236..f40061c034d5 100644 --- a/assets/images/product-illustrations/receipts-search--yellow.svg +++ b/assets/images/product-illustrations/receipts-search--yellow.svg @@ -1,55 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/rocket--blue.svg b/assets/images/product-illustrations/rocket--blue.svg index b59e8a28c8ca..5fec253cc4e1 100644 --- a/assets/images/product-illustrations/rocket--blue.svg +++ b/assets/images/product-illustrations/rocket--blue.svg @@ -1,286 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/rocket--orange.svg b/assets/images/product-illustrations/rocket--orange.svg index a3bb9a67fb7d..0e8078e926f4 100644 --- a/assets/images/product-illustrations/rocket--orange.svg +++ b/assets/images/product-illustrations/rocket--orange.svg @@ -1,87 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/safe.svg b/assets/images/product-illustrations/safe.svg index db2ac0707f7f..70e6d116daa9 100644 --- a/assets/images/product-illustrations/safe.svg +++ b/assets/images/product-illustrations/safe.svg @@ -1,119 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/simple-illustration__smartscan.svg b/assets/images/product-illustrations/simple-illustration__smartscan.svg index 34d1fadfaa3b..688133368956 100644 --- a/assets/images/product-illustrations/simple-illustration__smartscan.svg +++ b/assets/images/product-illustrations/simple-illustration__smartscan.svg @@ -1,21 +1 @@ - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/tada--blue.svg b/assets/images/product-illustrations/tada--blue.svg index 5430863ca145..c0f2b3f104eb 100644 --- a/assets/images/product-illustrations/tada--blue.svg +++ b/assets/images/product-illustrations/tada--blue.svg @@ -1,54 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/tada--yellow.svg b/assets/images/product-illustrations/tada--yellow.svg index 037baef7defe..b21887899768 100644 --- a/assets/images/product-illustrations/tada--yellow.svg +++ b/assets/images/product-illustrations/tada--yellow.svg @@ -1,56 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/todd-behind-cloud.svg b/assets/images/product-illustrations/todd-behind-cloud.svg index 6281ce0ef727..65911b275499 100644 --- a/assets/images/product-illustrations/todd-behind-cloud.svg +++ b/assets/images/product-illustrations/todd-behind-cloud.svg @@ -1,58 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/profile.svg b/assets/images/profile.svg index 9f41da0e141f..84edb572b236 100644 --- a/assets/images/profile.svg +++ b/assets/images/profile.svg @@ -1,10 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/qrcode.svg b/assets/images/qrcode.svg index 8851a69a03d7..f506a944d54e 100644 --- a/assets/images/qrcode.svg +++ b/assets/images/qrcode.svg @@ -1,5 +1 @@ - - - + \ No newline at end of file diff --git a/assets/images/question-mark-circle.svg b/assets/images/question-mark-circle.svg index ae318f655750..cd42f3c118d3 100644 --- a/assets/images/question-mark-circle.svg +++ b/assets/images/question-mark-circle.svg @@ -1,12 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/receipt-doc.png b/assets/images/receipt-doc.png index 773bfaed73ad..4c9ced0356a3 100644 Binary files a/assets/images/receipt-doc.png and b/assets/images/receipt-doc.png differ diff --git a/assets/images/receipt-generic.png b/assets/images/receipt-generic.png index 1aabe854617d..d0ac937ac777 100644 Binary files a/assets/images/receipt-generic.png and b/assets/images/receipt-generic.png differ diff --git a/assets/images/receipt-html.png b/assets/images/receipt-html.png index 5cf8d585b21f..cade062115f2 100644 Binary files a/assets/images/receipt-html.png and b/assets/images/receipt-html.png differ diff --git a/assets/images/receipt-search.svg b/assets/images/receipt-search.svg index a8aa5f51f581..f79866cae6a5 100644 --- a/assets/images/receipt-search.svg +++ b/assets/images/receipt-search.svg @@ -1,16 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/receipt-svg.png b/assets/images/receipt-svg.png index 130c331dd8c9..c0306e94827f 100644 Binary files a/assets/images/receipt-svg.png and b/assets/images/receipt-svg.png differ diff --git a/assets/images/receipt-upload.svg b/assets/images/receipt-upload.svg index 813aaac51f5b..d008c2999dae 100644 --- a/assets/images/receipt-upload.svg +++ b/assets/images/receipt-upload.svg @@ -1,110 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/receipt.svg b/assets/images/receipt.svg index 5ad963dab8ad..0983f6fec369 100644 --- a/assets/images/receipt.svg +++ b/assets/images/receipt.svg @@ -1,15 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/rotate-image.svg b/assets/images/rotate-image.svg index b1c4f02cbb8d..c3cb0e0293bf 100644 --- a/assets/images/rotate-image.svg +++ b/assets/images/rotate-image.svg @@ -1,12 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/rotate-left.svg b/assets/images/rotate-left.svg index 47447cf91cf9..935cbf93757f 100644 --- a/assets/images/rotate-left.svg +++ b/assets/images/rotate-left.svg @@ -1,8 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/rotate.svg b/assets/images/rotate.svg index 651f9a6ac82d..f9eb97017020 100644 --- a/assets/images/rotate.svg +++ b/assets/images/rotate.svg @@ -1,12 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/send.svg b/assets/images/send.svg index 9ba1c6a336a2..34327fab329f 100644 --- a/assets/images/send.svg +++ b/assets/images/send.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/shield.svg b/assets/images/shield.svg index cb46f32fdfa0..09d5c7d51252 100644 --- a/assets/images/shield.svg +++ b/assets/images/shield.svg @@ -1,14 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/shutter.svg b/assets/images/shutter.svg index e4dadcea8089..f8d81efcee2b 100644 --- a/assets/images/shutter.svg +++ b/assets/images/shutter.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/signIn/apple-logo.svg b/assets/images/signIn/apple-logo.svg index 4e428fc41aed..de5ebf191066 100644 --- a/assets/images/signIn/apple-logo.svg +++ b/assets/images/signIn/apple-logo.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/assets/images/signIn/google-logo.svg b/assets/images/signIn/google-logo.svg index ebdd4be8cade..ec879fafa988 100644 --- a/assets/images/signIn/google-logo.svg +++ b/assets/images/signIn/google-logo.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__bank-arrow.svg b/assets/images/simple-illustrations/simple-illustration__bank-arrow.svg index 7e5b4dae1ccc..85a0445f640b 100644 --- a/assets/images/simple-illustrations/simple-illustration__bank-arrow.svg +++ b/assets/images/simple-illustrations/simple-illustration__bank-arrow.svg @@ -1,172 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__bill.svg b/assets/images/simple-illustrations/simple-illustration__bill.svg index 7fb76fb8c09b..aa4cb8fc9faf 100644 --- a/assets/images/simple-illustrations/simple-illustration__bill.svg +++ b/assets/images/simple-illustrations/simple-illustration__bill.svg @@ -1,57 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__chatbubbles.svg b/assets/images/simple-illustrations/simple-illustration__chatbubbles.svg index 8edeea7e06f9..37857c15c074 100644 --- a/assets/images/simple-illustrations/simple-illustration__chatbubbles.svg +++ b/assets/images/simple-illustrations/simple-illustration__chatbubbles.svg @@ -1,24 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__coffeemug.svg b/assets/images/simple-illustrations/simple-illustration__coffeemug.svg index de4ae88d731b..7d54b9892fce 100644 --- a/assets/images/simple-illustrations/simple-illustration__coffeemug.svg +++ b/assets/images/simple-illustrations/simple-illustration__coffeemug.svg @@ -1,46 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg b/assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg index eeabc78b1881..b3a6bf98deba 100644 --- a/assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg +++ b/assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg @@ -1,102 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__concierge.svg b/assets/images/simple-illustrations/simple-illustration__concierge.svg index 8275671c3486..061a37d492e9 100644 --- a/assets/images/simple-illustrations/simple-illustration__concierge.svg +++ b/assets/images/simple-illustrations/simple-illustration__concierge.svg @@ -1,95 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__credit-cards.svg b/assets/images/simple-illustrations/simple-illustration__credit-cards.svg index 8e070f074ef3..f0ffd1174efc 100644 --- a/assets/images/simple-illustrations/simple-illustration__credit-cards.svg +++ b/assets/images/simple-illustrations/simple-illustration__credit-cards.svg @@ -1,92 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__email-address.svg b/assets/images/simple-illustrations/simple-illustration__email-address.svg index a8f0db9a4f8b..7bf0d253530a 100644 --- a/assets/images/simple-illustrations/simple-illustration__email-address.svg +++ b/assets/images/simple-illustrations/simple-illustration__email-address.svg @@ -1,35 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__handearth.svg b/assets/images/simple-illustrations/simple-illustration__handearth.svg index f79e3f73293b..30828ee3585b 100644 --- a/assets/images/simple-illustrations/simple-illustration__handearth.svg +++ b/assets/images/simple-illustrations/simple-illustration__handearth.svg @@ -1,30 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__invoice.svg b/assets/images/simple-illustrations/simple-illustration__invoice.svg index 0a10b70a7bfe..bd7738e571cd 100644 --- a/assets/images/simple-illustrations/simple-illustration__invoice.svg +++ b/assets/images/simple-illustrations/simple-illustration__invoice.svg @@ -1,65 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__lockopen.svg b/assets/images/simple-illustrations/simple-illustration__lockopen.svg index fb07d7a8628b..6a269e686ab5 100644 --- a/assets/images/simple-illustrations/simple-illustration__lockopen.svg +++ b/assets/images/simple-illustrations/simple-illustration__lockopen.svg @@ -1,73 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__luggage.svg b/assets/images/simple-illustrations/simple-illustration__luggage.svg index 9a01eee56662..3d3118f8cebd 100644 --- a/assets/images/simple-illustrations/simple-illustration__luggage.svg +++ b/assets/images/simple-illustrations/simple-illustration__luggage.svg @@ -1,79 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__money-receipts.svg b/assets/images/simple-illustrations/simple-illustration__money-receipts.svg index 3d81f5dba653..af9d6a26a73a 100644 --- a/assets/images/simple-illustrations/simple-illustration__money-receipts.svg +++ b/assets/images/simple-illustrations/simple-illustration__money-receipts.svg @@ -1,142 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__moneybadge.svg b/assets/images/simple-illustrations/simple-illustration__moneybadge.svg index 1f673aa20a90..68fc7845e531 100644 --- a/assets/images/simple-illustrations/simple-illustration__moneybadge.svg +++ b/assets/images/simple-illustrations/simple-illustration__moneybadge.svg @@ -1,78 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__moneyintowallet.svg b/assets/images/simple-illustrations/simple-illustration__moneyintowallet.svg index 3f89e6b14836..e184257a4456 100644 --- a/assets/images/simple-illustrations/simple-illustration__moneyintowallet.svg +++ b/assets/images/simple-illustrations/simple-illustration__moneyintowallet.svg @@ -1,137 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__moneywings.svg b/assets/images/simple-illustrations/simple-illustration__moneywings.svg index b13abdf448af..921af4ff88be 100644 --- a/assets/images/simple-illustrations/simple-illustration__moneywings.svg +++ b/assets/images/simple-illustrations/simple-illustration__moneywings.svg @@ -1,131 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__opensafe.svg b/assets/images/simple-illustrations/simple-illustration__opensafe.svg index 273d68b62723..1dd6ab9e5215 100644 --- a/assets/images/simple-illustrations/simple-illustration__opensafe.svg +++ b/assets/images/simple-illustrations/simple-illustration__opensafe.svg @@ -1,195 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg b/assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg index 79779e85c940..ef1dfd547614 100644 --- a/assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg +++ b/assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg @@ -1,78 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__shield.svg b/assets/images/simple-illustrations/simple-illustration__shield.svg index 5d56b9c3acb2..ebea008403a0 100644 --- a/assets/images/simple-illustrations/simple-illustration__shield.svg +++ b/assets/images/simple-illustrations/simple-illustration__shield.svg @@ -1,77 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg b/assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg index 623874d2d3eb..23412a17af4a 100644 --- a/assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg +++ b/assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg @@ -1,69 +1 @@ - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__track-shoe.svg b/assets/images/simple-illustrations/simple-illustration__track-shoe.svg index 5d45f2f9df67..04679033a714 100644 --- a/assets/images/simple-illustrations/simple-illustration__track-shoe.svg +++ b/assets/images/simple-illustrations/simple-illustration__track-shoe.svg @@ -1,100 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__treasurechest.svg b/assets/images/simple-illustrations/simple-illustration__treasurechest.svg index edb868db11d2..2bdee0c7e90f 100644 --- a/assets/images/simple-illustrations/simple-illustration__treasurechest.svg +++ b/assets/images/simple-illustrations/simple-illustration__treasurechest.svg @@ -1,138 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/social-facebook.svg b/assets/images/social-facebook.svg index 3a966653e688..a8ed9b7c5231 100644 --- a/assets/images/social-facebook.svg +++ b/assets/images/social-facebook.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/social-instagram.svg b/assets/images/social-instagram.svg index 79d4aadf374a..a90ae389b0ed 100644 --- a/assets/images/social-instagram.svg +++ b/assets/images/social-instagram.svg @@ -1,17 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/social-linkedin.svg b/assets/images/social-linkedin.svg index 97a1da8962f4..04a88fa700a9 100644 --- a/assets/images/social-linkedin.svg +++ b/assets/images/social-linkedin.svg @@ -1,8 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/social-podcast.svg b/assets/images/social-podcast.svg index 1366699b6823..41b6905a37b2 100644 --- a/assets/images/social-podcast.svg +++ b/assets/images/social-podcast.svg @@ -1,15 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/social-twitter.svg b/assets/images/social-twitter.svg index 6a90f95032bb..5955d2ab21de 100644 --- a/assets/images/social-twitter.svg +++ b/assets/images/social-twitter.svg @@ -1,9 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/social-youtube.svg b/assets/images/social-youtube.svg index 834314d27d27..ef8161be32cd 100644 --- a/assets/images/social-youtube.svg +++ b/assets/images/social-youtube.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/sync.svg b/assets/images/sync.svg index 65d8df356901..af28fef95b86 100644 --- a/assets/images/sync.svg +++ b/assets/images/sync.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/task.svg b/assets/images/task.svg index 20412f771b69..4a51073fece2 100644 --- a/assets/images/task.svg +++ b/assets/images/task.svg @@ -1,15 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/three-dots.svg b/assets/images/three-dots.svg index 1ff11c8448a1..2c92f2db7820 100644 --- a/assets/images/three-dots.svg +++ b/assets/images/three-dots.svg @@ -1,8 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/transfer.svg b/assets/images/transfer.svg index 76288f8227b4..bc717c0a1c85 100644 --- a/assets/images/transfer.svg +++ b/assets/images/transfer.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/trashcan.svg b/assets/images/trashcan.svg index 6158a705bd35..0456e6c94350 100644 --- a/assets/images/trashcan.svg +++ b/assets/images/trashcan.svg @@ -1,11 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/unlock.svg b/assets/images/unlock.svg index 0cf22e8d1813..22c4278b0072 100644 --- a/assets/images/unlock.svg +++ b/assets/images/unlock.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/upload-alt.svg b/assets/images/upload-alt.svg index 3b8e7137bd93..19973e11f98d 100644 --- a/assets/images/upload-alt.svg +++ b/assets/images/upload-alt.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/upload.svg b/assets/images/upload.svg index faffc4a9b8cf..fee5f66a1d67 100644 --- a/assets/images/upload.svg +++ b/assets/images/upload.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/user.svg b/assets/images/user.svg index 96f403e2ce4a..16672c8b8ed9 100644 --- a/assets/images/user.svg +++ b/assets/images/user.svg @@ -1,7 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/users.svg b/assets/images/users.svg index 8af469328821..ac2ee1ccb83f 100644 --- a/assets/images/users.svg +++ b/assets/images/users.svg @@ -1,10 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/assets/images/wallet.svg b/assets/images/wallet.svg index 1c6d606683b8..1e213fe209e1 100644 --- a/assets/images/wallet.svg +++ b/assets/images/wallet.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/assets/images/workspace-default-avatar.svg b/assets/images/workspace-default-avatar.svg index 63d0a47f6806..b853e2ad2520 100644 --- a/assets/images/workspace-default-avatar.svg +++ b/assets/images/workspace-default-avatar.svg @@ -1,13 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/assets/images/zoom-icon.svg b/assets/images/zoom-icon.svg index 24d019654795..81f025aedf79 100644 --- a/assets/images/zoom-icon.svg +++ b/assets/images/zoom-icon.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/zoom.svg b/assets/images/zoom.svg index 25e82c2903eb..a7d1c64556ba 100644 --- a/assets/images/zoom.svg +++ b/assets/images/zoom.svg @@ -1,13 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/contributingGuides/OfflineUX_Patterns_Flowchart.png b/contributingGuides/OfflineUX_Patterns_Flowchart.png index 813474dedb0d..bd7a0f311059 100644 Binary files a/contributingGuides/OfflineUX_Patterns_Flowchart.png and b/contributingGuides/OfflineUX_Patterns_Flowchart.png differ diff --git a/contributingGuides/REVIEWER_CHECKLIST.md b/contributingGuides/REVIEWER_CHECKLIST.md index d52d80a818bb..16c8f88927b1 100644 --- a/contributingGuides/REVIEWER_CHECKLIST.md +++ b/contributingGuides/REVIEWER_CHECKLIST.md @@ -57,42 +57,42 @@ ### Screenshots/Videos
-Web +Android: Native
-Mobile Web - Chrome +Android: mWeb Chrome
-Mobile Web - Safari +iOS: Native
-Desktop +iOS: mWeb Safari
-iOS +MacOS: Chrome / Safari
-Android +MacOS: Desktop diff --git a/contributingGuides/data_flow.png b/contributingGuides/data_flow.png index e874afce6537..6ee8a4deec5a 100644 Binary files a/contributingGuides/data_flow.png and b/contributingGuides/data_flow.png differ diff --git a/desktop/electron.png b/desktop/electron.png index e80796026c67..914e4ff34637 100644 Binary files a/desktop/electron.png and b/desktop/electron.png differ diff --git a/desktop/icon-adhoc.png b/desktop/icon-adhoc.png index 5812ad6c5404..8e74a974198f 100644 Binary files a/desktop/icon-adhoc.png and b/desktop/icon-adhoc.png differ diff --git a/desktop/icon-dev.png b/desktop/icon-dev.png index 4589faa80d41..2d38819c450b 100644 Binary files a/desktop/icon-dev.png and b/desktop/icon-dev.png differ diff --git a/desktop/icon-stg.png b/desktop/icon-stg.png index c605f8aea6ad..fcca93d0cf43 100644 Binary files a/desktop/icon-stg.png and b/desktop/icon-stg.png differ diff --git a/desktop/icon.png b/desktop/icon.png index f837c238d19d..f90a81e86c65 100644 Binary files a/desktop/icon.png and b/desktop/icon.png differ diff --git a/desktop/package-lock.json b/desktop/package-lock.json index abc1299154ef..0ff280c4b9c6 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -10,15 +10,10 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", "electron-serve": "^1.0.0", - "electron-updater": "^4.3.4", + "electron-updater": "^6.1.4", "node-machine-id": "^1.1.12" } }, - "node_modules/@types/semver": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", - "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -55,11 +50,11 @@ } }, "node_modules/builder-util-runtime": { - "version": "8.9.2", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.2.tgz", - "integrity": "sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz", + "integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==", "dependencies": { - "debug": "^4.3.2", + "debug": "^4.3.4", "sax": "^1.2.4" }, "engines": { @@ -98,9 +93,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, @@ -155,18 +150,18 @@ "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" }, "node_modules/electron-updater": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.5.tgz", - "integrity": "sha512-kdTly8O9mSZfm9fslc1mnCY+mYOeaYRy7ERa2Fed240u01BKll3aiupzkd07qKw69KvhBSzuHroIW3mF0D8DWA==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz", + "integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==", "dependencies": { - "@types/semver": "^7.3.6", - "builder-util-runtime": "8.9.2", - "fs-extra": "^10.0.0", + "builder-util-runtime": "9.2.1", + "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", - "semver": "^7.3.5" + "semver": "^7.3.8", + "tiny-typed-emitter": "^2.1.0" } }, "node_modules/emoji-regex": { @@ -206,9 +201,9 @@ } }, "node_modules/fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "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==", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -219,9 +214,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", @@ -333,14 +328,14 @@ } }, "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -410,6 +405,11 @@ "node": ">=8" } }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==" + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -437,11 +437,6 @@ } }, "dependencies": { - "@types/semver": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", - "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -466,11 +461,11 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" }, "builder-util-runtime": { - "version": "8.9.2", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.2.tgz", - "integrity": "sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz", + "integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==", "requires": { - "debug": "^4.3.2", + "debug": "^4.3.4", "sax": "^1.2.4" } }, @@ -497,9 +492,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } @@ -540,18 +535,18 @@ "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" }, "electron-updater": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.5.tgz", - "integrity": "sha512-kdTly8O9mSZfm9fslc1mnCY+mYOeaYRy7ERa2Fed240u01BKll3aiupzkd07qKw69KvhBSzuHroIW3mF0D8DWA==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz", + "integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==", "requires": { - "@types/semver": "^7.3.6", - "builder-util-runtime": "8.9.2", - "fs-extra": "^10.0.0", + "builder-util-runtime": "9.2.1", + "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", - "semver": "^7.3.5" + "semver": "^7.3.8", + "tiny-typed-emitter": "^2.1.0" } }, "emoji-regex": { @@ -582,9 +577,9 @@ } }, "fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "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==", "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -592,9 +587,9 @@ } }, "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -680,14 +675,14 @@ } }, "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { "lru-cache": "^6.0.0" } @@ -736,6 +731,11 @@ "ansi-regex": "^5.0.1" } }, + "tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==" + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", diff --git a/desktop/package.json b/desktop/package.json index 45283a260970..bf49d93f1a7b 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -7,7 +7,7 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", "electron-serve": "^1.0.0", - "electron-updater": "^4.3.4", + "electron-updater": "^6.1.4", "node-machine-id": "^1.1.12" }, "author": "Expensify, Inc.", diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 375b00d62eac..2fbdac02e85c 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -1,5 +1,159 @@ --- title: Business Bank Accounts - USD -description: Business Bank Accounts - USD +description: How to add/remove Business Bank Accounts (US) --- -## Resource Coming Soon! +# Overview +Adding a verified business bank account unlocks a myriad of features and automation in Expensify. +Once you connect your business bank account, you can: +- Pay employee expense reports via direct deposit (US) +- Settle company bills via direct transfer +- Accept invoice payments through direct transfer +- Access the Expensify Card + +# How to add a verified business bank account +To connect a business bank account to Expensify, follow the below steps: +1. Go to **Settings > Account > Payments** +2. Click **Add Verified Bank Account** +3. Click **Log into your bank** +4. Click **Continue** +5. When you hit the **Plaid** screen, you'll be shown a list of compatible banks that offer direct online login access +6. Login to the business bank account +- If the bank is not listed, click the X to go back to the connection type +- Here you’ll see the option to **Connect Manually** +- Enter your account and routing numbers +7. Enter your bank login credentials. +- If your bank requires additional security measures, you will be directed to obtain and enter a security code +- If you have more than one account available to choose from, you will be directed to choose the desired account +Next, to verify the bank account, you’ll enter some details about the business as well as some personal information. + +## Enter company information +This is where you’ll add the legal business name as well as several other company details. + +### Company address +The company address must: +- Be located in the US +- Be a physical location +If you input a maildrop address (PO box, UPS Store, etc.), the address will likely be flagged for review and adding the bank account to Expensify will be delayed. + +### Tax Identification Number +This is the identification number that was assigned to the business by the IRS. +### Company website +A company website is required to use most of Expensify’s payment features. When adding the website of the business, format it as, https://www.domain.com. +### Industry Classification Code +You can locate a list of Industry Classification Codes here. +## Enter personal information +Whoever is connecting the bank account to Expensify, must enter their details under the Requestor Information section: +- The address must be a physical address +- The address must be located in the US +- The SSN must be US-issued +This does not need to be a signor on the bank account. If someone other than the Expensify account holder enters their personal information in this section, the details will be flagged for review and adding the bank account to Expensify will be delayed. + +## Upload ID +After entering your personal details, you’ll be prompted to click a link or scan a QR code so that you can do the following: +1. Upload the front and back of your ID +2. Use your device to take a selfie and record a short video of yourself +It’s required that your ID is: +- Issued in the US +- Unexpired + +## Additional Information +Check the appropriate box under **Additional Information**, accept the agreement terms, and verify that all of the information is true and accurate: +- A Beneficial Owner refers to an **individual** who owns 25% or more of the business. +- If you or another **individual** owns 25% or more of the business, please check the appropriate box +- If someone else owns 25% or more of the business, you will be prompted to provide their personal information +If no individual owns more than 25% of the company you do not need to list any beneficial owners. In that case, be sure to leave both boxes unchecked under the Beneficial Owner Additional Information section. + +# How to validate the bank account +The account you set up can be found under **Settings > Account > Payment > Bank Accounts** section in either **Verifying** or **Pending** status. +If it is **Verifying**, then this means we sent you a message and need more information from you. Please check your Concierge chat which should include a message with specific details about what we require to move forward. +If it is **Pending**, then in 1-2 business days Expensify will administer 3 test transactions to your bank account. Please check your Concierge chat for further instructions. If you do not see these test transactions +After these transactions (2 withdrawals and 1 deposit) have been processed in your account, visit your Expensify Inbox, where you'll see a prompt to input the transaction amounts. +Once you've finished these steps, your business bank account is ready to use in Expensify! + +# How to share a verified bank account +Only admins with access to the verified bank account can reimburse employees or pay vendor bills. To grant another admin access to the bank account in Expensify, go to **Settings > Account > Payments > Bank Accounts** and click **"Share"**. Enter their email address, and they will receive instructions from us. Please note, they must be a policy admin on a policy you also have access to in order to share the bank account with them. +When a bank account is shared, it must be revalidated with three new microtransactions to ensure the shared admin has access. This process takes 1-2 business days. Once received, the shared admin can enter the transactions via their Expensify account's Inbox tab. + +Note: A report is shared with all individuals with access to the same business bank account in Expensify for audit purposes. + + +# How to remove access to a verified bank account +This step is important when accountants and staff leave your business. +To remove an admin's access to a shared bank account, go to **Settings > Account > Payments > Shared Business Bank Accounts**. +You'll find a list of individuals who have access to the bank account. Next to each user, you'll see the option to Unshare the bank account. + +# How to delete a verified bank account +If you need to delete a bank account from Expensify, run through the following steps: +1. Head to **Settings > Account > Payments** +2. Click the red **Delete** button under the corresponding bank account + +Be cautious, as if it hasn't been shared with someone else, the next user will need to set it up from the beginning. + +If the bank account is set as the settlement account for your Expensify Cards, you’ll need to designate another bank account as your settlement account under **Settings > Domains > Company Cards > Settings** before this account can be deleted. + +# Deep Dive + +## Verified bank account requirements + +To add a business bank account to issue reimbursements via ACH (US), to pay invoices (US) or utilize the Expensify Card: +- You must enter a physical address for yourself, any Beneficial Owner (if one exists), and the business associated with the bank account. We **cannot** accept a PO Box or MailDrop location. +- If you are adding the bank account to Expensify, you must add it from **your** Expensify account settings. +- If you are adding a bank account to Expensify, we are required by law to verify your identity. Part of this process requires you to verify a US issued photo ID. For utilizing features related to US ACH, your idea must be issued by the United States. You and any Beneficial Owner (if one exists), must also have a US address +- You must have a valid website for your business to utilize the Expensify Card, or to pay invoices with Expensify. + +## Locked bank account +When you reimburse a report, you authorize Expensify to withdraw the funds from your account. If your bank rejects Expensify’s withdrawal request, your verified bank account is locked until the issue is resolved. + +Withdrawal requests can be rejected due to insufficient funds, or if the bank account has not been enabled for direct debit. +If you need to enable direct debits from your verified bank account, your bank will require the following details: +- The ACH CompanyIDs (1270239450 and 4270239450) +- The ACH Originator Name (Expensify) +To request to unlock the bank account, click **Fix** on your bank account under **Settings > Account > Payments > Bank Accounts**. +This sends a request to our support team to review exactly why the bank account was locked. +Please note, unlocking a bank account can take 4-5 business days to process. + +## Error adding ID to Onfido +Expensify is required by both our sponsor bank and federal law to verify the identity of the individual that is initiating the movement of money. We use Onfido to confirm that the person adding a payment method is genuine and not impersonating someone else. + +If you get a generic error message that indicates, "Something's gone wrong", please go through the following steps: + +1. Ensure you are using either Safari (on iPhone) or Chrome (on Android) as your web browser. +2. Check your browser's permissions to make sure that the camera and microphone settings are set to "Allow" +3. Clear your web cache for Safari (on iPhone) or Chrome (on Android). +4. If using a corporate Wi-Fi network, confirm that your corporate firewall isn't blocking the website. +5. Make sure no other apps are overlapping your screen, such as the Facebook Messenger bubble, while recording the video. +6. On iPhone, if using iOS version 15 or later, disable the Hide IP address feature in Safari. +7. If possible, try these steps on another device +8. If you have another phone available, try to follow these steps on that device +If the issue persists, please contact your Account Manager or Concierge for further troubleshooting assistance. + +# FAQ +## What is a Beneficial Owner? + +A Beneficial Owner refers to an **individual** who owns 25% or more of the business. If no individual owns 25% or more of the business, the company does not have a Beneficial Owner. + + +## What do I do if the Beneficial Owner section only asks for personal details, but our business is owned by another company? + + +Please only indicate you have a Beneficial Owner, if it is an individual that owns 25% or more of the business. + +## Why can’t I input my address or upload my ID? + + +Are you entering a US address? When adding a verified business bank account in Expensify, the individual adding the account, and any beneficial owner (if one exists) are required to have a US address, US photo ID, and a US SSN. If you do not meet these requirements, you’ll need to have another admin add the bank account, and then share access with you once verified. + + +## Why am I being asked for documentation when adding my bank account? +When a bank account is added to Expensify, we complete a series of checks to verify the information provided to us. We conduct these checks to comply with both our sponsor bank's requirements and federal government regulations, specifically the Bank Secrecy Act / Anti-Money Laundering (BSA / AML) laws. Expensify also has anti-fraud measures in place. +If automatic verification fails, we may request manual verification, which could involve documents such as address verification for your business, a letter from your bank confirming bank account ownership, etc. + +If you have any questions regarding the documentation request you received, please contact Concierge and they will be happy to assist. + + +## I don’t see all three microtransactions I need to validate my bank account. What should I do? + +It's a good idea to wait till the end of that second business day. If you still don’t see them, please reach out to your bank and ask them to whitelist our ACH ID's **1270239450** and **4270239450**. Expensify’s ACH Originator Name is "Expensify". + +Make sure to reach out to your Account Manager or to Concierge once you have done so and our team will be able to re-trigger those 3 transactions! + diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md index f1d939ca9c89..8b6ea7de2642 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md @@ -1,5 +1,108 @@ --- title: Direct Bank Connections -description: Direct Bank Connections +description: Connect your company cards in Expensify to bring all team members’ card expenses into their accounts and conveniently manage card transactions and out-of-pocket expenses in one place. + --- -## Resource Coming Soon! +# Overview +If you're a Domain Admin, you have the power to connect and manage your company cards in Expensify centrally. If your company uses a card program with one of our Approved! Banking Partners, you can easily connect the card feed to Expensify via login credentials. Connecting company cards is a great way to bring all team members’ card expenses into their accounts and conveniently manage card transactions and out-of-pocket expenses in one place. Keeping things organized has never been easier! +# How to connect company cards using a direct bank connection +1. Go to **Settings > Domains > _Domain Name_ > Company Cards** +2. Click **Import Card** + +![Expensify domain cards](https://help.expensify.com/assets/images/ExpensifyHelp_DomainCards.png){:width="100%"} + +3. Select your card issuer and input the **master administrative login credentials** +4. You will then be able to assign accounts to cardholders +5. Set a start date from which expenses will appear in their accounts +## How to assign company cards +After connecting your company cards with Expensify, you can assign each card to its respective cardholder. +To assign the company cards, go to **Settings > Domains > _Domain Name_ > Company Cards**. +If you have more than one card feed, select the correct feed in the drop-down list in the Company Card section. + +![Expensify domain card list](https://help.expensify.com/assets/images/ExpensifyHelp_DomainCardsList.png){:width="100%"} + + +Once you’ve selected the appropriate feed, click the `Assign New Cards` button to populate the emails and the last four digits of the cardholder. + +![Expensify assign cards](https://help.expensify.com/assets/images/ExpensifyHelp_AssignCardBtn.png){:width="100%"} + +![Expensify domain assign card form](https://help.expensify.com/assets/images/ExpensifyHelp_AssignCardForm.png){:width="100%"} + + **Select the cardholder:** Search the populated list for all employee email addresses. The employee will need to have an email address under this Domain to assign a card. + +**Select the card:** Search the list using the last four digits of the card number. If no transactions have been posted on the card, the card number will not appear in the list. You can instead assign the card by typing in the full card number in the field. + +**Note:** If you're assigning a card by typing in the full PAN (the full card number), press the ENTER key on your keyboard after typing the full PAN into the card field. The field may clear itself after pressing ENTER, but click **Assign** anyway and then verify that the assignment shows up in the cardholder table. + +**Set the transaction start date (optional):** Any transactions that were posted before this date will not be imported into Expensify. If you do not make a selection, it will default to the earliest available transactions from the card. +Please note we can only import data for the time period the bank is releasing to us. Most banks only provide a certain amount of historical data, averaging 30-90 days into the past. It's not possible to override the start date the bank has provided via this tool. + +**Click the Assign button:** Once assigned, you will see each cardholder associated with their card and the start date listed. + +![Expensify domain assigned cards](https://help.expensify.com/assets/images/ExpensifyHelp_AssignedCard.png){:width="100%"} + + +## How to unassign company cards +_**Important** - Before you begin the unassigning process, please note that unassigning a company card will **delete** any **Open** or **Unreported** expenses in the cardholder's account. To avoid this, users should submit these expenses **before** their cards are unassigned._ + +If you need to unassign a certain card, click the **Actions** button associated with the card in question and then click **Unassign**. + +![Expensify domain unassign cards](https://help.expensify.com/assets/images/ExpensifyHelp_UnassignCard.png){:width="100%"} + +To completely remove the card connection, unassign every card from the list and then refresh the page. + +**Note:** If expenses are **Processing** and then rejected, they will also be deleted when they're returned to an **Open** state as the linked card they're linked to no longer exists. + +# Deep Dive +## Configure card settings +Once you’ve imported your company cards, the next step is configuring the cards’ settings. +If you're using a connected accounting system such as NetSuite, Xero, Sage Intacct, Quickbooks Desktop, or QuickBooks Online. In that case, you can connect the card to export to a specific credit card GL account. +1. Go to **Settings > Domains > _Domain Name_ > Company Cards** +2. Click **Edit Exports** on the right-hand side of the card table and select the GL account you want to export expenses to +3. You're all done. After the account is set, exported expenses will be mapped to the specific account selected when exported by a Domain Admin. + +![Expensify domain cards settings](https://help.expensify.com/assets/images/ExpensifyHelp_UnassignCard-1.png){:width="100%"} + + +You can access the remaining company card settings by navigating to **Settings > Domains > _Domain Name_ > Company Cards > Settings.** More information on card settings can be found by searching **“How to configure company card settings”**. + +# FAQ +## How can I connect and manage my company’s cards centrally if I’m not a domain admin? + If you cannot access Domains, you must request Domain Admin access to an existing Domain Admin (usually the workspace owner). + +## Are direct bank connections the best option for connecting credit cards to Expensify? +If we currently have a connection with your bank, then it’s a good option. However, if you want enhanced stability and additional functionality, consider opting for a commercial card feed directly from your bank or getting the Expensify card. + +## My card feed is set up. Why is a specific card not coming up when I try to assign it to an employee? +Cards will appear in the drop-down when activated and have at least one posted transaction. If the card is activated and has been used for a while and you're still not seeing it, please contact your Account Manager or message Concierge for further assistance. + +## Is there a fee for utilizing direct card connections? +Nope! Direct card connections come at no extra cost and are part of the Corporate Workspace pricing. + +## What is the difference between commercial card feeds and direct bank connections? +The direct bank connection is a connection set up with your login credentials for that account. In contrast, the commercial card feed is set up by your bank requesting that Visa/MasterCard/Amex send a daily transaction feed to Expensify. The former can be done without the assistance of your bank or Expensify, but the latter is more stable and reliable. + +## What if my bank uses a card program that isn't with one of Expensify's Approved! Banking partners? +If your company uses a Commercial Card program that isn’t with one of our Approved! Banking Partners (which supports connecting the feed via login credentials), the best way to import your company cards is by setting up a direct Commercial Card feed between Expensify and your bank. Note the Approved! Banking Partners include: +- Bank of America +- Citibank +- Capital One +- Chase +- Wells Fargo +- Amex +- Stripe +- Brex + +## Why do direct bank connections break? +Banks often make changes to safeguard your confidential information, and when they do, we need to update the connection between Expensify and the bank. We have a team of engineers who work closely with banks to monitor this and update our software accordingly when this happens. + +## How do I resolve errors while trying to import my card? +Ensure you're importing your card in the correct spot in Expensify and selecting the proper bank connection. For company cards, use the master administrative credentials to import your set of cards at **Settings > Domains > _Domain Name_ > Company Cards > Import Card.** + +## Why is my card connection broken after working fine? +The first step is to check for any changes to your bank information. Have you recently changed your banking password without updating it in Expensify? Has your banking username or card number been updated? Did you edit your security questions for your bank? Additionally, if your security questions have changed or their answers aren't saved in Expensify. In that case, we won't be able to access your account list, and you'll need to address this within Expensify. + +If you've answered "yes" to any of these questions, you'll need to update this information in Expensify and manually re-establish the connection. Please note that Expensify cannot automatically update this information for you. +A Domain Admin can fix the connection by heading to **Settings > Domains > _Domain Name_ > Company Cards > Fix**. You will be prompted to enter the new credentials/updated information, and this should reestablish the connection. +If you are still experiencing issues with the card connection, please search for company card troubleshooting or contact Expensify Support for help. + diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md index e3d1307e6a05..8d1a79e49eaf 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md @@ -1,5 +1,101 @@ --- title: Troubleshooting -description: Troubleshooting +description: How to troubleshoot company card importing in Expensify --- -## Resource Coming Soon! +# Overview +Whether you're encountering issues related to company cards, require assistance with company card account access, or have questions about company card import features, you've come to the right place. + +## How to add company cards to Expensify +You can add company credit cards under the Domain settings in your Expensify account by navigating to *Settings* > *Domain* > _Domain Name_ > *Company Cards* and clicking *Import Card/Bank* and following the prompts. + +# Errors connecting company cards + +## Error: Too many attempts +If you've been locked out while trying to import a new card, you'll need to wait a full 24 hours before trying again. This lock happens when incorrect online banking credentials are entered multiple times, and it's there for your security — it can't be removed. To avoid this, make sure your online banking credentials are correct before attempting to import your card again. + +## Error: Invalid credentials/Login failed +Verify your ability to log into your online banking portal by attempting to log into your bank account via the banking website. +Check for any potential temporary outages on your bank's end that may affect third-party connections like Expensify. +For specific card types: +- *Chase Card*: Confirm your password meets their new 8-32 character requirement. +- *Wells Fargo Card*: Ensure your password is under 14 characters. Reset it if necessary before importing your card to Expensify. If your card is already imported, update it and use the "Fix Card" option to reestablish the connection. +- *SVB Card*: Enable Direct Connect from the SVB website and use your online banking username and Direct Connect PIN instead of your password when connecting an SVB card. If connecting via *Settings* > *Domain* > _[Domain Name]_ > *Company Cards*, contact SVB for CDF feed setup. + +## Error: Direct Connect not enabled +Direct Connect will need to be enabled in your account for your bank/credit card provider before you can import your card to Expensify. Please reach out to your bank to confirm if this option is available for your account, as well as get instructions on how to get this setup. + +## Error: Account Setup +This error message typically indicates that there's something you need to do on your bank account's end. Please visit your online banking portal and check if there are any pending actions required. Once you've addressed those, you can try connecting your card again. +For Amex cardholders with multiple card programs in your Amex US Business account: To import multiple card programs into Expensify, you'll need to contact Amex and request that they separate the multiple card programs into distinct logins. For instance, you'll want to have your _Business Platinum_ cards under *"username1/password1"* and _Business Gold_ cards under *"username2/password2."* This ensures smooth integration with Expensify. + +## Error: Account type not supported +If Expensify doesn't have a direct connection to your bank/credit card provider, we can still support the connection via spreadsheet import, which you can learn more about [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import#gsc.tab=0). If the cards you're trying to import are company cards, it’s possible that you might be able to obtain a commercial feed directly from your bank. Please find more information on this [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds#gsc.tab=0). + +## Error: Username/Password/Questions out of date +Your company card connection is broken because we're missing some answers to some security questions. Please head to *Settings* > *Domain* > _[Domain Name]_ > *Company Cards* and click _Fix Card_. +This will require you to answer your bank's security questions. You will need to do this for each security question you have with your bank; so if you have 3 security questions, you will need to do this 3 times. + +## Error: Account not found/Card number changed +This error message appears when you have been issued a new card, or if there's been a significant change to the account in some other way (password and/or card number change). +When your online bank/card account password has been changed, you may need to update the details on the Expensify end as well. To do this, navigate to *Settings* > *Domain* > _[Domain Name]_ > *Company Cards* and click _Fix Card_. +If there’s been a recent change to the card number, you’ll have to remove the card with the previous number and re-import the card using the new number. A Domain Admin will have to re-assign the card via *Settings* > *Domain* > _Domain Name_ > *Company Cards*. Before removing the card, please ensure *all Open reports have been submitted*, as removing the card will remove all imported transactions from the account that are associated with that card. + +## Error: General connection error +This error message states that your bank or credit card provider is under maintenance and is unavailable at this time. Try waiting a few hours before trying to import your credit card again. Check out our [status page](https://status.expensify.com/) for updates on bank/credit card connections, or you can also choose to subscribe to updates for your specific account type. + +## Error: Not seeing cards listed after a successful login +The card will only appear in the drop-down list for assignment once it’s activated and there are transactions that have been incurred and posted on the card. If not, the card won't be available to assign to the card holder until then. + +# Troubleshooting issues assigning company cards + +## Why do bank connections break? +Banks often make changes to safeguard your confidential information, and when they do, we need to update the connection between Expensify and the bank. We have a team of engineers that works closely with banks to monitor this and update our software accordingly when this happens. +The first step is to check if there have been any changes to your bank information. Have you recently changed your banking password without updating it in Expensify? Has your banking username or card number been updated? Did you update your security questions for your bank? +If you've answered "yes" to any of these questions, a Domain Admins need to update this information in Expensify and manually reestablish the connection by heading to *Settings* > *Domains* > _Domain Name_ > *Company Cards* > *Fix*. The Domain Admin will be prompted to enter the new credentials/updated information and this should reestablish the connection. + +## How do I resolve errors while I’m trying to import my card?* +Make sure you're importing your card in the correct spot in Expensify and selecting the right bank connection. For company cards, use the master administrative credentials to import your set of cards at *Settings* > *Domains* > _Domain Name_ > *Company Cards* > *Import Card*. +Please note there are some things that cannot be bypassed within Expensify, including two-factor authentication being enabled within your bank account. This will prevent the connection from remaining stable and will need to be turned off on the bank side. + +## What are the most reliable bank connections in Expensify?* +The most reliable corporate card to use with Expensify is the Expensify Card. We offer daily settlement, unapproved expense limits, and real-time compliance for secure and efficient spending, as well as 2% cash back. Click here to learn more or apply. +Additionally, we've teamed up with major banks worldwide to ensure a smooth import of credit card transactions into your accounts. Corporate cards from the following banks also offer the most dependable connections in Expensify: +- American Express +- Bank of America +- Brex +- Capital One +- Chase +- Citibank +- Stripe +- Wells Fargo + +Commercial feeds for company cards are the dependable connections in Expensify. If you have a corporate or commercial card account, you might have access to a daily transaction feed where expenses from Visa, MasterCard, and American Express are automatically sent to Expensify. Reach out to your banking relationship manager to check if your card program qualifies for this feature. + +# Troubleshooting American Express Business + +## Amex account roles +American Express provides three different roles for accessing accounts on their website. When connecting Amex cards to Expensify, it's crucial to use the credentials of the Primary/Basic account holder. Here's what each role means: +- *Primary/Basic Account Holder*: The person who applied for the American Express Business card, owns the account, manages its finances, and controls card issuance and account management. They can view all charges by other cardmembers on their account. They can see all charges made by other cardmembers on their account. +- *Supplemental Cardmember (Employee Cardmember)*: Chosen by the Primary Card Member (typically an employee on business accounts), they can access their own card info and make payments but can't see other account details. +- *Authorized Account Manager (AAM)*: Chosen by the Primary Card Member, AAMs can manage the account online or by phone, but they can't link cards to services like Expensify. They have admin rights, including adding cards, making payments, canceling cards, and setting limits. To connect cards to Expensify, use the Primary Card Holder's credentials for full access. + +## The connection is established but there are no cards to assign + +When establishing the connection, you must assign cards during the same session. It isn't possible to create the connection, log out, and assign the cards later, as the connection will not stick, and require you to reattempt the connection again. + +## Amex error: Card isn't eligible +This error comes directly from American Express and is typically related to an account that is not a business account or using credentials that are not the primary account holder credentials. + +## Amex error: Session has expired +If you get an error stating an American Express Business Card “Your session has expired. Please return to Expensify and try again, this always means that you are using the incorrect credentials. Remember, you need to use primary/basic cardholder credentials. If you are not sure which credentials you should use, reach out to American Express for guidance. + +## Connect multiple company card programs under the same credentials +If you have multiple company card programs with the same credentials, you can select ALL programs at once. With this, all programs will be under one dropdown. Make sure to select all cards each time you are adding any cards from any program. +If you would like your card programs listed under separate dropdowns, you can select only that group making sure to select all cards from that group each time you are adding a new card. +Once you have authorized the account, you’ll be guided back to Expensify where you’ll assign all necessary cards across all programs. +This will store all cards under the same American Express Business connection dropdown and allow all cards to be added to Expensify for you to assign to users. +*Important Reminder*: Whenever you need to access the connection to assign a new card, you must still choose "ALL card programs." For instance, if you have a new employee with a card under your Business Gold Rewards Card program, you'll still need to authorize all the cards in that program or all the programs if you have only one dropdown menu! + +## Add cards under different programs with different logins +If you have multiple card programs with different credentials, you will need to have another Domain Admin account add each card program from their own account. +Once all Domain Admins have connected and assigned the cards that they are the Primary account holder for, all cards will be listed under one *American Express (New and Upgraded)* list in the Domain Company Card page. diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Overview.md b/docs/articles/expensify-classic/billing-and-subscriptions/Overview.md index 963186916f01..b835db54cbf2 100644 --- a/docs/articles/expensify-classic/billing-and-subscriptions/Overview.md +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Overview.md @@ -1,5 +1,38 @@ --- -title: Overview -description: Overview +title: Billing in Expensify +description: An overview of how billing works in Expensify. --- -## Resource Coming Soon! +# Overview +Expensify’s billing is based on monthly member activity. At the beginning of each month, you’ll be billed for the previous month’s activity. Your Expensify bill ultimately depends on your plan type, whether you're on an annual subscription or pay-per-use billing, and whether you’re using Expensify Cards. +# How billing works in Expensify +Expensify bills the owners of Group Workspaces on the first of each month for the previous month's usage. You can find billing receipts in **Settings > Account > Payments > Billing History**. We recommend that businesses have one billing owner for all of their Group Workspaces. +## Active members +An **active member** is anyone who chats, creates, submits, approves, reimburses, or exports a report in Expensify in any given month. This includes Copilots and automated settings. +## Annual subscription +With an annual subscription, you set your monthly active member count at the beginning and get a 50% discount on your monthly active member cost. That means an annual subscription paired with the Collect plan will cost $10 per monthly active member instead of $20, and the Control plan will cost $18 instead of $36. + +Each month, you’ll be billed for the amount of members you originally set in your subscription size. Any active members in a given month above this subscription size will be billed at the pay-per-use rate. + +For example, let’s say you set your annual subscription size at 10 members and you’re on the Control plan. You’ll be billed $18/member for 10 members each month. However, let’s say in one particular month you had 12 active members, you’d be billed at $18/member for the 10 members in your subscription size + $36/member (pay-per-use rate) for the additional 2 active members. + +You can always increase your annual subscription size, which will extend your annual subscription length. You cannot reduce your annual subscription size until your current subscription has ended. If you have any questions about this, reach out to Concierge or your account manager. +## Pay-per-use +The pay-per-use rate is the full rate per active member without any discounts. The pay-per-use rate for each member on the Collect plan is $20 and on the Control plan is $36. +## How the Expensify Card can reduce your bill +Bundling the Expensify Card with an annual subscription ensures you pay the lowest possible monthly price for Expensify. And the more you spend on Expensify Cards, the lower your bill will be. + +If at least 50% of your approved USD spend in a given month is on your company’s Expensify Cards, you will receive an additional 50% discount on the price per member. This additional 50% discount, when coupled with an annual subscription, brings the price per member to $5 on a Collect plan and $9 on a Control plan. + +Additionally, every month, you receive 1% cash back on all Expensify Card purchases, and 2% if the spend across your Expensify Cards is $250k or more. Any cash back from the Expensify Card is first applied to your Expensify bill, further reducing your price per member. Any leftover cash back is deposited directly into your connected bank account. +## Savings calculator +To see how much money you can save (and even earn!) by using the Expensify Card, check out our [savings calculator](https://use.expensify.com/price-savings-calculator). Just enter a few details and see how much you’ll save! +# FAQ +## What if we put less than 50% of our total spend on the Expensify Card? +If you put less than 50% of your total USD spend on your Expensify Card, your bill gets discounted on a sliding scale based on the percentage of use. So if you don't use the Expensify Card at all, you'll be charged the full rate for each member based on your plan and subscription. +Example: +- Annual subscription discount: 50% +- % of Expensify Card spend (USD) across all workspaces: 20% +- Expensify Card discount: 20% +You save 70% on the price per member on your bill for that month. + +Note: USD spend refers to approved USD transactions on the Expensify Card in any given month, compared to all approved USD spend on workspaces in that same month. diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Receipt-Breakdown.md b/docs/articles/expensify-classic/billing-and-subscriptions/Receipt-Breakdown.md new file mode 100644 index 000000000000..275fb2c93cf0 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Receipt-Breakdown.md @@ -0,0 +1,49 @@ +--- +title: Receipts Breakdown +description: This article goes over the Expensify receipt for billing owners. +--- + +# Overview +This article will give you (the billing owner) a detailed breakdown of your Expensify bill. + +Your receipt is broken up into multiple sections that include: +1. A high-level summary of your total Expensify bill +2. Ways to reduce your bill and get paid to use Expensify +3. A billing breakdown that covers all activity and discounts +4. An activity breakdown by workspace + +## How-to understand the high-level summary +The top section will show the total amount you paid as the billing owner of Expensify workspaces and give you a breakdown of price per member. Every member of your workspace(s) gets to store data, review data, and access free features like Expensify Chat. Thus, we show the total price and then use all of the members across all of the workspaces you own to calculate the price per member. Further down in the receipt, and in this article, we break down the members who generated billable activity. + +## How-to reduce your bill and get paid to use Expensify +Chances are you can actually get paid to use Expensify with the Expensify Card. In this section of the receipt, we outline how much money you're leaving on the table by not using the Expensify Card. You can click `Get started` to connect with your account manager (if you have one) or Concierge, both of whom can help get you started with the card. + +_Note: Currently, we offer Expensify Cards to companies with USD bank accounts._ + +## How-to understand your billing breakdown +Your receipt will have a detailed breakdown of activity and discounts across all workspaces. Here's a description of items that may appear on your bill: +- [Number of] Inactive workspace members @ $0.00 + - All inactive members from any of your workspaces. +- [Number of] Chat-only members @ $0.00 + - Any workspace members who chatted but didn't generate any other billable activity. Learn more about [chatting for free.](https://help.expensify.com/articles/new-expensify/getting-started/chat/Everything-About-Chat#gsc.tab=0) +- [Number of] Annual Control members @ $18.00 + - Any members included in your annual subscription on the Control plan. +- [Number of] Pay-per-use Control members @ $36.00 + - Any members above your annual subscription size on the Control plan. They're billed at the pay-per-use rate. +- [Number of] Annual Collect members @ $10.00 + - Any members included in your annual subscription on the Collect plan. +- [Number of] Pay-per-use Collect members @ $20.00 + - Any members above your annual subscription size on the Collect plan. These members are billed at the pay-per-use rate. +- [Number of] Free members @ $0.00 + - All members across any of your Free workspaces. +- X% Expensify Card discount with $Y spend + - This shows the % discount you're getting based on total spend across your Expensify Cards. This is only available in the US. +- X% Expensify Card cash back credit for $Y spend + - The amount of cash back you've earned based on total spend across your Expensify Cards. This is only available in the US. +- 50% ExpensifyApproved! partner discount + - If you're part of an accounting firm, you get an additional discount for being our partner. [Learn more about our ExpensifyApproved! accountants program.](https://use.expensify.com/accountants-program) +- Total + - Sum of all the line items above. + +## How-to understand your activity breakdown +This section will list all of your workspaces alongside their IDs and break down the billing for each of them. diff --git a/docs/articles/expensify-classic/expense-and-report-features/Currency.md b/docs/articles/expensify-classic/expense-and-report-features/Currency.md index e5c9096fa610..eb6ca9bb2d40 100644 --- a/docs/articles/expensify-classic/expense-and-report-features/Currency.md +++ b/docs/articles/expensify-classic/expense-and-report-features/Currency.md @@ -1,5 +1,64 @@ --- -title: Currency -description: Currency +title: Report Currency +description: Understanding expense and report currency --- -## Resource Coming Soon! + +# Overview +As a workspace admin, you can choose a default currency for your employees' expense reports, and we’ll automatically convert any expenses into that currency. + +Here are a few essential things to remember: + +- Currency settings for a workspace apply to all expenses under that workspace. If you need different default currencies for certain employees, creating separate workspaces and configuring the currency settings is best. +- As an admin, the currency settings you establish in the workspace will take precedence over any currency settings individual users may have in their accounts. +- Currency is a workspace-level setting, meaning the currency you set will determine the currency for all expenses submitted on that workspace. + +# How to select the currency on a workspace + +## As an admin on a group workspace + +1. Sign into your Expensify web account +2. Go to **Settings > Workspaces > Group > _[Workspace Name]_> Reports > Report Basics** +3. Adjust the **Report Output Currency** + +## On an individual workspace + +1. Sign into your Expensify web account +2. Go to **Settings > Workspaces > Individual >_[Workspace Name]_> Reports > Report Basics** +3. Adjust the **Report Output Currency** + +Please note the currency setting on an individual workspace is overridden when you submit a report on a group workspace. + +# Deep Dive + +## Conversion Rates + +Using data from Open Exchange Rates, Expensify takes the average rate on the day the expense occurred to convert an expense from one currency to another. The conversion rate can vary depending on when the expense happened since the rate is determined after the market closes on that specific date. + +If the markets aren’t open on the day the expense takes place (i.e., on a Saturday), Expensify will use the daily average rate from the last available market day before the purchase took place. + +When an expense is logged for a future date, possibly to anticipate a purchase that has yet to occur, we'll use the most recent available data. This means the report's value may change up to the day of that expense. + +## Managing expenses for employees in several different countries + +Suppose you have employees scattered across the globe who submit expense reports in various currencies. The best way to manage those expenses is to create separate group workspaces for each location or region where your employees are based. + +Then, set the default currency for that workspace to match the currency in which the employees are reimbursed. + +For example, if you have employees in the US, France, Japan, and India, you’d want to create four separate workspaces, add the employees to each, and then set the corresponding currency for each workspace. + +# FAQ + +## I have expenses in several different currencies. How will this show up on a report? + +If you're traveling to foreign countries during a reporting period and making purchases in various currencies, each expense is imported with the currency of the purchase. + +On your expense report, Expensify will automatically convert each expense to the default currency set for the group workspace. + +## How does the currency of an expense impact the conversion rate? + +Expenses entered in a foreign currency are automatically converted to the default currency on your workspace. The conversion uses the day’s average trading rate pulled from [Open Exchange Rates](https://openexchangerates.org/). + +If you want to bypass the exchange rate conversion, you can manually enter an expense in your default currency instead. + + + diff --git a/docs/articles/expensify-classic/expensify-card/Card-Settings.md b/docs/articles/expensify-classic/expensify-card/Card-Settings.md index 35708b6fbb1e..a8d56f267757 100644 --- a/docs/articles/expensify-classic/expensify-card/Card-Settings.md +++ b/docs/articles/expensify-classic/expensify-card/Card-Settings.md @@ -2,74 +2,76 @@ title: Expensify Card Settings description: Admin Card Settings and Features --- -## Expensify Card - admin settings and features -​ + # Overview -​ + The Expensify Card offers a range of settings and functionality to customize how admins manage expenses and card usage in Expensify. To start, we'll lay out the best way to make these options work for you. -​ + Set Smart Limits to control card spend. Smart Limits are spend limits that can be set for individual cards or specific groups. Once a given Smart Limit is reached, the card is temporarily disabled until expenses are approved. -​ + Monitor spend using your Domain Limit and the Reconciliation Dashboard. Your Domain Limit is the total Expensify Card limit across your entire organization. No member can spend more than what's available here, no matter what their individual Smart Limit is. A Domain Limit is dynamic and depends on a number of factors, which we'll explain below. -​ + Decide the settlement model that works best for your business Monthly settlement is when your Expensify Card balance is paid in full on a certain day each month. Though the Expensify Card is set to settle daily by default, any Domain Admin can change this setting to monthly. -​ + Now, let's get into the mechanics of each piece mentioned above. -​ + # How to set Smart Limits Smart Limits allow you to set a custom spend limit for each Expensify cardholder, or default limits for groups. Setting a Smart Limit is the step that activates an Expensify card for your user (and issues a virtual card for immediate use). -​ + ## Set limits for individual cardholders As a Domain Admin, you can set or edit Custom Smart Limits for a card by going to Settings > Domains > Domain Name > Company Cards. Simply click Edit Limit to set the limit. This limit will restrict the amount of unapproved (unsubmitted and Processing) expenses that a cardholder can incur. After the limit is reached, the cardholder won't be able to use their card until they submit outstanding expenses and have their card spend approved. If you set the Smart Limit to $0, the user's card can't be used. ## Set default group limits Domain Admins can set or edit custom Smart Limits for a domain group by going to Settings > Domains > Domain Name > Groups. Just click on the limit in-line for your chosen group and amend the value. -​ + This limit will apply to all members of the Domain Group who do not have an individual limit set via Settings > Domains > Domain Name > Company Cards. + ## Refreshing Smart Limits To let cardholders keep spending, you can approve their pending expenses via the Reconciliation tab. This will free up their limit, allowing them to use their card again. -​ + To check an unapproved card balance and approve expenses, click on Reconciliation and enter a date range, then click though the Unapproved total to see what needs approving. You can add to a new report or approve an existing report from here. -​ + You can also increase a Smart Limit at any time by clicking Edit Limit. -​ + # Understanding your Domain Limit -​ + To get the most accurate Domain Limit for your company, connect your bank account via Plaid under Settings > Account > Payments > Add Verified Bank Account. -​ + If your bank isn't supported or you're having connection issues, you can request a custom limit under Settings > Domains > Domain Name > Company Cards > Request Limit Increase. As a note, you'll need to provide three months of unredacted bank statements for review by our risk management team. -​ + Your Domain Limit may fluctuate from time to time based on various factors, including: -​ + - Available funds in your Verified Business Bank Account: We regularly check bank balances via Plaid. A sudden drop in balance within the last 24 hours may affect your limit. For 'sweep' accounts, be sure to maintain a substantial balance even if you're sweeping daily. - Pending expenses: Review the Reconciliation Dashboard to check for large pending expenses that may impact your available balance. Your Domain Limit will adjust automatically to include pending expenses. - Processing settlements: Settlements need about three business days to process and clear. Several large settlements over consecutive days may impact your Domain Limit, which will dynamically update when settlements have cleared. -​ -As a note, if your Domain Limit is reduced to $0, your cardholders can't make purchases even if they have a larger Smart Limit set on their individual cards. + +As a note, if your Domain Limit is reduced to $0, your cardholders can't make purchases even if they have a larger Smart Limit set on their individual cards. + # How to reconcile Expensify Cards ## How to reconcile expenses Reconciling expenses is essential to ensuring your financial records are accurate and up-to-date. -​ + Follow the steps below to quickly review and reconcile expenses associated with your Expensify Cards: -​ + 1. Go to Settings > Domains > Domain Name > Company Cards > Reconciliation > Expenses 2. Enter your start and end dates, then click Run 3. The Imported Total will show all Expensify Card transactions for the period 4. You'll also see a list of all Expensify Cards, the total spend on each card, and a snapshot of expenses that have and have not been approved (Approved Total and Unapproved Total, respectively) By clicking on the amounts, you can view the associated expenses -​ + + ## How to reconcile settlements A settlement is the payment to Expensify for the purchases made using the Expensify Cards. -​ + The Expensify Card program can settle on either a daily or monthly basis. One thing to note is that not all transactions in a settlement will be approved when running reconciliation. -​ + You can view the Expensify Card settlements under Settings > Domains > Domain Name > Company Cards > Reconciliation > Settlements. -​ + By clicking each settlement amount, you can see the transactions contained in that specific payment amount. -​ + Follow the below steps to run reconciliation on the Expensify Card settlements: -​ + 1. Log into the Expensify web app 2. Click Settings > Domains > Domain Name > Company Cards > Reconciliation tab > Settlements 3. Use the Search function to generate a statement for the specific period you need @@ -82,7 +84,7 @@ Follow the below steps to run reconciliation on the Expensify Card settlements: - Card: refers to the Expensify credit card number and cardholder's email address - Business Account: the business bank account connected to Expensify that the settlement is paid from - Transaction ID: a special ID that helps Expensify support locate transactions if there's an issue -​ + 5. Review the individual transactions (debits) and the payments (credits) that settled them 6. Every cardholder will have a virtual and a physical card listed. They're handled the same way for settlements, reconciliation, and exporting. 7. Click Download CSV for reconciliation @@ -90,77 +92,78 @@ Follow the below steps to run reconciliation on the Expensify Card settlements: 9. To reconcile pre-authorizations, you can use the Transaction ID column in the CSV file to locate the original purchase 10. Review account payments 11. You'll see payments made from the accounts listed under Settings > Account > Payments > Bank Accounts. Payment data won't show for deleted accounts. -​ + You can use the Reconciliation Dashboard to confirm the status of expenses that are missing from your accounting system. It allows you to view both approved and unapproved expenses within your selected date range that haven't been exported yet. -​ + + # Deep dive ## Set a preferred workspace Some customers choose to split their company card expenses from other expense types for coding purposes. Most commonly this is done by creating a separate workspace for card expenses. -​ + You can use the preferred workspace feature in conjunction with Scheduled Submit to make sure all newly imported card expenses are automatically added to reports connected to your card-specific workspace. + ## How to change your settlement account You can change your settlement account to any other verified business bank account in Expensify. If your bank account is closing, make sure you set up the replacement bank account in Expensify as early as possible. -​ + To select a different settlement account: -​ + 1. Go to Settings > Domains > Domain Name > Company Cards > Settings tab 2. Use the Expensify Card settlement account dropdown to select a new account 3. Click Save -​ + + ## Change the settlement frequency -​ + By default, the Expensify Cards settle on a daily cadence. However, you can choose to have the cards settle on a monthly basis. -​ + 1. Monthly settlement is only available if the settlement account hasn't had a negative balance in the last 90 days 2. There will be an initial settlement to settle any outstanding spend that happened before switching the settlement frequency 3. The date that the settlement is changed to monthly is the settlement date going forward (e.g. If you switch to monthly settlement on September 15th, Expensify Cards will settle on the 15th of each month going forward) -​ + To change the settlement frequency: 1. Go to Settings > Domains > Domain Name > Company Cards > Settings tab 2. Click the Settlement Frequency dropdown and select Monthly 3. Click Save to confirm the change -​ -​ + + + ## Declined Expensify Card transactions As long as you have 'Receive realtime alerts' enabled, you'll get a notification explaining the decline reason. You can enable alerts in the mobile app by clicking on three-bar icon in the upper-left corner > Settings > toggle Receive realtime alerts on. -​ + If you ever notice any unfamiliar purchases or need a new card, go to Settings > Account > Credit Card Import and click on Request a New Card right away. -​ + Here are some reasons an Expensify Card transaction might be declined: -​ + 1. You have an insufficient card limit - If a transaction amount exceeds the available limit on your Expensify Card, the transaction will be declined. It's essential to be aware of the available balance before making a purchase to avoid this - you can see the balance under Settings > Account > Credit Card Import on the web app or mobile app. Submitting expenses and having them approved will free up your limit for more spend. -​ + 2. Your card hasn't been activated yet, or has been canceled - If the card has been canceled or not yet activated, it won't process any transactions. -​ + 3. Your card information was entered incorrectly. Entering incorrect card information, such as the CVC, ZIP or expiration date will also lead to declines. -​ + 4. There was suspicious activity - If Expensify detects unusual or suspicious activity, we may block transactions as a security measure. This could happen due to irregular spending patterns, attempted purchases from risky vendors, or multiple rapid transactions. Check your Expensify Home page to approve unsual merchants and try again. If the spending looks suspicious, we may do a manual due diligence check, and our team will do this as quickly as possible - your cards will all be locked while this happens. -​ 5. The merchant is located in a restricted country - Some countries may be off-limits for transactions. If a merchant or their headquarters (billing address) are physically located in one of these countries, Expensify Card purchases will be declined. This list may change at any time, so be sure to check back frequently: Belarus, Burundi, Cambodia, Central African Republic, Democratic Republic of the Congo, Cuba, Iran, Iraq, North Korea, Lebanon, Libya, Russia, Somalia, South Sudan, Syrian Arab Republic, Tanzania, Ukraine, Venezuela, Yemen, and Zimbabwe. -​ + # FAQ ## What happens when I reject an Expensify Card expense? -​ -​ Rejecting an Expensify Card expense from an Expensify report will simply allow it to be reported on a different report. You cannot undo a credit card charge. -​ + If an Expensify Card expense needs to be rejected, you can reject the report or the specific expense so it can be added to a different report. The rejected expense will become Unreported and return to the submitter's Expenses page. -​ + If you want to dispute a card charge, please message Concierge to start the dispute process. -​ + If your employee has accidentally made an unauthorised purchase, you will need to work that out with the employee to determine how they will pay back your company. -​ -​ + + ## What happens when an Expensify Card transaction is refunded? -​ -​ + + The way a refund is displayed in Expensify depends on the status of the expense (pending or posted) and whether or not the employee also submitted an accompanying SmartScanned receipt. Remember, a SmartScanned receipt will auto-merge with the Expensify Card expense. -​ + - Full refunds: If a transaction is pending and doesn't have a receipt attached (except for eReceipts), getting a full refund will make the transaction disappear. If a transaction is pending and has a receipt attached (excluding eReceipts), a full refund will zero-out the transaction (amount becomes zero). diff --git a/docs/articles/expensify-classic/expensify-card/Get-The-Card.md b/docs/articles/expensify-classic/expensify-card/Get-The-Card.md deleted file mode 100644 index e5233a3732a3..000000000000 --- a/docs/articles/expensify-classic/expensify-card/Get-The-Card.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Get the Card -description: Get the Card ---- -## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Request-the-Card.md b/docs/articles/expensify-classic/expensify-card/Request-the-Card.md new file mode 100644 index 000000000000..4830c0fffbcd --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Request-the-Card.md @@ -0,0 +1,49 @@ +--- +title: Request the Card +description: Details on requesting the Expensify Card as an employee +--- +# Overview + +Once your organization is approved for the Expensify Card, you can request a card! + +This article covers how to request, activate, and replace your physical and virtual Expensify Cards. + +# How to get your first Expensify Card + +An admin in your organization must first enable the Expensify Cards before you can receive a card. After that, an admin may assign you a card by setting a limit. You can think of setting a card limit as “unlocking” access to the card. + +If you haven’t been assigned a limit yet, look for the task on your account's homepage that says, “Ask your admin for the card!” This task allows you to message your admin team to make that request. + +Once you’re assigned a card limit, we’ll notify you via email to let you know you can request a card. A link within the notification email will take you to your account’s homepage, where you can provide your shipping address for the physical card. Enter your address, and we’ll ship the card to arrive within 3-5 business days. + +Once your physical card arrives in the mail, activate it in Expensify by entering the last four digits of the card in the activation task on your account’s homepage. + +# Virtual Card + +Once assigned a limit, a virtual card is available immediately. You can view the virtual card details via **Settings > Account > Credit Card Import > Show Details**. Feel free to begin transacting with the virtual card while your physical card is in transit – your virtual card and physical card share a limit. + +Please note that you must enable two-factor authentication on your account if you want to have the option to dispute transactions made on your virtual card. + +# Notifications + +To stay up-to-date on your card’s limit and spending activity, download the Expensify mobile app and enable push notifications. Your card is connected to your Expensify account, so each transaction on your card will trigger a push notification. We’ll also send you a push notification if we detect potentially fraudulent activity and allow you to confirm your purchase. + +# How to request a replacement Expensify Card + +You can request a new card anytime if your Expensify Card is lost, stolen, or damaged. From your Expensify account on the web, head to **Settings > Account > Credit Card Import** and click **Request a New Card**. Confirm the shipping information, complete the prompts, and your new card will arrive in 2 - 3 business days. + +Selecting the “lost” or “stolen” options will deactivate your current card to prevent potentially fraudulent activity. However, choosing the “damaged” option will leave your current card active so you can use it while the new one is shipped to you. + +If you need to cancel your Expensify Card and cannot access the website or mobile app, call our interactive voice recognition phone service (available 24/7). Call 1-877-751-5848 (US) or +44 808 196 0632 (Internationally). + +It's not possible to order a replacement card over the phone, so, if applicable, you would need to handle this step from your Expensify account. + +# FAQ + +## What if I haven’t received my card after multiple weeks? + +Reach out to support, and we can locate a tracking number for the card. If the card shows as delivered, but you still haven’t received it, you’ll need to confirm your address and order a new one. + +## I’m self-employed. Can I set up the Expensify Card as an individual? + +Yep! As long as you have a business bank account and have registered your company with the IRS, you are eligible to use the Expensify Card as an individual business owner. diff --git a/docs/articles/expensify-classic/getting-started/Tips-And-Tricks.md b/docs/articles/expensify-classic/getting-started/Tips-And-Tricks.md new file mode 100644 index 000000000000..b692bf466413 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Tips-And-Tricks.md @@ -0,0 +1,72 @@ +--- +title: Tips and Tricks +description: How to get started with setup tips for your Expensify account +--- + +# Overview +In this article, we'll outline helpful tips for using Expensify, such as keyboard shortcuts and text formatting. + +# How to Format Text in Expensify +You can use a basic markdown in report comments to emphasize or clarify your sentiments. This includes italicizing, bolding, and strikethrough for text, as well as adding basic hyperlinks. +Formatting is consistent across both web and mobile applications, with three markdown options available for your report comments: +- **Bold:** Place an asterisk on either side (*bold*) +- **Italicize:** Place an underscore on either side (_italic_) +- **Strikethrough:** Place a tilde on either side (~strikethrough~) + +# How to Use Keyboard Shortcuts +Keyboard shortcuts can speed things up and simplify tasks. Expensify offers several shortcuts for your convenience. Let's explore them! +- **Shift + ?** - Opens the keyboard shortcuts dialog +- **Shift + G** - Prompts you for a reportID to open the report page for a specific report +- **ESC** - Closes any shortcut dialog window +- **Ctrl+Enter** - Submit a comment on a report from the comment field in the Report History & Comments section. +- **Shift + P** - Takes you to the report’s policy when you’re on a report +- **Shift + →** - Go to the next report +- **Shift + ←** - Go to the previous report +- **Shift + R** - Reloads the current page + +# How to Create a Copy of a Report +If you have identical monthly expenses and want to copy them easily, visit your Reports page, check the box next to the report you would like to duplicate, and click "Copy" to duplicate all expenses (excluding receipt images). +If you prefer, you can create a standard template for certain expenses: +1. Go to the Reports page. +2. Click "New Report." +3. Assign an easily searchable name to the report. +4. Click the green '+' button to add an expense. +5. Choose "New Expense." +6. Select the type of expense (e.g., regular expense, distance, time, etc.). +7. Enter the expense details, code, and any relevant description. +8. Click "Save." +**Pro Tip:** If you use Scheduled Submit, place the template report under your individual workspace to avoid accidental submission. When you're ready to use it, check the report box, copy it, and make necessary changes to the name and workspace. + +# How to Enable Location Access on Web +If you’d like to use features that rely on your current location, you will need to enable location permissions for Expensify. You can find instructions for enabling location settings on the three most common web browsers below. If your browser is not on the list, then please do a web search for your browser and “enable location settings”. + +## Chrome +1. Open Chrome +2. At the top right, click the three-dot Menu > Settings +3. Click “Privacy and Security” and then “Site Settings” +4. Click "Location" +5. Check the “Not allowed to see your location” list to ensure that expensify.com and new.expensify.com are not listed. If they are, click the delete icon next to them to allow location access + +## Firefox +1. Open Firefox +2. In the URL bar enter “about:preferences” +3. On the left-hand side select “Privacy & Security” +4. Scroll down to Permissions +5. Click on Settings next to Location +6. If location access is blocked for expensify.com or new.expensify.com, you can update it here to allow access + +## Safari +1. In the top menu bar, click Safari +2. Then select Settings > Websites +3. Click Location on the left-hand side +4. If expensify.com or new.expensify.com have “Deny” set as their access, update it to “Ask” or “Allow” + +# Which browser works best with Expensify? +We recommend using Google Chrome, but you can use Expensify on most major browsers, such as: +- [Google Chrome](https://google.com/chrome/) +- [Mozilla Firefox](https://mozilla.com/firefox) +- [Microsoft Edge](https://microsoft.com/edge) +- [Microsoft Internet Explorer](https://microsoft.com/ie). Please note: Microsoft has discontinued support and security updates for all versions below Version 11. This means those older versions may not work well. Due to the lack of security updates for the older versions, parts of our site may not be accessible. Please update your IE version or choose a different browser. +- [Apple Safari (Apple devices only)](https://apple.com/safari) +- [Opera](https://opera.com) +It's always best practice to ensure you have the most recent updates for your browser and keep your operating system up to date. diff --git a/docs/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options.md b/docs/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options.md index 31f5aaf93032..dfc545c4c367 100644 --- a/docs/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options.md +++ b/docs/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options.md @@ -14,7 +14,7 @@ From the **Expenses** page, you can export individual expenses into a CSV. From 3. Click **Export to** at the top right of the page 4. Choose the desired export option -You can use one of the default templates or create your own template. The default templates and the option to export to a connected accounting package are only available on the **Reports** page. Visit the specific help page for your accounting package to learn more about how to get this set up. +You can use one of the [default templates](https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Default-Export-Templates#gsc.tab=0) or [create your own template](https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates#gsc.tab=0). The default templates and the option to export to a connected accounting package are only available on the **Reports** page. Visit the specific help page for your accounting package to learn more about how to get this set up. # How to export a report as a PDF 1. Go to the **Reports** page diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Bolt.md b/docs/articles/expensify-classic/integrations/travel-integrations/Bolt.md deleted file mode 100644 index 3ee1c8656b4b..000000000000 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Bolt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Coming Soon -description: Coming Soon ---- -## Resource Coming Soon! diff --git a/docs/articles/new-expensify/getting-started/Security.md b/docs/articles/new-expensify/getting-started/Security.md new file mode 100644 index 000000000000..5c8eee7ae60e --- /dev/null +++ b/docs/articles/new-expensify/getting-started/Security.md @@ -0,0 +1,53 @@ +--- +title: Security +description: Expensify prioritizes data security and maintains strict compliance standards to safeguard users' sensitive information. +--- + + +# Overview + +We take security seriously. Our measures align with what banks use to protect sensitive financial data. We regularly test and update our security to stay ahead of any threats. Plus, we're checked daily by McAfee for extra reassurance against hackers. You can verify our security strength below or on the McAfee SECURE site. + +Discover how Expensify safeguards your information below! + +## The Gold Standard of Security + +Expensify follows the highest standard of security, known as the Payment Card Industry Data Security Standard. This standard is used by major companies like PayPal, Visa, and banks to protect online credit card information. It covers many aspects of how systems work together securely. You can learn more about it on the PCI-DSS website. And, Expensify is also compliant with SSAE 16! + + +## Data and Password Encryption + +When you press 'enter,' your data transforms into a secret code, making it super secure. This happens whether it's moving between your browser and our servers or within our server network. In tech talk, we use HTTPS+TLS for all web connections, ensuring your information is encrypted at every stage of the journey. This means your data is always protected! + +## Account Safety + +Protecting your data on our servers is our top priority. We've taken strong measures to ensure your data is safe when it travels between you and us and when it's stored on our servers. +In our first year, we focused on creating a super-reliable, geographically redundant, and PCI compliant data center. This means your data stays safe, and our systems stay up and running. +We use a dual-control key, which only our servers know about. This key is split into two parts and stored in separate secure places, managed by different Expensify employees. +With this setup, sensitive data stays secure and can't be accessed outside our secure servers. + +## Our Commitment to GDPR + +The General Data Protection Regulation (GDPR), introduced by the European Commission, is a set of rules to strengthen and unify data protection for individuals in the European Union (EU). It also addresses the transfer of personal data outside the EU. This regulation applies not only to EU-based organizations but also to those outside the EU that handle the data of EU citizens. The compliance deadline for GDPR was May 25, 2018. + +Our commitment to protecting the privacy of our customer’s data includes: + +- Being active participants in the EU-US Privacy Shield and Swiss-US Privacy Shield Frameworks. +- Undergoing annual SSAE-18 SOC 1 Type 2 audit by qualified, independent third-party auditors. +- Maintaining PCI-DSS compliance. +- Leveraging third-party experts to conduct yearly penetration tests. +- All employees and contractors are subject to background checks (refreshed. annually), sign non-disclosure agreements, and are subject to ongoing security and privacy training. + + +We have worked diligently to ensure we comply with GDPR. Here are some key changes we made: + + +- **Enhanced Security and Data Privacy**: We've strengthened our security measures and carefully reviewed our privacy policies to align with GDPR requirements. +- **Dedicated Data Protection Officer**: We've appointed a dedicated Data Protection Officer who can be reached at [privacy@expensify.com](mailto:privacy@expensify.com) for any privacy-related inquiries. +- **Vendor Agreements**: We've signed Data Processing Addendums (DPAs) with all our vendors to ensure your data is handled safely during onward transfers. +- **Transparency**: You can find details about the sub-processors we use on our website. +- **Privacy Shield Certification**: We maintain certifications for the E.U.-U.S. Privacy Shield and the Swiss-U.S. Privacy Shield, which help secure international data transfers. +- **GDPR Compliance**: We have a Data Processing Addendum that outlines the terms to meet GDPR requirements. You can request a copy by contacting [concierge@expensify.com](mailto:concierge@expensify.com). +- **User Control**: Our product tools allow users to export data, manage preferences, and close accounts anytime. + +**Disclaimer**: Please note that the information on this page is for informational purposes only and is not intended as legal advice. It's essential to consult with legal and professional counsel to understand how GDPR may apply to your specific situation. diff --git a/docs/articles/new-expensify/workspace-and-domain-settings/Domain-Settings-Overview.md b/docs/articles/new-expensify/workspace-and-domain-settings/Domain-Settings-Overview.md new file mode 100644 index 000000000000..cf2f0f59a4a0 --- /dev/null +++ b/docs/articles/new-expensify/workspace-and-domain-settings/Domain-Settings-Overview.md @@ -0,0 +1,140 @@ +--- +title: Domains +description: Want to gain greater control over your company settings in Expensify? Read on to find out more about our Domains feature and how it can help you save time and effort when managing your company expenses. +--- + +# Overview +Domains is a feature in Expensify that allows admins to have more nuanced control over a specific Expensify activity, as well as providing a bird’s eye view of company card expenditure. Think of it as your command center for things like managing user account access, enforcing stricter Workspace rules for certain groups, or issuing cards and reconciling statements. +There are several settings within Domains that you can configure so that you have more control and visibility into your organization’s settings. Those features are: +- Company Cards +- Domain Admins +- Domain Members + - Two-Factor Authentication +- Domain Groups + - Domain Group Settings +- Reporting Tools +- SAML + +There are two ways to use Domains – as an unverified domain or a verified domain. An unverified domain allows you to import Company Cards and manage them, whereas a verified domain allows you to do that in addition to: +1. Receive vendor bills in Expensify +2. Fine-tune user restrictions using domain Groups +3. Configure SAML SSO for easier login to Expensify +4. Set vacation delegates for your domain members +5. Use consolidated domain billing + +# How to claim a domain +To use the domains feature with an unverified domain, you’ll need to claim the domain first. +To claim a domain, you need to be a Workspace Admin with a company email address. This allows you to manage company bills, company cards, and reconciliation. Claiming requires an email matching your company's domain. +1. Create an Expensify account +2. Set up an expense Workspace +3. Go to **Settings > _Domains_**. +Whichever member runs through those steps will automatically be made a Domain Admin. + + +# How to verify a domain +To use the domains feature with a verified domain, you’ll want to go through the steps of verifying it. + +To verify domain ownership, follow these steps: +1. Log in to your DNS service provider, which could be your Domain Name Registrar like NameCheap or GoDaddy, a dedicated DNS service provider like DNSMadeEasy or Amazon Route53, or managed internally by your company's IT department. +2. Find the page for editing DNS records for expensify.com. This might be labeled as DNS Management or Zone File Editor. +3. Add a new TXT record and set the value as: **532F6180D8** +4. Save your changes +5. Click the Verify button to confirm domain ownership + +After successful verification, you can remove the TXT DNS record. Please note that an email will be sent to all Expensify users on the domain to inform them that their accounts will be under Domain Control after verification. + +**Tips:** +Not sure how to do this? Check the below guides from some of the most popular hosts on the web: +[123-reg.co.uk](https://www.123-reg.co.uk/) +[One.com](https://www.one.com/en/) +[Wix.com](https://www.wix.com/) +Google/GSuite +[Godaddy](https://www.godaddy.com/) +When creating the TXT record, input only the code and no other values or information. +You can always confirm if you added the TXT code correctly here: https://viewdns.info/dnsrecord/?domain=[enterdomainhere] + +# Domain settings + +## Domain Admins +Domain Admins have full authority over domain settings. They can modify member group names and rules, link or modify Company Cards, and add or remove domain members and other admins. + +### Adding a Domain Admin +1. Head to **Settings > Domains > [Domain Name] > Domain Admins** +2. In the "Email or Phone" field, type in the email address of the person you want to make a Domain Admin (this can be any email not specifically tied to the domain) +3. Click "Add Admin" + +### Removing a Domain Admin: +1. If you're already a Domain Admin, go to **Settings > Domains > [Domain Name] > Domain Admins** +2. Locate the list of Domain Admins and find the one you want to remove +3. Next to the Domain Admin's name, click the red trash can icon. This will remove that person from the Domain Admin role + +## Domain Members +A domain member is a user associated with a specific domain (usually a company or another group) in Expensify and typically managed by a Domain Admin. This is also where you can enable Two-Factor authentication for your domain. + +### Adding users to the domain +When a Domain Admin adds a user to the domain, that will create a new Expensify account for that user, and they'll receive invitations to set up their account. Users can also join a verified domain by creating their own account, as long as they have an email address associated with that domain (e.g. yourname@yourcompany.com). Once they have verified the account, all Domain Admins will be notified, and the employee will be added to the Default Group. +**Important Note:** If someone who isn't a Domain Admin invites a user to a Workspace before they're invited to the domain, their account will be created, but in a closed state. A closed state means that the account cannot be used until it has been validated. Once the Domain Admin has invited the user, the user will receive a magic link to verify their account, sign in, and open the account completely. + +### How to add users +1. In your web account, go to **Settings > Domains > [Domain Name] > Domain Admins** +2. In the email field, enter the user you want to invite. This will create their Expensify account and send them an invitation + +### Removing users from the Domain +Removing a user means taking them out of your domain and closing their Expensify account completely if they don't have another login. Be cautious because closing an account is permanent and deletes any unsubmitted or processing reports. + +### How to remove users +In your web account, go to **Settings > Domains > [Domain Name] > Domain Admins** +Check the box next to the employee's name you want to remove, then click “Close Accounts”. + +### Important notes about closing accounts through Domain settings: +If a user has a Secondary Login linked to their Expensify account, they can still access their account after it's closed in the domain. This is helpful for accessing financial data, like tax-related receipts. +Closing an account through the domain permanently removes any unsubmitted receipts/reports. Make sure to approve or reimburse all employee reports before closing an account. +If an employee doesn't have a Secondary Login, they'll be automatically removed from the group Workspace. If they have a Secondary Login, it will continue to be associated with the group Workspace. + +## Domain Groups +Domain Groups can be accessed if you have verified your domain. Groups are used to set rules or permissions for groups of users so you can enforce multiple different expense workspaces and rules. If you are a Domain Admin, you can create and edit Domain Groups under **Settings > Domains > _Domain Name_ > Groups**. + +### Creating Domain Groups +1. In your Expensify account on the web, navigate to **Settings > Domains > _Domain Name_ > Groups** +2. Select “Create Group” to create the group. This will allow you to name the Group, as well as configure permissions that will apply to members of the Group. + +### Adding members to a Domain Group +1. In your Expensify account on the web, navigate to **Settings > Domains > [Domain Name] > Domain Members** +2. Select the checkbox next to the domain members you wish to add to the Domain Group +3. Select “Add to Group” to select the Group you wish to add them to + +### Editing Domain Groups +1. In your Expensify account on the web, navigate to **Settings > Domains > _Domain Name_ > Groups** +2. Next to the Group you wish to edit, select “Edit” +3. This will open the Edit Permission Group pane, where you can edit the rules and permissions for that group +4. Make your edits and click “Save” + +## Domain Group settings +These are the settings that can be customized for each group you have created. Typically, companies use two groups (Employees and Managers) and enforce stricter rules for Employees. The settings are: +- Strict Workspace Enforcement: When enabled, all Workspace rules must be followed for a report to be submitted. If a rule is violated, the report can't be submitted until the issue is fixed. Employees can't bypass this by dismissing notifications. +- Login Restrictions: Enabling this prevents users from using non-company email addresses as their primary login. Secondary logins are still allowed. +- Workspace Creation and Removal Restrictions: This feature stops users from creating new group workspaces or unsubscribing from existing workspaces. Admins who need these abilities should be in a separate group with this restriction turned off. +- Preferred Workspace: When enabled, group members can only create reports under one designated Workspace. They can move a report to a different Workspace or their personal one later if needed. This helps keep personal and company expenses separate. If a company card uses a specific Workspace, this setting overrides it for more control over company card expenses. +- Setting a Preferred Workspace: If Preferred Workspace is on, you can choose a default group Workspace for all Group Members. + +## SAML +To enable SAML SSO in Expensify you will first need to claim and verify your domain. Once you have a verified domain, you can access SAML SSO by navigating to **Settings > Domains > _Domain Name_ > SAML** + +## Enable Two-Factor Authentication (2FA) +1. As a Domain Admin, head to: **Settings > Domains > _Your Domain Name_ > Domain Members** +2. Turn on Two Factor Authentication by toggling it to ENABLED +3. Any Domain members that do not have two-factor authentication enabled will be asked to set it up on their Home page when they next log in, and won't be able to use Expensify until they do. +4. To turn it off, simply toggle it off and refresh the page. + +**Tips:** +- When using SAML, two-factor authentication cannot be required. +- For disputing digital Expensify Card purchases, two-factor authentication must be enabled. +- It might take up to 2 hours for domain-level enforcement to take effect, and users will be prompted to configure their individual 2FA settings on their next login to Expensify. + +# FAQ + +## How many domains can I have? +You can manage multiple domains by adding them through **Settings > Domains > New Domain**. However, to verify additional domains, you must be a Workspace Admin on a Control Workspace. Keep in mind that the Collect plan allows verification for just one domain. + +## What’s the difference between claiming a domain and verifying a domain? +Claiming a domain is limited to users with matching email domains, and allows Workspace Admins with a company email to manage bills, company cards, and reconciliation. Verifying a domain offers extra features and security. diff --git a/docs/assets/images/Auto-Reconciliation_Image2.png b/docs/assets/images/Auto-Reconciliation_Image2.png index 5c7ed2d6b7ea..4e53dc49f6f1 100644 Binary files a/docs/assets/images/Auto-Reconciliation_Image2.png 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 index 9fcb7e316aaf..a9a4a33954c8 100644 Binary files a/docs/assets/images/Auto-Reconciliaton_Image1.png and b/docs/assets/images/Auto-Reconciliaton_Image1.png differ diff --git a/docs/assets/images/ExpensifyHelp_ApprovingReports_01.png b/docs/assets/images/ExpensifyHelp_ApprovingReports_01.png index a9bc57525a1a..587b7cb1d9d8 100644 Binary files a/docs/assets/images/ExpensifyHelp_ApprovingReports_01.png and b/docs/assets/images/ExpensifyHelp_ApprovingReports_01.png differ diff --git a/docs/assets/images/ExpensifyHelp_ApprovingReports_02.png b/docs/assets/images/ExpensifyHelp_ApprovingReports_02.png index 4bd2c5af455b..1376c8ccf71c 100644 Binary files a/docs/assets/images/ExpensifyHelp_ApprovingReports_02.png and b/docs/assets/images/ExpensifyHelp_ApprovingReports_02.png differ diff --git a/docs/assets/images/ExpensifyHelp_ApprovingReports_03.png b/docs/assets/images/ExpensifyHelp_ApprovingReports_03.png index f5318cd5272a..3bf84342edf6 100644 Binary files a/docs/assets/images/ExpensifyHelp_ApprovingReports_03.png and b/docs/assets/images/ExpensifyHelp_ApprovingReports_03.png differ diff --git a/docs/assets/images/ExpensifyHelp_CardSettings.png b/docs/assets/images/ExpensifyHelp_CardSettings.png index c10a3d1cbc39..8339bc41ab7f 100644 Binary files a/docs/assets/images/ExpensifyHelp_CardSettings.png and b/docs/assets/images/ExpensifyHelp_CardSettings.png differ diff --git a/docs/assets/images/ExpensifyHelp_ExpenseRules_01.png b/docs/assets/images/ExpensifyHelp_ExpenseRules_01.png index 7a6c3c1b3a13..ad71c304a06f 100644 Binary files a/docs/assets/images/ExpensifyHelp_ExpenseRules_01.png 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 index 28c6a7689b77..58d448f4c1f4 100644 Binary files a/docs/assets/images/ExpensifyHelp_ExpenseRules_02.png and b/docs/assets/images/ExpensifyHelp_ExpenseRules_02.png differ diff --git a/docs/assets/images/ExpensifyHelp_Lyft_01.png b/docs/assets/images/ExpensifyHelp_Lyft_01.png index 35959779ec09..15c21c750d1d 100644 Binary files a/docs/assets/images/ExpensifyHelp_Lyft_01.png and b/docs/assets/images/ExpensifyHelp_Lyft_01.png differ diff --git a/docs/assets/images/ExpensifyHelp_SettlementExpanded.png b/docs/assets/images/ExpensifyHelp_SettlementExpanded.png index 8672c8639202..8b0693c59a40 100644 Binary files a/docs/assets/images/ExpensifyHelp_SettlementExpanded.png and b/docs/assets/images/ExpensifyHelp_SettlementExpanded.png differ diff --git a/docs/assets/images/ExpensifyHelp_SettlementExport.png b/docs/assets/images/ExpensifyHelp_SettlementExport.png index b8c9a36220d4..de2a52495576 100644 Binary files a/docs/assets/images/ExpensifyHelp_SettlementExport.png and b/docs/assets/images/ExpensifyHelp_SettlementExport.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_1.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_1.png index d4e73beb16b3..988aa216b59c 100644 Binary files a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_1.png and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_1.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_2.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_2.png index 45956a586d98..3b441e9cdf1a 100644 Binary files a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_2.png and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_2.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_3.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_3.png index 32aae12d3687..d1ff39eb22f6 100644 Binary files a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_3.png and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_3.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_4.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_4.png index ccd9335025bf..316d3f5aca6f 100644 Binary files a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_4.png and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_4.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_5.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_5.png index 5363935f0ab5..a19880d950fd 100644 Binary files a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_5.png and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_5.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_6.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_6.png index 739446de8383..af7f3421336e 100644 Binary files a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_6.png and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_6.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_7.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_7.png index 21a1d3416858..260558881327 100644 Binary files a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_7.png and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_7.png differ diff --git a/docs/assets/images/accounting.svg b/docs/assets/images/accounting.svg index 4398e9573747..bacc7ae21e68 100644 --- a/docs/assets/images/accounting.svg +++ b/docs/assets/images/accounting.svg @@ -1,68 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/add-australian-deposit-only-account-modal.png b/docs/assets/images/add-australian-deposit-only-account-modal.png index 1196a57c8f8f..86fd488fa947 100644 Binary files a/docs/assets/images/add-australian-deposit-only-account-modal.png and b/docs/assets/images/add-australian-deposit-only-account-modal.png differ diff --git a/docs/assets/images/add-australian-deposit-only-account.png b/docs/assets/images/add-australian-deposit-only-account.png index 4cea4fb11757..d9593f9958af 100644 Binary files a/docs/assets/images/add-australian-deposit-only-account.png and b/docs/assets/images/add-australian-deposit-only-account.png differ diff --git a/docs/assets/images/add-vba-australian-account-modal.png b/docs/assets/images/add-vba-australian-account-modal.png index ee624eca3814..fd02919f1586 100644 Binary files a/docs/assets/images/add-vba-australian-account-modal.png and b/docs/assets/images/add-vba-australian-account-modal.png differ diff --git a/docs/assets/images/add-vba-australian-account.png b/docs/assets/images/add-vba-australian-account.png index f064225e176a..dbe06801de62 100644 Binary files a/docs/assets/images/add-vba-australian-account.png and b/docs/assets/images/add-vba-australian-account.png differ diff --git a/docs/assets/images/arrow-right.svg b/docs/assets/images/arrow-right.svg index 01e94332393c..2565efbf30a5 100644 --- a/docs/assets/images/arrow-right.svg +++ b/docs/assets/images/arrow-right.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/arrow-up.svg b/docs/assets/images/arrow-up.svg index 9f609925d910..844ef49314e4 100644 --- a/docs/assets/images/arrow-up.svg +++ b/docs/assets/images/arrow-up.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/back-left.svg b/docs/assets/images/back-left.svg index 071cd22b1644..e6ea47be3da5 100644 --- a/docs/assets/images/back-left.svg +++ b/docs/assets/images/back-left.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/bank-card.svg b/docs/assets/images/bank-card.svg index 48da9af0d986..051d028d4af2 100644 --- a/docs/assets/images/bank-card.svg +++ b/docs/assets/images/bank-card.svg @@ -1,41 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/cancel-reimbursement.png b/docs/assets/images/cancel-reimbursement.png index 87317d2f60ba..9257cd759214 100644 Binary files a/docs/assets/images/cancel-reimbursement.png and b/docs/assets/images/cancel-reimbursement.png differ diff --git a/docs/assets/images/circle-hourglass.svg b/docs/assets/images/circle-hourglass.svg index 5c307db6bb04..68359ab49315 100644 --- a/docs/assets/images/circle-hourglass.svg +++ b/docs/assets/images/circle-hourglass.svg @@ -1,9 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/close.svg b/docs/assets/images/close.svg index 71e4df7ace0c..9b050b1e91c6 100644 --- a/docs/assets/images/close.svg +++ b/docs/assets/images/close.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/concierge-avatar.svg b/docs/assets/images/concierge-avatar.svg index d2a7cf31ac98..eb374a9a5a68 100644 --- a/docs/assets/images/concierge-avatar.svg +++ b/docs/assets/images/concierge-avatar.svg @@ -1,39 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/delete-australian-bank-account.png b/docs/assets/images/delete-australian-bank-account.png index 2148973e5a6c..6d2be119f736 100644 Binary files a/docs/assets/images/delete-australian-bank-account.png and b/docs/assets/images/delete-australian-bank-account.png differ diff --git a/docs/assets/images/down.svg b/docs/assets/images/down.svg index d0925eeb1336..c47a53f35ea6 100644 --- a/docs/assets/images/down.svg +++ b/docs/assets/images/down.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/envelope-receipt.svg b/docs/assets/images/envelope-receipt.svg index 40f57cc4ebda..650f4dca9145 100644 --- a/docs/assets/images/envelope-receipt.svg +++ b/docs/assets/images/envelope-receipt.svg @@ -1,35 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/expensify-footer-logo--vertical.svg b/docs/assets/images/expensify-footer-logo--vertical.svg index 58dc05ad2944..9cd5e26cc8f2 100644 --- a/docs/assets/images/expensify-footer-logo--vertical.svg +++ b/docs/assets/images/expensify-footer-logo--vertical.svg @@ -1,30 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/expensify-footer-logo.svg b/docs/assets/images/expensify-footer-logo.svg index e664651b84fd..9e3f837f7365 100644 --- a/docs/assets/images/expensify-footer-logo.svg +++ b/docs/assets/images/expensify-footer-logo.svg @@ -1,30 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/expensify-help.svg b/docs/assets/images/expensify-help.svg index 7aa084e0fc0c..0655b947a27f 100644 --- a/docs/assets/images/expensify-help.svg +++ b/docs/assets/images/expensify-help.svg @@ -1,51 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/expensify-logo-round.png b/docs/assets/images/expensify-logo-round.png index 59d29ed09530..18fa9ba49969 100644 Binary files a/docs/assets/images/expensify-logo-round.png and b/docs/assets/images/expensify-logo-round.png differ diff --git a/docs/assets/images/gears.svg b/docs/assets/images/gears.svg index 23621afc8008..a598dbee28d5 100644 --- a/docs/assets/images/gears.svg +++ b/docs/assets/images/gears.svg @@ -1,101 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/hand-card.svg b/docs/assets/images/hand-card.svg index 779e6ff4184c..4c5b7cfcc666 100644 --- a/docs/assets/images/hand-card.svg +++ b/docs/assets/images/hand-card.svg @@ -1,19 +1 @@ - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/handshake.svg b/docs/assets/images/handshake.svg index 04872bd3a88b..1a2f9badc781 100644 --- a/docs/assets/images/handshake.svg +++ b/docs/assets/images/handshake.svg @@ -1,36 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/insights-chart.png b/docs/assets/images/insights-chart.png index 4b21b8d70a09..54000c0ee307 100644 Binary files a/docs/assets/images/insights-chart.png and b/docs/assets/images/insights-chart.png differ diff --git a/docs/assets/images/lightbulb.svg b/docs/assets/images/lightbulb.svg index 45a889fb9e0c..b15ec9ced77d 100644 --- a/docs/assets/images/lightbulb.svg +++ b/docs/assets/images/lightbulb.svg @@ -1,71 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/menu.svg b/docs/assets/images/menu.svg index 4fc34e918899..6bc8bc8fa8f7 100644 --- a/docs/assets/images/menu.svg +++ b/docs/assets/images/menu.svg @@ -1,11 +1 @@ - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/docs/assets/images/moderation-context-menu.png b/docs/assets/images/moderation-context-menu.png index 55aa17498cf7..e76a09ce8153 100644 Binary files a/docs/assets/images/moderation-context-menu.png and b/docs/assets/images/moderation-context-menu.png differ diff --git a/docs/assets/images/moderation-flag-page.png b/docs/assets/images/moderation-flag-page.png index e60e2ccb8776..e28ff940322d 100644 Binary files a/docs/assets/images/moderation-flag-page.png and b/docs/assets/images/moderation-flag-page.png differ diff --git a/docs/assets/images/moderation-reportee-whisper.png b/docs/assets/images/moderation-reportee-whisper.png index 9235a262bef5..1b8fa77b20b7 100644 Binary files a/docs/assets/images/moderation-reportee-whisper.png and b/docs/assets/images/moderation-reportee-whisper.png differ diff --git a/docs/assets/images/moderation-reporter-whisper.png b/docs/assets/images/moderation-reporter-whisper.png index 881b25268515..68397f838060 100644 Binary files a/docs/assets/images/moderation-reporter-whisper.png and b/docs/assets/images/moderation-reporter-whisper.png differ diff --git a/docs/assets/images/money-case.svg b/docs/assets/images/money-case.svg index c1bb49b3ab5a..e40430c9e27b 100644 --- a/docs/assets/images/money-case.svg +++ b/docs/assets/images/money-case.svg @@ -1,135 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/money-circle.svg b/docs/assets/images/money-circle.svg index 645e1a58701a..f17183ff63b6 100644 --- a/docs/assets/images/money-circle.svg +++ b/docs/assets/images/money-circle.svg @@ -1,12 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/money-into-wallet.svg b/docs/assets/images/money-into-wallet.svg index d6d5b0e7d6e7..06592778bd51 100644 --- a/docs/assets/images/money-into-wallet.svg +++ b/docs/assets/images/money-into-wallet.svg @@ -1,32 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/money-receipt.svg b/docs/assets/images/money-receipt.svg index 379d56727e42..406aaf3c1db7 100644 --- a/docs/assets/images/money-receipt.svg +++ b/docs/assets/images/money-receipt.svg @@ -1,34 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/money-wings.svg b/docs/assets/images/money-wings.svg index c2155080f721..87ffdf28ec4b 100644 --- a/docs/assets/images/money-wings.svg +++ b/docs/assets/images/money-wings.svg @@ -1,26 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/monitor.svg b/docs/assets/images/monitor.svg index 6e2580b4c9e8..c577e6b0db0a 100644 --- a/docs/assets/images/monitor.svg +++ b/docs/assets/images/monitor.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/og-preview-image.png b/docs/assets/images/og-preview-image.png index db4bfac8a5cb..4914e7283779 100644 Binary files a/docs/assets/images/og-preview-image.png and b/docs/assets/images/og-preview-image.png differ diff --git a/docs/assets/images/paper-airplane.svg b/docs/assets/images/paper-airplane.svg index 8daa13bfa38b..152a5d10f21c 100644 --- a/docs/assets/images/paper-airplane.svg +++ b/docs/assets/images/paper-airplane.svg @@ -1,113 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/playbook-existing-corporate-card.png b/docs/assets/images/playbook-existing-corporate-card.png index 5baad14abf7c..9cfbc02e0ab2 100644 Binary files a/docs/assets/images/playbook-existing-corporate-card.png and b/docs/assets/images/playbook-existing-corporate-card.png differ diff --git a/docs/assets/images/playbook-expense-basics.png b/docs/assets/images/playbook-expense-basics.png index b0bbd2095415..99c462070405 100644 Binary files a/docs/assets/images/playbook-expense-basics.png and b/docs/assets/images/playbook-expense-basics.png differ diff --git a/docs/assets/images/playbook-impoort-employees.png b/docs/assets/images/playbook-impoort-employees.png index 4f4f6f4e50ae..1cb8a11b95fc 100644 Binary files a/docs/assets/images/playbook-impoort-employees.png and b/docs/assets/images/playbook-impoort-employees.png differ diff --git a/docs/assets/images/playbook-new-bill.png b/docs/assets/images/playbook-new-bill.png index 8e8a01fe156b..b27d269e862d 100644 Binary files a/docs/assets/images/playbook-new-bill.png and b/docs/assets/images/playbook-new-bill.png differ diff --git a/docs/assets/images/playbook-scheduled-submit.png b/docs/assets/images/playbook-scheduled-submit.png index c8c6eb91774c..32919ae0eb06 100644 Binary files a/docs/assets/images/playbook-scheduled-submit.png and b/docs/assets/images/playbook-scheduled-submit.png differ diff --git a/docs/assets/images/playbook.svg b/docs/assets/images/playbook.svg index 0088d8f915f1..e0e2a883e636 100644 --- a/docs/assets/images/playbook.svg +++ b/docs/assets/images/playbook.svg @@ -1,88 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/reimbursing-default.png b/docs/assets/images/reimbursing-default.png index 6823fecaba35..58970385f5dd 100644 Binary files a/docs/assets/images/reimbursing-default.png and b/docs/assets/images/reimbursing-default.png differ diff --git a/docs/assets/images/reimbursing-manual-warning.png b/docs/assets/images/reimbursing-manual-warning.png index 34ef6bbc8d3c..1f4c6491c5db 100644 Binary files a/docs/assets/images/reimbursing-manual-warning.png and b/docs/assets/images/reimbursing-manual-warning.png differ diff --git a/docs/assets/images/reimbursing-manual.png b/docs/assets/images/reimbursing-manual.png index 8d27f71036ca..a4b66546922e 100644 Binary files a/docs/assets/images/reimbursing-manual.png and b/docs/assets/images/reimbursing-manual.png differ diff --git a/docs/assets/images/reimbursing-reports-dropdown.png b/docs/assets/images/reimbursing-reports-dropdown.png index 5a3da794c5e9..d56ad6cbf250 100644 Binary files a/docs/assets/images/reimbursing-reports-dropdown.png and b/docs/assets/images/reimbursing-reports-dropdown.png differ diff --git a/docs/assets/images/search.svg b/docs/assets/images/search.svg index 9680cc415454..5bd5fc24d858 100644 --- a/docs/assets/images/search.svg +++ b/docs/assets/images/search.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/docs/assets/images/send.svg b/docs/assets/images/send.svg index f442d9148327..2ac109683395 100644 --- a/docs/assets/images/send.svg +++ b/docs/assets/images/send.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/docs/assets/images/settings-new-dot.svg b/docs/assets/images/settings-new-dot.svg index 13338fc72362..558e7413c295 100644 --- a/docs/assets/images/settings-new-dot.svg +++ b/docs/assets/images/settings-new-dot.svg @@ -1,88 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/settings-old-dot.svg b/docs/assets/images/settings-old-dot.svg index 89302b65c70d..ca5bc04bd0ff 100644 --- a/docs/assets/images/settings-old-dot.svg +++ b/docs/assets/images/settings-old-dot.svg @@ -1,187 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/shield.svg b/docs/assets/images/shield.svg index 252da0321692..3202a532884c 100644 --- a/docs/assets/images/shield.svg +++ b/docs/assets/images/shield.svg @@ -1,16 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/social-facebook.svg b/docs/assets/images/social-facebook.svg index e10f0b21b4dc..0cf2317839f0 100644 --- a/docs/assets/images/social-facebook.svg +++ b/docs/assets/images/social-facebook.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/docs/assets/images/social-instagram.svg b/docs/assets/images/social-instagram.svg index 848a8563403e..d78d50d65aa3 100644 --- a/docs/assets/images/social-instagram.svg +++ b/docs/assets/images/social-instagram.svg @@ -1,17 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/social-linkedin.svg b/docs/assets/images/social-linkedin.svg index d1715494c23a..ef7ed938540f 100644 --- a/docs/assets/images/social-linkedin.svg +++ b/docs/assets/images/social-linkedin.svg @@ -1,8 +1 @@ - - - - - + \ No newline at end of file diff --git a/docs/assets/images/social-podcast.svg b/docs/assets/images/social-podcast.svg index b3db63124d2a..69c5340a389e 100644 --- a/docs/assets/images/social-podcast.svg +++ b/docs/assets/images/social-podcast.svg @@ -1,15 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/social-twitter.svg b/docs/assets/images/social-twitter.svg index 40465f27185c..cadb5fd7474a 100644 --- a/docs/assets/images/social-twitter.svg +++ b/docs/assets/images/social-twitter.svg @@ -1,9 +1 @@ - - - - - + \ No newline at end of file diff --git a/docs/assets/images/social-youtube.svg b/docs/assets/images/social-youtube.svg index 60cfcd9e4147..cec64f2a7766 100644 --- a/docs/assets/images/social-youtube.svg +++ b/docs/assets/images/social-youtube.svg @@ -1,11 +1 @@ - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/users.svg b/docs/assets/images/users.svg index 15f3c0e01de5..ad34f781df52 100644 --- a/docs/assets/images/users.svg +++ b/docs/assets/images/users.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/workflow.svg b/docs/assets/images/workflow.svg index e5eac423cd1d..435e3556a5bf 100644 --- a/docs/assets/images/workflow.svg +++ b/docs/assets/images/workflow.svg @@ -1,117 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/Store.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/Store.png index 35d8a1c3cef2..c848522b0b80 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/Store.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/Store.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iOS@2x.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iOS@2x.png index 2f88e782bfd9..c42e7ac3f925 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iOS@2x.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iOS@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iOS@3x.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iOS@3x.png index cae156c0409f..a1e71e266bee 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iOS@3x.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iOS@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPad.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPad.png index c4f9c3c467d0..ab723d4719b3 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPad.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPad@2x.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPad@2x.png index f36fe1698c60..a87298744bda 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPad@2x.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPad@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPadPro.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPadPro.png index 389e9bf64676..aa2cbc1473ee 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPadPro.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/iPadPro.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@2x 1.png index 48f5d96656bf..cae0b7d91800 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@2x.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@2x.png index 48f5d96656bf..f5d6ad425da1 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@2x.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@3x.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@3x.png index f5ccc2a65c77..e09d8a3d7db6 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@3x.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/notification@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings.png index 6899938f4b2b..114a9a961e36 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@2x 1.png index c401dfbb94d6..f20a51d77e05 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@2x.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@2x.png index c401dfbb94d6..fc73dfab1e1f 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@2x.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@3x.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@3x.png index f43a34acb801..fa9b49aa9b77 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@3x.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/settings@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight.png index 20f25cfb4a76..21021c78a083 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@2x 1.png index fb8e5ca9d8cc..c692788c1884 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@2x.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@2x.png index fb8e5ca9d8cc..c692788c1884 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@2x.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@3x.png b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@3x.png index b9ac89a69f28..2ac3b654fe66 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@3x.png and b/ios/NewExpensify/Images.xcassets/AppIcon.appiconset/spotlight@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_Store.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_Store.png index 2647154f2d96..c5426cffe80f 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_Store.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_Store.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadApp.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadApp.png index 2ac48e7baaeb..1486278b3a88 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadApp.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadApp.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadApp@2x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadApp@2x.png index 8fe39eeb5d9e..b3e0c7a19150 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadApp@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadApp@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadPro@2x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadPro@2x.png index b6e348e6ca32..621562e831f2 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadPro@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPadPro@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@2x 1.png index ae9b47105114..3975d1932fcb 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@2x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@2x.png index ae9b47105114..3975d1932fcb 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@3x 1.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@3x 1.png index e47a76cd08a7..a68d092e932b 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@3x 1.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@3x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@3x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@3x.png index e47a76cd08a7..a68d092e932b 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@3x.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_iPhoneApp@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@2x 1.png index 523e6cfff8ae..03666c6795c6 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@2x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@2x.png index 523e6cfff8ae..03666c6795c6 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@3x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@3x.png index 8f4f398039c2..8a9f7e8f33d0 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@3x.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_notification@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings 1.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings 1.png index 9f18efd8459e..8acce9b73e11 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings 1.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings.png index 9f18efd8459e..4cbeca376a16 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@2x 1.png index edad826a6f40..593f6c0b80e6 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@2x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@2x.png index edad826a6f40..8b8e901b76ea 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@3x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@3x.png index 69007e1228d7..f0fcce2013f0 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@3x.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_settings@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight.png index 70d926b9a29a..cfb6931918fb 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@2x 1.png index d7d5d2d5a6c5..8aeedc372d8a 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@2x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@2x.png index d7d5d2d5a6c5..8aeedc372d8a 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@3x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@3x.png index 9ab486c7d734..de088f2cbb4a 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@3x.png and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/ADHOC_spotlight@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_Store.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_Store.png index 059d94609dca..8c874e35d5c6 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_Store.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_Store.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPad.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPad.png index 6eca3a8f40ac..6faf177e719c 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPad.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPad@2x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPad@2x.png index dc3830a7bcf6..cf1e53c5bbd7 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPad@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPad@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPadPro@2x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPadPro@2x.png index d97d63d7b46f..d346990c457b 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPadPro@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPadPro@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@2x 1.png index dd38161a9b07..384591a8b1cb 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@2x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@2x.png index dd38161a9b07..384591a8b1cb 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@3x 1.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@3x 1.png index 97ec15762932..4babfe38e762 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@3x 1.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@3x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@3x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@3x.png index 97ec15762932..4babfe38e762 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@3x.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_iPhoneApp@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@2x 1.png index 376f4d892acf..28319e37fc83 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@2x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@2x.png index 376f4d892acf..a168b886a55a 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@3x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@3x.png index d432d60317da..9557f767292b 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@3x.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_notification@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings 1.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings 1.png index dd3aeae3ec96..f656aa833eb9 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings 1.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings.png index dd3aeae3ec96..d1a3e4aab777 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@2x 1.png index f0e93e004e40..435c13e5a593 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@2x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@2x.png index f0e93e004e40..ec35ac2b1519 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@3x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@3x.png index 706525192794..95a262ee83dd 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@3x.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_settings@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight.png index bdda62ab3996..3c4d7c922246 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@2x 1.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@2x 1.png index 03c0e26ce666..ca98e2363033 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@2x 1.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@2x 1.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@2x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@2x.png index 03c0e26ce666..ca98e2363033 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@2x.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@3x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@3x.png index 1e20ff590d2d..85a707216902 100644 Binary files a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@3x.png and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/DEV_spotlight@3x.png differ diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index e2667ec43358..759364765b4c 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.90 + 1.3.92 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.90.2 + 1.3.92.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 1d2dca3bbddf..6dd2088565cb 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.90 + 1.3.92 CFBundleSignature ???? CFBundleVersion - 1.3.90.2 + 1.3.92.0 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index cb120bca2b88..97143f53b867 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -753,6 +753,8 @@ PODS: - Firebase/Performance (= 8.8.0) - React-Core - RNFBApp + - RNFlashList (1.6.1): + - React-Core - RNFS (2.20.0): - React-Core - RNGestureHandler (2.12.0): @@ -925,6 +927,7 @@ DEPENDENCIES: - "RNFBApp (from `../node_modules/@react-native-firebase/app`)" - "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)" - "RNFBPerf (from `../node_modules/@react-native-firebase/perf`)" + - "RNFlashList (from `../node_modules/@shopify/flash-list`)" - RNFS (from `../node_modules/react-native-fs`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - "RNGoogleSignin (from `../node_modules/@react-native-google-signin/google-signin`)" @@ -1134,6 +1137,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-firebase/crashlytics" RNFBPerf: :path: "../node_modules/@react-native-firebase/perf" + RNFlashList: + :path: "../node_modules/@shopify/flash-list" RNFS: :path: "../node_modules/react-native-fs" RNGestureHandler: @@ -1273,6 +1278,7 @@ SPEC CHECKSUMS: RNFBApp: 729c0666395b1953198dc4a1ec6deb8fbe1c302e RNFBCrashlytics: 2061ca863e8e2fa1aae9b12477d7dfa8e88ca0f9 RNFBPerf: 389914cda4000fe0d996a752532a591132cbf3f9 + RNFlashList: 236646d48f224a034f35baa0242e1b77db063b1e RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: dec4645026e7401a0899f2846d864403478ff6a5 RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 diff --git a/jest/setup.js b/jest/setup.js index 4def7d1efad5..a54a90678491 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -1,5 +1,6 @@ import 'setimmediate'; import 'react-native-gesture-handler/jestSetup'; +import '@shopify/flash-list/jestSetup'; import * as reanimatedJestUtils from 'react-native-reanimated/src/reanimated2/jestUtils'; import mockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock'; import setupMockImages from './setupMockImages'; diff --git a/package-lock.json b/package-lock.json index f7ce98fdf111..f852bc4ddee4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.90-2", + "version": "1.3.92-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.90-2", + "version": "1.3.92-0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -40,6 +40,7 @@ "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", + "@shopify/flash-list": "^1.6.1", "@types/node": "^18.14.0", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", @@ -50,7 +51,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#bdbdf44825658500ba581d3e86237d7b8996cc2e", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#2adc24c4e889b3a15f199a6b273e343c7d9cff78", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -9133,6 +9134,34 @@ "version": "1.14.1", "license": "0BSD" }, + "node_modules/@shopify/flash-list": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-1.6.1.tgz", + "integrity": "sha512-SlBlpP7+zol6D1SKaf402aS30Qgwdjwb8/Qn2CupYwXnTcu2l8TiXB766vcsIvKTqUO7ELfQnCwCq8NXx55FsQ==", + "dependencies": { + "recyclerlistview": "4.2.0", + "tslib": "2.4.0" + }, + "peerDependencies": { + "@babel/runtime": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/@shopify/flash-list/node_modules/recyclerlistview": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.0.tgz", + "integrity": "sha512-uuBCi0c+ggqHKwrzPX4Z/mJOzsBbjZEAwGGmlwpD/sD7raXixdAbdJ6BTcAmuWG50Cg4ru9p12M94Njwhr/27A==", + "dependencies": { + "lodash.debounce": "4.0.8", + "prop-types": "15.8.1", + "ts-object-utils": "0.0.5" + }, + "peerDependencies": { + "react": ">= 15.2.1", + "react-native": ">= 0.30.0" + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -21052,8 +21081,9 @@ } }, "node_modules/@xmldom/xmldom": { - "version": "0.7.5", - "license": "MIT", + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", + "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", "engines": { "node": ">=10.0.0" } @@ -30189,8 +30219,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#bdbdf44825658500ba581d3e86237d7b8996cc2e", - "integrity": "sha512-/kXD/18YQJY/iWB5MOSN0ixB1mpxUA+NXEYgKjac1tJd+DoY3K6+bDeu++Qfqg26LCNfvjTkjkDGZVdGsJQ7Hw==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#2adc24c4e889b3a15f199a6b273e343c7d9cff78", + "integrity": "sha512-O7XTAfJoCHiFof+X5oFcCgAZAVVJbdIZ+ANA3WBlvabxcPqN0c+dGxIroV8HlRBbTNAkD3CoDVoF61YBUOxCUg==", "license": "MIT", "dependencies": { "classnames": "2.3.1", @@ -50106,6 +50136,11 @@ "node": ">=6.10" } }, + "node_modules/ts-object-utils": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz", + "integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==" + }, "node_modules/ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -59447,6 +59482,27 @@ } } }, + "@shopify/flash-list": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-1.6.1.tgz", + "integrity": "sha512-SlBlpP7+zol6D1SKaf402aS30Qgwdjwb8/Qn2CupYwXnTcu2l8TiXB766vcsIvKTqUO7ELfQnCwCq8NXx55FsQ==", + "requires": { + "recyclerlistview": "4.2.0", + "tslib": "2.4.0" + }, + "dependencies": { + "recyclerlistview": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.0.tgz", + "integrity": "sha512-uuBCi0c+ggqHKwrzPX4Z/mJOzsBbjZEAwGGmlwpD/sD7raXixdAbdJ6BTcAmuWG50Cg4ru9p12M94Njwhr/27A==", + "requires": { + "lodash.debounce": "4.0.8", + "prop-types": "15.8.1", + "ts-object-utils": "0.0.5" + } + } + } + }, "@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -68117,7 +68173,9 @@ } }, "@xmldom/xmldom": { - "version": "0.7.5" + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", + "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==" }, "@xtuc/ieee754": { "version": "1.2.0", @@ -74774,9 +74832,9 @@ } }, "expensify-common": { - "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", + "version": "git+ssh://git@github.com/Expensify/expensify-common.git#2adc24c4e889b3a15f199a6b273e343c7d9cff78", + "integrity": "sha512-O7XTAfJoCHiFof+X5oFcCgAZAVVJbdIZ+ANA3WBlvabxcPqN0c+dGxIroV8HlRBbTNAkD3CoDVoF61YBUOxCUg==", + "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#2adc24c4e889b3a15f199a6b273e343c7d9cff78", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", @@ -88988,6 +89046,11 @@ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", "dev": true }, + "ts-object-utils": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz", + "integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==" + }, "ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", diff --git a/package.json b/package.json index 18886571cefc..a5d189bf943c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.90-2", + "version": "1.3.92-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.", @@ -87,6 +87,7 @@ "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", + "@shopify/flash-list": "^1.6.1", "@types/node": "^18.14.0", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", @@ -97,7 +98,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#bdbdf44825658500ba581d3e86237d7b8996cc2e", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#2adc24c4e889b3a15f199a6b273e343c7d9cff78", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", diff --git a/scripts/shellCheck.sh b/scripts/shellCheck.sh index 14424b4d9b30..d148958900d4 100755 --- a/scripts/shellCheck.sh +++ b/scripts/shellCheck.sh @@ -22,7 +22,13 @@ info ASYNC_PROCESSES=() for SHELL_SCRIPT in $SHELL_SCRIPTS; do - npx shellcheck -e SC1091 "$SHELL_SCRIPT" & + if [[ "$CI" == 'true' ]]; then + # ShellCheck is installed by default on GitHub Actions ubuntu runners + shellcheck -e SC1091 "$SHELL_SCRIPT" & + else + # Otherwise shellcheck is used via npx + npx shellcheck -e SC1091 "$SHELL_SCRIPT" & + fi ASYNC_PROCESSES+=($!) done diff --git a/src/CONST.ts b/src/CONST.ts index 9d912a4df20e..6602df7fa5eb 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -254,7 +254,6 @@ const CONST = { TASKS: 'tasks', THREADS: 'threads', CUSTOM_STATUS: 'customStatus', - NEW_DOT_CATEGORIES: 'newDotCategories', NEW_DOT_TAGS: 'newDotTags', NEW_DOT_SAML: 'newDotSAML', }, diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8a3df3153326..cd8c3f325ab3 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -384,15 +384,15 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download; [ONYXKEYS.COLLECTION.POLICY]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategory; - [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTag; + [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTags; [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; - [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember; + [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportActions; - [ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: string; + [ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: OnyxTypes.ReportActionsDrafts; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions; [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT]: string; [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES]: number; diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index b84e67df634f..28c61076332a 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -74,6 +74,9 @@ const propTypes = { /** A description of the location (usually the address) */ description: PropTypes.string, + /** The name of the location */ + name: PropTypes.string, + /** Data required by the google auto complete plugin to know where to put the markers on the map */ geometry: PropTypes.shape({ /** Data about the location */ @@ -167,9 +170,10 @@ function AddressSearch(props) { // amount of data massaging needs to happen for what the parent expects to get from this function. if (_.size(details)) { props.onPress({ - address: lodashGet(details, 'description', ''), + address: lodashGet(details, 'description'), lat: lodashGet(details, 'geometry.location.lat', 0), lng: lodashGet(details, 'geometry.location.lng', 0), + name: lodashGet(details, 'name'), }); } return; @@ -220,7 +224,7 @@ function AddressSearch(props) { const values = { street: `${streetNumber} ${streetName}`.trim(), - + name: lodashGet(details, 'name', ''), // Autocomplete returns any additional valid address fragments (e.g. Apt #) as subpremise. street2: subpremise, // Make sure country is updated first, since city and state will be reset if the country changes @@ -382,6 +386,16 @@ function AddressSearch(props) { /> } + renderRow={(data) => { + const title = data.isPredefinedPlace ? data.name : data.structured_formatting.main_text; + const subtitle = data.isPredefinedPlace ? data.description : data.structured_formatting.secondary_text; + return ( + + {title && {title}} + {subtitle} + + ); + }} renderHeaderComponent={renderHeaderComponent} onPress={(data, details) => { saveLocationDetails(data, details); diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 1984ee77207a..f8c4122dddb0 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -1,4 +1,4 @@ -import React, {useState, useCallback, useRef, useMemo} from 'react'; +import React, {useState, useCallback, useRef, useMemo, useEffect} from 'react'; import PropTypes from 'prop-types'; import {View, Animated, Keyboard} from 'react-native'; import Str from 'expensify-common/lib/str'; @@ -137,6 +137,10 @@ function AttachmentModal(props) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); + useEffect(() => { + setFile(props.originalFileName ? {name: props.originalFileName} : undefined); + }, [props.originalFileName]); + const onCarouselAttachmentChange = props.onCarouselAttachmentChange; /** diff --git a/src/components/Attachments/AttachmentCarousel/index.js b/src/components/Attachments/AttachmentCarousel/index.js index 00b603cdd7d9..131c57d4c345 100644 --- a/src/components/Attachments/AttachmentCarousel/index.js +++ b/src/components/Attachments/AttachmentCarousel/index.js @@ -78,7 +78,7 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, setDownl * @param {Object} item * @param {number} index */ - const updatePage = useRef( + const updatePage = useCallback( ({viewableItems}) => { Keyboard.dismiss(); @@ -207,7 +207,7 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, setDownl getItemLayout={getItemLayout} keyExtractor={(item) => item.source} viewabilityConfig={viewabilityConfig} - onViewableItemsChanged={updatePage.current} + onViewableItemsChanged={updatePage} /> )} diff --git a/src/components/Button/index.js b/src/components/Button/index.js index dc12a4ded5c2..26d33b1431b8 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -301,6 +301,7 @@ class Button extends Component { this.props.isDisabled && !this.props.danger && !this.props.success ? styles.buttonDisabled : undefined, this.props.shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined, this.props.shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined, + this.props.icon || this.props.shouldShowRightIcon ? styles.alignItemsStretch : undefined, ...this.props.innerStyles, ]} hoverStyle={[ diff --git a/src/components/CardOverlay.js b/src/components/CardOverlay.js deleted file mode 100644 index 5a94b48ec9af..000000000000 --- a/src/components/CardOverlay.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import styles from '../styles/styles'; - -function CardOverlay() { - return ; -} -CardOverlay.displayName = 'CardOverlay'; - -export default CardOverlay; diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index 3e6921718432..2872060afba5 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -19,6 +19,7 @@ import isEnterWhileComposition from '../../libs/KeyboardShortcut/isEnterWhileCom import CONST from '../../CONST'; import withNavigation from '../withNavigation'; import ReportActionComposeFocusManager from '../../libs/ReportActionComposeFocusManager'; +import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; const propTypes = { /** Maximum number of lines in the text input */ @@ -140,6 +141,8 @@ const getNextChars = (str, cursorPos) => { return substr.substring(0, spaceIndex); }; +const supportsPassive = DeviceCapabilities.hasPassiveEventListenerSupport(); + // Enable Markdown parsing. // On web we like to have the Text Input field always focused so the user can easily type a new chat function Composer({ @@ -339,7 +342,6 @@ function Composer({ } textInput.current.scrollTop += event.deltaY; - event.preventDefault(); event.stopPropagation(); }, []); @@ -384,7 +386,7 @@ function Composer({ if (textInput.current) { document.addEventListener('paste', handlePaste); - textInput.current.addEventListener('wheel', handleWheel); + textInput.current.addEventListener('wheel', handleWheel, supportsPassive ? {passive: true} : false); } return () => { @@ -445,6 +447,7 @@ function Composer({ StyleSheet.flatten([style, {outline: 'none'}]), StyleUtils.getComposeTextAreaPadding(numberOfLines, isComposerFullSize), + Browser.isMobileSafari() || Browser.isSafari() ? styles.rtlTextRenderForSafari : {}, ], [style, maxLines, numberOfLines, isComposerFullSize], ); diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index f866de0b885e..98f50b1f682f 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -91,7 +91,8 @@ function DistanceEReceipt({transaction}) { key={key} > {translate(descriptionKey)} - {waypoint.address || ''} + {waypoint.name && {waypoint.name}} + {waypoint.address && {waypoint.address}} ); })} diff --git a/src/components/DistanceRequest/DistanceRequestFooter.js b/src/components/DistanceRequest/DistanceRequestFooter.js index d8214774d2c1..eaa02968c388 100644 --- a/src/components/DistanceRequest/DistanceRequestFooter.js +++ b/src/components/DistanceRequest/DistanceRequestFooter.js @@ -27,6 +27,7 @@ const propTypes = { lat: PropTypes.number, lng: PropTypes.number, address: PropTypes.string, + name: PropTypes.string, }), ), diff --git a/src/components/DistanceRequest/DistanceRequestRenderItem.js b/src/components/DistanceRequest/DistanceRequestRenderItem.js index 4e4eeee881c5..0e54635e82cc 100644 --- a/src/components/DistanceRequest/DistanceRequestRenderItem.js +++ b/src/components/DistanceRequest/DistanceRequestRenderItem.js @@ -14,6 +14,7 @@ const propTypes = { lat: PropTypes.number, lng: PropTypes.number, address: PropTypes.string, + name: PropTypes.string, }), ), @@ -65,10 +66,13 @@ function DistanceRequestRenderItem({waypoints, item, onSecondaryInteraction, get waypointIcon = Expensicons.DotIndicator; } + const waypoint = lodashGet(waypoints, [`waypoint${index}`], {}); + const title = waypoint.name || waypoint.address; + return ( { const emojisAndHeaderRowIndices = getEmojisAndHeaderRowIndices(); @@ -174,6 +175,7 @@ function EmojiPickerMenu(props) { setHeaderIndices(headerRowIndices.current); setHighlightedIndex(-1); updateFirstNonHeaderIndex(emojis.current); + setHighlightFirstEmoji(false); return; } const newFilteredEmojiList = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, preferredLocale, emojis.current.length); @@ -183,6 +185,7 @@ function EmojiPickerMenu(props) { setHeaderIndices([]); setHighlightedIndex(0); updateFirstNonHeaderIndex(newFilteredEmojiList); + setHighlightFirstEmoji(true); }, throttleTime); /** @@ -209,6 +212,7 @@ function EmojiPickerMenu(props) { searchInputRef.current.blur(); setArePointerEventsDisabled(true); setIsUsingKeyboardMovement(true); + setHighlightFirstEmoji(false); // We only want to hightlight the Emoji if none was highlighted already // If we already have a highlighted Emoji, lets just skip the first navigation @@ -434,11 +438,13 @@ function EmojiPickerMenu(props) { const emojiCode = types && types[preferredSkinTone] ? types[preferredSkinTone] : code; const isEmojiFocused = index === highlightedIndex && isUsingKeyboardMovement; + const shouldEmojiBeHighlighted = index === highlightedIndex && highlightFirstEmoji; return ( onEmojiSelected(emoji, item)} onHoverIn={() => { + setHighlightFirstEmoji(false); if (!isUsingKeyboardMovement) { return; } @@ -452,10 +458,11 @@ function EmojiPickerMenu(props) { setHighlightedIndex((prevState) => (prevState === index ? -1 : prevState)) } isFocused={isEmojiFocused} + isHighlighted={shouldEmojiBeHighlighted} /> ); }, - [isUsingKeyboardMovement, highlightedIndex, onEmojiSelected, preferredSkinTone, translate], + [isUsingKeyboardMovement, highlightedIndex, onEmojiSelected, preferredSkinTone, translate, highlightFirstEmoji], ); const isFiltered = emojis.current.length !== filteredEmojis.length; diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index c5ca5463aec4..1b3e457f7525 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -29,6 +29,9 @@ const propTypes = { /** Whether this menu item is currently focused or not */ isFocused: PropTypes.bool, + + /** Whether the menu item should be highlighted or not */ + isHighlighted: PropTypes.bool, }; class EmojiPickerMenuItem extends PureComponent { @@ -56,6 +59,7 @@ class EmojiPickerMenuItem extends PureComponent { if (!this.props.isFocused) { return; } + this.focusAndScroll(); } @@ -91,7 +95,7 @@ class EmojiPickerMenuItem extends PureComponent { ref={(ref) => (this.ref = ref)} style={({pressed}) => [ this.props.isFocused ? styles.emojiItemKeyboardHighlighted : {}, - this.state.isHovered ? styles.emojiItemHighlighted : {}, + this.state.isHovered || this.props.isHighlighted ? styles.emojiItemHighlighted : {}, Browser.isMobile() && StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), styles.emojiItem, ]} @@ -107,6 +111,7 @@ class EmojiPickerMenuItem extends PureComponent { EmojiPickerMenuItem.propTypes = propTypes; EmojiPickerMenuItem.defaultProps = { isFocused: false, + isHighlighted: false, onHoverIn: () => {}, onHoverOut: () => {}, onFocus: () => {}, @@ -115,4 +120,7 @@ EmojiPickerMenuItem.defaultProps = { // Significantly speeds up re-renders of the EmojiPickerMenu's FlatList // by only re-rendering at most two EmojiPickerMenuItems that are highlighted/un-highlighted per user action. -export default React.memo(EmojiPickerMenuItem, (prevProps, nextProps) => prevProps.isFocused === nextProps.isFocused && prevProps.emoji === nextProps.emoji); +export default React.memo( + EmojiPickerMenuItem, + (prevProps, nextProps) => prevProps.isFocused === nextProps.isFocused && prevProps.isHighlighted === nextProps.isHighlighted && prevProps.emoji === nextProps.emoji, +); diff --git a/src/components/ExpensifyCashLogo.js b/src/components/ExpensifyCashLogo.js deleted file mode 100644 index e9f2fe244eca..000000000000 --- a/src/components/ExpensifyCashLogo.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ProductionLogo from '../../assets/images/new-expensify.svg'; -import DevLogo from '../../assets/images/new-expensify-dev.svg'; -import StagingLogo from '../../assets/images/new-expensify-stg.svg'; -import AdhocLogo from '../../assets/images/new-expensify-adhoc.svg'; -import CONST from '../CONST'; -import useEnvironment from '../hooks/useEnvironment'; - -const propTypes = { - /** Width of logo */ - width: PropTypes.number.isRequired, - - /** Height of logo */ - height: PropTypes.number.isRequired, -}; - -const logoComponents = { - [CONST.ENVIRONMENT.DEV]: DevLogo, - [CONST.ENVIRONMENT.STAGING]: StagingLogo, - [CONST.ENVIRONMENT.PRODUCTION]: ProductionLogo, - [CONST.ENVIRONMENT.ADHOC]: AdhocLogo, -}; - -function ExpensifyCashLogo(props) { - const {environment} = useEnvironment(); - - // PascalCase is required for React components, so capitalize the const here - const LogoComponent = logoComponents[environment]; - return ( - - ); -} - -ExpensifyCashLogo.displayName = 'ExpensifyCashLogo'; -ExpensifyCashLogo.propTypes = propTypes; -export default ExpensifyCashLogo; diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index add58dbef18c..ce402976d097 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -229,13 +229,15 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC .first() .value() || ''; + const value = !_.isUndefined(inputValues[`${inputID}ToDisplay`]) ? inputValues[`${inputID}ToDisplay`] : inputValues[inputID]; + return { ...propsToParse, ref: newRef, inputID, key: propsToParse.key || inputID, errorText: errors[inputID] || fieldErrorMessage, - value: inputValues[inputID], + value, // As the text input is controlled, we never set the defaultValue prop // as this is already happening by the value prop. defaultValue: undefined, @@ -275,13 +277,19 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC propsToParse.onBlur(event); } }, - onInputChange: (value, key) => { + onInputChange: (inputValue, key) => { const inputKey = key || inputID; setInputValues((prevState) => { - const newState = { - ...prevState, - [inputKey]: value, - }; + const newState = _.isFunction(propsToParse.valueParser) + ? { + ...prevState, + [inputKey]: propsToParse.valueParser(inputValue), + [`${inputKey}ToDisplay`]: inputValue, + } + : { + ...prevState, + [inputKey]: inputValue, + }; if (shouldValidateOnChange) { onValidate(newState); @@ -290,11 +298,11 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC }); if (propsToParse.shouldSaveDraft) { - FormActions.setDraftValues(propsToParse.formID, {[inputKey]: value}); + FormActions.setDraftValues(propsToParse.formID, {[inputKey]: inputValue}); } if (_.isFunction(propsToParse.onValueChange)) { - propsToParse.onValueChange(value, inputKey); + propsToParse.onValueChange(inputValue, inputKey); } }, }; diff --git a/src/components/Form/InputWrapper.js b/src/components/Form/InputWrapper.js index 8a87bc2f5a5a..74a741239a3f 100644 --- a/src/components/Form/InputWrapper.js +++ b/src/components/Form/InputWrapper.js @@ -7,11 +7,13 @@ const propTypes = { inputID: PropTypes.string.isRequired, valueType: PropTypes.string, forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), + valueParser: PropTypes.func, }; const defaultProps = { forwardedRef: undefined, valueType: 'string', + valueParser: undefined, }; function InputWrapper(props) { diff --git a/src/components/FormScrollView.js b/src/components/FormScrollView.js deleted file mode 100644 index b52c8d00c51a..000000000000 --- a/src/components/FormScrollView.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {ScrollView} from 'react-native'; -import styles from '../styles/styles'; - -const propTypes = { - /** Form elements */ - children: PropTypes.node.isRequired, -}; - -const FormScrollViewWithRef = React.forwardRef((props, ref) => ( - - {props.children} - -)); - -FormScrollViewWithRef.displayName = 'FormScrollViewWithRef'; -FormScrollViewWithRef.propTypes = propTypes; -export default FormScrollViewWithRef; diff --git a/src/components/FormScrollView.tsx b/src/components/FormScrollView.tsx new file mode 100644 index 000000000000..b6c1062b5e4e --- /dev/null +++ b/src/components/FormScrollView.tsx @@ -0,0 +1,27 @@ +import React, {ForwardedRef} from 'react'; +import {ScrollView, ScrollViewProps} from 'react-native'; +import styles from '../styles/styles'; + +type FormScrollViewProps = ScrollViewProps & { + /** Form elements */ + children: React.ReactNode; +}; + +function FormScrollView({children, ...rest}: FormScrollViewProps, ref: ForwardedRef) { + return ( + + {children} + + ); +} + +FormScrollView.displayName = 'FormScrollView'; + +export default React.forwardRef(FormScrollView); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js index 812f4e951f74..6c098e8bea05 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js @@ -6,6 +6,9 @@ import lodashGet from 'lodash/get'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import Text from '../../Text'; +import styles from '../../../styles/styles'; +import * as ReportUtils from '../../../libs/ReportUtils'; +import {ShowContextMenuContext, showContextMenuForReport} from '../../ShowContextMenuContext'; import UserDetailsTooltip from '../../UserDetailsTooltip'; import htmlRendererPropTypes from './htmlRendererPropTypes'; import withCurrentUserPersonalDetails from '../../withCurrentUserPersonalDetails'; @@ -13,10 +16,10 @@ import personalDetailsPropType from '../../../pages/personalDetailsPropType'; import * as StyleUtils from '../../../styles/StyleUtils'; import * as PersonalDetailsUtils from '../../../libs/PersonalDetailsUtils'; import compose from '../../../libs/compose'; -import TextLink from '../../TextLink'; import ONYXKEYS from '../../../ONYXKEYS'; import useLocalize from '../../../hooks/useLocalize'; import CONST from '../../../CONST'; +import * as LocalePhoneNumber from '../../../libs/LocalePhoneNumber'; const propTypes = { ...htmlRendererPropTypes, @@ -40,7 +43,7 @@ function MentionUserRenderer(props) { if (!_.isEmpty(htmlAttribAccountID)) { const user = lodashGet(props.personalDetails, htmlAttribAccountID); accountID = parseInt(htmlAttribAccountID, 10); - displayNameOrLogin = lodashGet(user, 'login', '') || lodashGet(user, 'displayName', '') || translate('common.hidden'); + displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(lodashGet(user, 'login', '')) || lodashGet(user, 'displayName', '') || translate('common.hidden'); navigationRoute = ROUTES.PROFILE.getRoute(htmlAttribAccountID); } else if (!_.isEmpty(props.tnode.data)) { // We need to remove the LTR unicode and leading @ from data as it is not part of the login @@ -56,26 +59,38 @@ function MentionUserRenderer(props) { const isOurMention = accountID === props.currentUserPersonalDetails.accountID; return ( - - - Navigation.navigate(navigationRoute)} - // Add testID so it is NOT selected as an anchor tag by SelectionScraper - testID="span" + + {({anchor, report, action, checkIfContextMenuActive}) => ( + showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onPress={(event) => { + event.preventDefault(); + Navigation.navigate(navigationRoute); + }} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.LINK} + accessibilityLabel={`/${navigationRoute}`} > - {!_.isEmpty(htmlAttribAccountID) ? `@${displayNameOrLogin}` : } - - - + + + {!_.isEmpty(htmlAttribAccountID) ? `@${displayNameOrLogin}` : } + + + + )} + ); } diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js index 782ad82f643c..ca86b957e55b 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js @@ -6,6 +6,8 @@ import * as DeviceCapabilities from '../../../../libs/DeviceCapabilities'; import htmlRendererPropTypes from '../htmlRendererPropTypes'; import BasePreRenderer from './BasePreRenderer'; +const supportsPassive = DeviceCapabilities.hasPassiveEventListenerSupport(); + const isScrollingVertically = (event) => // Mark as vertical scrolling only when absolute value of deltaY is more than the double of absolute // value of deltaX, so user can use trackpad scroll on the code block horizontally at a wide angle. @@ -32,7 +34,6 @@ function PreRenderer(props) { const horizontalOverflow = node.scrollWidth > node.offsetWidth; if (event.currentTarget === node && horizontalOverflow && !debouncedIsScrollingVertically(event)) { node.scrollLeft += event.deltaX; - event.preventDefault(); event.stopPropagation(); } }, []); @@ -42,7 +43,7 @@ function PreRenderer(props) { if (!eventListenerRefValue) { return; } - eventListenerRefValue.getScrollableNode().addEventListener('wheel', scrollNode); + eventListenerRefValue.getScrollableNode().addEventListener('wheel', scrollNode, supportsPassive ? {passive: true} : false); return () => { if (!eventListenerRefValue.getScrollableNode()) { diff --git a/src/components/HeaderWithBackButton/headerWithBackButtonPropTypes.js b/src/components/HeaderWithBackButton/headerWithBackButtonPropTypes.js index d2cdc5b29898..8db9fbb3a321 100644 --- a/src/components/HeaderWithBackButton/headerWithBackButtonPropTypes.js +++ b/src/components/HeaderWithBackButton/headerWithBackButtonPropTypes.js @@ -31,12 +31,18 @@ const propTypes = { /** Whether we should show a get assistance (question mark) button */ shouldShowGetAssistanceButton: PropTypes.bool, + /** Whether we should disable the get assistance button */ + shouldDisableGetAssistanceButton: PropTypes.bool, + /** Whether we should show a pin button */ shouldShowPinButton: PropTypes.bool, /** Whether we should show a more options (threedots) button */ shouldShowThreeDotsButton: PropTypes.bool, + /** Whether we should disable threedots button */ + shouldDisableThreeDotsButton: PropTypes.bool, + /** List of menu items for more(three dots) menu */ threeDotsMenuItems: ThreeDotsMenuItemPropTypes, @@ -84,6 +90,9 @@ const propTypes = { /** Children to wrap in Header */ children: PropTypes.node, + + /** Single execution function to prevent concurrent navigation actions */ + singleExecution: PropTypes.func, }; export default propTypes; diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index 6a02ce02237d..fc1f1a533ab7 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -18,6 +18,7 @@ import headerWithBackButtonPropTypes from './headerWithBackButtonPropTypes'; import useThrottledButtonState from '../../hooks/useThrottledButtonState'; import useLocalize from '../../hooks/useLocalize'; import useKeyboardState from '../../hooks/useKeyboardState'; +import useWaitForNavigation from '../../hooks/useWaitForNavigation'; function HeaderWithBackButton({ iconFill = undefined, @@ -35,8 +36,10 @@ function HeaderWithBackButton({ shouldShowCloseButton = false, shouldShowDownloadButton = false, shouldShowGetAssistanceButton = false, + shouldDisableGetAssistanceButton = false, shouldShowPinButton = false, shouldShowThreeDotsButton = false, + shouldDisableThreeDotsButton = false, stepCounter = null, subtitle = '', title = '', @@ -49,10 +52,12 @@ function HeaderWithBackButton({ shouldEnableDetailPageNavigation = false, children = null, shouldOverlay = false, + singleExecution = (func) => func, }) { const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState(); const {translate} = useLocalize(); const {isKeyboardShown} = useKeyboardState(); + const waitForNavigate = useWaitForNavigation(); return ( Navigation.navigate(ROUTES.GET_ASSISTANCE.getRoute(guidesCallTaskID))} + disabled={shouldDisableGetAssistanceButton} + onPress={singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.GET_ASSISTANCE.getRoute(guidesCallTaskID))))} style={[styles.touchableButtonImage]} accessibilityRole="button" accessibilityLabel={translate('getAssistancePage.questionMarkButtonTooltip')} @@ -141,6 +147,7 @@ function HeaderWithBackButton({ {shouldShowPinButton && } {shouldShowThreeDotsButton && ( - {props.message} + {message} ); } -InlineSystemMessage.propTypes = propTypes; -InlineSystemMessage.defaultProps = defaultProps; InlineSystemMessage.displayName = 'InlineSystemMessage'; + export default InlineSystemMessage; diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.js b/src/components/InvertedFlatList/BaseInvertedFlatList.js index baee08eae4cd..044143774dd6 100644 --- a/src/components/InvertedFlatList/BaseInvertedFlatList.js +++ b/src/components/InvertedFlatList/BaseInvertedFlatList.js @@ -4,6 +4,7 @@ import _ from 'underscore'; import PropTypes from 'prop-types'; import * as CollectionUtils from '../../libs/CollectionUtils'; import FlatList from '../FlatList'; +import variables from '../../styles/variables'; const propTypes = { /** Same as FlatList can be any array of anything */ @@ -135,6 +136,7 @@ function BaseInvertedFlatList(props) { windowSize={15} maintainVisibleContentPosition={{ minIndexForVisible: 0, + autoscrollToTopThreshold: variables.listItemHeightNormal, }} inverted /> diff --git a/src/components/KeyboardDismissingFlatList/index.js b/src/components/KeyboardDismissingFlatList/index.js deleted file mode 100644 index 0ca8504d96ab..000000000000 --- a/src/components/KeyboardDismissingFlatList/index.js +++ /dev/null @@ -1,51 +0,0 @@ -import React, {useRef, useEffect, useCallback} from 'react'; -import {FlatList, Keyboard} from 'react-native'; -import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; - -function KeyboardDismissingFlatList(props) { - const isScreenTouched = useRef(false); - - useEffect(() => { - if (!DeviceCapabilities.canUseTouchScreen()) { - return; - } - - const touchStart = () => { - isScreenTouched.current = true; - }; - - const touchEnd = () => { - isScreenTouched.current = false; - }; - - // We're setting `isScreenTouched` in this listener only for web platforms with touchscreen (mWeb) where - // we want to dismiss the keyboard only when the list is scrolled by the user and not when it's scrolled programmatically. - document.addEventListener('touchstart', touchStart); - document.addEventListener('touchend', touchEnd); - - return () => { - document.removeEventListener('touchstart', touchStart); - document.removeEventListener('touchend', touchEnd); - }; - }, []); - - const onScroll = useCallback(() => { - // Only dismiss the keyboard whenever the user scrolls the screen - if (!isScreenTouched.current) { - return; - } - Keyboard.dismiss(); - }, []); - - return ( - - ); -} - -KeyboardDismissingFlatList.displayName = 'KeyboardDismissingFlatList'; - -export default KeyboardDismissingFlatList; diff --git a/src/components/KeyboardDismissingFlatList/index.native.js b/src/components/KeyboardDismissingFlatList/index.native.js deleted file mode 100644 index 97297528ac77..000000000000 --- a/src/components/KeyboardDismissingFlatList/index.native.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import {FlatList, Keyboard} from 'react-native'; - -function KeyboardDismissingFlatList(props) { - return ( - Keyboard.dismiss()} - /> - ); -} - -KeyboardDismissingFlatList.displayName = 'KeyboardDismissingFlatList'; - -export default KeyboardDismissingFlatList; diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js index 90f863ba2bc7..19253ce8bd94 100644 --- a/src/components/MenuItemList.js +++ b/src/components/MenuItemList.js @@ -1,6 +1,7 @@ import React from 'react'; import _ from 'underscore'; import PropTypes from 'prop-types'; +import useSingleExecution from '../hooks/useSingleExecution'; import MenuItem from './MenuItem'; import menuItemPropTypes from './menuItemPropTypes'; import * as ReportActionContextMenu from '../pages/home/report/ContextMenu/ReportActionContextMenu'; @@ -9,13 +10,18 @@ import {CONTEXT_MENU_TYPES} from '../pages/home/report/ContextMenu/ContextMenuAc const propTypes = { /** An array of props that are pass to individual MenuItem components */ menuItems: PropTypes.arrayOf(PropTypes.shape(menuItemPropTypes)), + + /** Whether or not to use the single execution hook */ + shouldUseSingleExecution: PropTypes.bool, }; const defaultProps = { menuItems: [], + shouldUseSingleExecution: false, }; function MenuItemList(props) { let popoverAnchor; + const {isExecuting, singleExecution} = useSingleExecution(); /** * Handle the secondary interaction for a menu item. @@ -41,6 +47,8 @@ function MenuItemList(props) { shouldBlockSelection={Boolean(menuItemProps.link)} // eslint-disable-next-line react/jsx-props-no-spreading {...menuItemProps} + disabled={menuItemProps.disabled || isExecuting} + onPress={props.shouldUseSingleExecution ? singleExecution(menuItemProps.onPress) : menuItemProps.onPress} /> ))} diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 0b266351a60c..fd3a89d14c86 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -225,8 +225,7 @@ function MoneyRequestConfirmationList(props) { const shouldCalculateDistanceAmount = props.isDistanceRequest && props.iouAmount === 0; // A flag for showing the categories field - const shouldShowCategories = - props.isPolicyExpenseChat && Permissions.canUseCategories(props.betas) && (props.iouCategory || OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories))); + const shouldShowCategories = props.isPolicyExpenseChat && (props.iouCategory || OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories))); // A flag and a toggler for showing the rest of the form fields const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 43500c731728..88cd91bc10ab 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -166,6 +166,7 @@ function MoneyRequestPreview(props) { const isDistanceRequest = TransactionUtils.isDistanceRequest(props.transaction); const isExpensifyCardTransaction = TransactionUtils.isExpensifyCardTransaction(props.transaction); const isSettled = ReportUtils.isSettled(props.iouReport.reportID); + const isDeleted = lodashGet(props.action, 'pendingAction', null) === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; // Show the merchant for IOUs and expenses only if they are custom or not related to scanning smartscan const shouldShowMerchant = @@ -232,6 +233,16 @@ function MoneyRequestPreview(props) { return CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency); }; + const getDisplayDeleteAmountText = () => { + const {amount, currency} = ReportUtils.getTransactionDetails(props.action.originalMessage); + + if (isDistanceRequest) { + return CurrencyUtils.convertToDisplayString(TransactionUtils.getAmount(props.action.originalMessage), currency); + } + + return CurrencyUtils.convertToDisplayString(amount, currency); + }; + const childContainer = ( - {getDisplayAmountText()} + {isDeleted ? getDisplayDeleteAmountText() : getDisplayAmountText()} {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 19f4a5b8e103..57ccbaeb7ddf 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -118,7 +118,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should const policyTagsList = lodashGet(policyTag, 'tags', {}); // Flags for showing categories and tags - const shouldShowCategory = isPolicyExpenseChat && Permissions.canUseCategories(betas) && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); + const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); const shouldShowTag = isPolicyExpenseChat && Permissions.canUseTags(betas) && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagsList))); const shouldShowBillable = isPolicyExpenseChat && Permissions.canUseTags(betas) && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index ec9bf7a090ab..197b6b77acae 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -1,49 +1,23 @@ -import React, {useState} from 'react'; -import _ from 'underscore'; +import React from 'react'; import CONST from '../../CONST'; import TextInput from '../TextInput'; import useLocalize from '../../hooks/useLocalize'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; +import InputWrapper from '../Form/InputWrapper'; +import getOperatingSystem from '../../libs/getOperatingSystem'; import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils'; -function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus}) { +function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, onBlur, shouldDelayFocus, inputID}) { const {translate} = useLocalize(); - const [selection, setSelection] = useState(); + const keyboardType = getOperatingSystem() === CONST.OS.IOS ? CONST.KEYBOARD_TYPE.ASCII_CAPABLE : CONST.KEYBOARD_TYPE.VISIBLE_PASSWORD; - /** - * Calls the onChangeText callback with a modified room name - * @param {Event} event - */ - const setModifiedRoomName = (event) => { - const roomName = event.nativeEvent.text; - const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName); - onChangeText(modifiedRoomName); - - // if custom component has onInputChange, use it to trigger changes (Form input) - if (_.isFunction(onInputChange)) { - onInputChange(modifiedRoomName); - } - - // Prevent cursor jump behaviour: - // Check if newRoomNameWithHash is the same as modifiedRoomName - // If it is then the room name is valid (does not contain unallowed characters); no action required - // If not then the room name contains unvalid characters and we must adjust the cursor position manually - // Read more: https://github.com/Expensify/App/issues/12741 - const oldRoomNameWithHash = value || ''; - const newRoomNameWithHash = `${CONST.POLICY.ROOM_PREFIX}${roomName}`; - if (modifiedRoomName !== newRoomNameWithHash) { - const offset = modifiedRoomName.length - oldRoomNameWithHash.length; - const newSelection = { - start: selection.start + offset, - end: selection.end + offset, - }; - setSelection(newSelection); - } - }; + const valueParser = (roomName) => RoomNameInputUtils.modifyRoomName(roomName); return ( - setSelection(event.nativeEvent.selection)} errorText={errorText} + valueParser={valueParser} autoCapitalize="none" onBlur={() => isFocused && onBlur()} shouldDelayFocus={shouldDelayFocus} @@ -63,6 +34,7 @@ function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, maxLength={CONST.REPORT.MAX_ROOM_NAME_LENGTH} spellCheck={false} shouldInterceptSwipe + keyboardType={keyboardType} // this is a bit hacky solution to a RN issue https://github.com/facebook/react-native/issues/27449 /> ); } diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js deleted file mode 100644 index 9e83a673982c..000000000000 --- a/src/components/RoomNameInput/index.native.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import _ from 'underscore'; -import CONST from '../../CONST'; -import useLocalize from '../../hooks/useLocalize'; -import TextInput from '../TextInput'; -import * as roomNameInputPropTypes from './roomNameInputPropTypes'; -import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils'; -import getOperatingSystem from '../../libs/getOperatingSystem'; - -function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus}) { - const {translate} = useLocalize(); - - /** - * Calls the onChangeText callback with a modified room name - * @param {Event} event - */ - const setModifiedRoomName = (event) => { - const roomName = event.nativeEvent.text; - const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName); - onChangeText(modifiedRoomName); - - // if custom component has onInputChange, use it to trigger changes (Form input) - if (_.isFunction(onInputChange)) { - onInputChange(modifiedRoomName); - } - }; - - const keyboardType = getOperatingSystem() === CONST.OS.IOS ? CONST.KEYBOARD_TYPE.ASCII_CAPABLE : CONST.KEYBOARD_TYPE.VISIBLE_PASSWORD; - - return ( - isFocused && onBlur()} - autoFocus={isFocused && autoFocus} - autoCapitalize="none" - shouldDelayFocus={shouldDelayFocus} - /> - ); -} - -RoomNameInput.propTypes = roomNameInputPropTypes.propTypes; -RoomNameInput.defaultProps = roomNameInputPropTypes.defaultProps; -RoomNameInput.displayName = 'RoomNameInput'; - -const RoomNameInputWithRef = React.forwardRef((props, ref) => ( - -)); - -RoomNameInputWithRef.displayName = 'RoomNameInputWithRef'; - -export default RoomNameInputWithRef; diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index 7f8292f0123e..3d1ad18d27b3 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import refPropTypes from '../refPropTypes'; const propTypes = { /** Callback to execute when the text input is modified correctly */ @@ -14,10 +15,10 @@ const propTypes = { errorText: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), /** A ref forwarded to the TextInput */ - forwardedRef: PropTypes.func, + forwardedRef: refPropTypes, /** The ID used to uniquely identify the input in a Form */ - inputID: PropTypes.string, + inputID: PropTypes.string.isRequired, /** Callback that is called when the text input is blurred */ onBlur: PropTypes.func, @@ -39,7 +40,6 @@ const defaultProps = { errorText: '', forwardedRef: () => {}, - inputID: undefined, onBlur: () => {}, autoFocus: false, shouldDelayFocus: false, diff --git a/src/components/ThreeDotsMenu/index.js b/src/components/ThreeDotsMenu/index.js index 3aac98fa1275..ab7ca57ed721 100644 --- a/src/components/ThreeDotsMenu/index.js +++ b/src/components/ThreeDotsMenu/index.js @@ -50,12 +50,16 @@ const propTypes = { /** Whether the popover menu should overlay the current view */ shouldOverlay: PropTypes.bool, + /** Whether the menu is disabled */ + disabled: PropTypes.bool, + /** Should we announce the Modal visibility changes? */ shouldSetModalVisibility: PropTypes.bool, }; const defaultProps = { iconTooltip: 'common.more', + disabled: false, iconFill: undefined, iconStyles: [], icon: Expensicons.ThreeDots, @@ -68,7 +72,7 @@ const defaultProps = { shouldSetModalVisibility: true, }; -function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, shouldOverlay, shouldSetModalVisibility}) { +function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, shouldOverlay, shouldSetModalVisibility, disabled}) { const [isPopupMenuVisible, setPopupMenuVisible] = useState(false); const buttonRef = useRef(null); const {translate} = useLocalize(); @@ -96,6 +100,7 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me onIconPress(); } }} + disabled={disabled} onMouseDown={(e) => { /* Keep the focus state on mWeb like we did on the native apps. */ if (!Browser.isMobile()) { diff --git a/src/components/ValuePicker/index.js b/src/components/ValuePicker/index.js index ce248baf707e..09573c1fdeca 100644 --- a/src/components/ValuePicker/index.js +++ b/src/components/ValuePicker/index.js @@ -7,6 +7,8 @@ import MenuItemWithTopDescription from '../MenuItemWithTopDescription'; import ValueSelectorModal from './ValueSelectorModal'; import FormHelpMessage from '../FormHelpMessage'; import refPropTypes from '../refPropTypes'; +import * as StyleUtils from '../../styles/StyleUtils'; +import variables from '../../styles/variables'; const propTypes = { /** Form Error description */ @@ -59,7 +61,7 @@ function ValuePicker({value, label, items, placeholder, errorText, onInputChange hidePickerModal(); }; - const descStyle = value.length === 0 ? styles.textNormal : null; + const descStyle = value.length === 0 ? StyleUtils.getFontSizeStyle(variables.fontSizeLabel) : null; const selectedItem = _.find(items, {value}); const selectedLabel = selectedItem ? selectedItem.label : ''; diff --git a/src/components/transactionPropTypes.js b/src/components/transactionPropTypes.js index bc0a10025ba8..e3e06bb00c01 100644 --- a/src/components/transactionPropTypes.js +++ b/src/components/transactionPropTypes.js @@ -45,6 +45,9 @@ export default PropTypes.shape({ /** The address of the waypoint */ address: PropTypes.string, + + /** The name of the waypoint */ + name: PropTypes.string, }), }), diff --git a/src/components/withAnimatedRef.js b/src/components/withAnimatedRef.js deleted file mode 100644 index bb04c6a599d7..000000000000 --- a/src/components/withAnimatedRef.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import {useAnimatedRef} from 'react-native-reanimated'; -import getComponentDisplayName from '../libs/getComponentDisplayName'; -import refPropTypes from './refPropTypes'; - -export default function withAnimatedRef(WrappedComponent) { - function WithAnimatedRef(props) { - const animatedRef = useAnimatedRef(); - return ( - - ); - } - WithAnimatedRef.displayName = `withAnimatedRef(${getComponentDisplayName(WrappedComponent)})`; - WithAnimatedRef.propTypes = { - forwardedRef: refPropTypes, - }; - WithAnimatedRef.defaultProps = { - forwardedRef: undefined, - }; - - const WithAnimatedRefWithRef = React.forwardRef((props, ref) => ( - - )); - - WithAnimatedRefWithRef.displayName = 'WithAnimatedRefWithRef'; - - return WithAnimatedRefWithRef; -} diff --git a/src/components/withEnvironment.js b/src/components/withEnvironment.tsx similarity index 50% rename from src/components/withEnvironment.js rename to src/components/withEnvironment.tsx index 3aa9b86e82c8..0f065eac68fe 100644 --- a/src/components/withEnvironment.js +++ b/src/components/withEnvironment.tsx @@ -1,21 +1,28 @@ -import React, {createContext, useState, useEffect, forwardRef, useContext, useMemo} from 'react'; -import PropTypes from 'prop-types'; +import React, {ComponentType, RefAttributes, ReactNode, ForwardedRef, ReactElement, createContext, useState, useEffect, forwardRef, useContext, useMemo} from 'react'; +import {ValueOf} from 'type-fest'; import * as Environment from '../libs/Environment/Environment'; import CONST from '../CONST'; import getComponentDisplayName from '../libs/getComponentDisplayName'; -const EnvironmentContext = createContext(null); +type EnvironmentProviderProps = { + /** Actual content wrapped by this component */ + children: ReactNode; +}; + +type EnvironmentValue = ValueOf; -const environmentPropTypes = { +type EnvironmentContextValue = { /** The string value representing the current environment */ - environment: PropTypes.string.isRequired, + environment: EnvironmentValue; /** The string value representing the URL of the current environment */ - environmentURL: PropTypes.string.isRequired, + environmentURL: string; }; -function EnvironmentProvider({children}) { - const [environment, setEnvironment] = useState(CONST.ENVIRONMENT.PRODUCTION); +const EnvironmentContext = createContext(null); + +function EnvironmentProvider({children}: EnvironmentProviderProps): ReactElement { + const [environment, setEnvironment] = useState(CONST.ENVIRONMENT.PRODUCTION); const [environmentURL, setEnvironmentURL] = useState(CONST.NEW_EXPENSIFY_URL); useEffect(() => { @@ -24,7 +31,7 @@ function EnvironmentProvider({children}) { }, []); const contextValue = useMemo( - () => ({ + (): EnvironmentContextValue => ({ environment, environmentURL, }), @@ -35,28 +42,27 @@ function EnvironmentProvider({children}) { } EnvironmentProvider.displayName = 'EnvironmentProvider'; -EnvironmentProvider.propTypes = { - /** Actual content wrapped by this component */ - children: PropTypes.node.isRequired, -}; -export default function withEnvironment(WrappedComponent) { - const WithEnvironment = forwardRef((props, ref) => { - const {environment, environmentURL} = useContext(EnvironmentContext); +export default function withEnvironment( + WrappedComponent: ComponentType>, +): (props: Omit & React.RefAttributes) => ReactElement | null { + function WithEnvironment(props: Omit, ref: ForwardedRef): ReactElement { + const {environment, environmentURL} = useContext(EnvironmentContext) ?? {}; return ( ); - }); + } WithEnvironment.displayName = `withEnvironment(${getComponentDisplayName(WrappedComponent)})`; - return WithEnvironment; + return forwardRef(WithEnvironment); } -export {EnvironmentContext, environmentPropTypes, EnvironmentProvider}; +export {EnvironmentContext, EnvironmentProvider}; +export type {EnvironmentContextValue}; diff --git a/src/hooks/useEnvironment.js b/src/hooks/useEnvironment.ts similarity index 59% rename from src/hooks/useEnvironment.js rename to src/hooks/useEnvironment.ts index e29e60a563b2..0b1601ee972a 100644 --- a/src/hooks/useEnvironment.js +++ b/src/hooks/useEnvironment.ts @@ -1,9 +1,15 @@ import {useContext} from 'react'; import CONST from '../CONST'; import {EnvironmentContext} from '../components/withEnvironment'; +import type {EnvironmentContextValue} from '../components/withEnvironment'; -export default function useEnvironment() { - const {environment, environmentURL} = useContext(EnvironmentContext); +type UseEnvironment = Partial & { + isProduction: boolean; + isDevelopment: boolean; +}; + +export default function useEnvironment(): UseEnvironment { + const {environment, environmentURL} = useContext(EnvironmentContext) ?? {}; return { environment, environmentURL, diff --git a/src/hooks/useInitialValue.ts b/src/hooks/useInitialValue.ts new file mode 100644 index 000000000000..e42ea044e27a --- /dev/null +++ b/src/hooks/useInitialValue.ts @@ -0,0 +1,9 @@ +import {useState} from 'react'; + +// In some places we set initial value on first render, but we don't want to re-run the function +// This hook will memoize the initial value and return that without setter, so it's never changed +// https://github.com/Expensify/App/pull/29643#issuecomment-1765894078 +export default function useInitialValue(initialStateFunc: () => T) { + const [initialValue] = useState(initialStateFunc); + return initialValue; +} diff --git a/src/hooks/useReportScrollManager/index.native.js b/src/hooks/useReportScrollManager/index.native.ts similarity index 68% rename from src/hooks/useReportScrollManager/index.native.js rename to src/hooks/useReportScrollManager/index.native.ts index d44a40222ca5..ed9b7968636c 100644 --- a/src/hooks/useReportScrollManager/index.native.js +++ b/src/hooks/useReportScrollManager/index.native.ts @@ -1,27 +1,26 @@ import {useContext, useCallback} from 'react'; import {ActionListContext} from '../../pages/home/ReportScreenContext'; +import ReportScrollManagerData from './types'; -function useReportScrollManager() { +function useReportScrollManager(): ReportScrollManagerData { const flatListRef = useContext(ActionListContext); /** * Scroll to the provided index. - * - * @param {Object} index */ - const scrollToIndex = (index) => { - if (!flatListRef.current) { + const scrollToIndex = (index: number) => { + if (!flatListRef?.current) { return; } - flatListRef.current.scrollToIndex(index); + flatListRef.current.scrollToIndex({index}); }; /** * Scroll to the bottom of the flatlist. */ const scrollToBottom = useCallback(() => { - if (!flatListRef.current) { + if (!flatListRef?.current) { return; } diff --git a/src/hooks/useReportScrollManager/index.js b/src/hooks/useReportScrollManager/index.ts similarity index 68% rename from src/hooks/useReportScrollManager/index.js rename to src/hooks/useReportScrollManager/index.ts index 9a3303504b92..fd2c884e5b4c 100644 --- a/src/hooks/useReportScrollManager/index.js +++ b/src/hooks/useReportScrollManager/index.ts @@ -1,29 +1,27 @@ import {useContext, useCallback} from 'react'; import {ActionListContext} from '../../pages/home/ReportScreenContext'; +import ReportScrollManagerData from './types'; -function useReportScrollManager() { +function useReportScrollManager(): ReportScrollManagerData { const flatListRef = useContext(ActionListContext); /** * Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because * we are editing a comment. - * - * @param {Object} index - * @param {Boolean} isEditing */ - const scrollToIndex = (index, isEditing) => { - if (!flatListRef.current || isEditing) { + const scrollToIndex = (index: number, isEditing?: boolean) => { + if (!flatListRef?.current || isEditing) { return; } - flatListRef.current.scrollToIndex(index); + flatListRef.current.scrollToIndex({index, animated: true}); }; /** * Scroll to the bottom of the flatlist. */ const scrollToBottom = useCallback(() => { - if (!flatListRef.current) { + if (!flatListRef?.current) { return; } diff --git a/src/hooks/useReportScrollManager/types.ts b/src/hooks/useReportScrollManager/types.ts new file mode 100644 index 000000000000..f5ff9b2f35cd --- /dev/null +++ b/src/hooks/useReportScrollManager/types.ts @@ -0,0 +1,9 @@ +import {ActionListContextType} from '../../pages/home/ReportScreenContext'; + +type ReportScrollManagerData = { + ref: ActionListContextType; + scrollToIndex: (index: number, isEditing?: boolean) => void; + scrollToBottom: () => void; +}; + +export default ReportScrollManagerData; diff --git a/src/languages/es.ts b/src/languages/es.ts index 37c72524935d..6d735b645e1d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -587,7 +587,7 @@ export default { duplicateWaypointsErrorMessage: 'Por favor elimina los puntos de ruta duplicados', emptyWaypointsErrorMessage: 'Por favor introduce al menos dos puntos de ruta', }, - waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `nicio el pago, pero no se procesará hasta que ${submitterDisplayName} active su Billetera`, + waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `Inició el pago, pero no se procesará hasta que ${submitterDisplayName} active su Billetera`, enableWallet: 'Habilitar Billetera', }, notificationPreferencesPage: { diff --git a/src/libs/DeviceCapabilities/hasPassiveEventListenerSupport/index.native.ts b/src/libs/DeviceCapabilities/hasPassiveEventListenerSupport/index.native.ts new file mode 100644 index 000000000000..a5e57675fcf5 --- /dev/null +++ b/src/libs/DeviceCapabilities/hasPassiveEventListenerSupport/index.native.ts @@ -0,0 +1,8 @@ +import HasPassiveEventListenerSupport from './types'; + +/** + * Allows us to identify whether the browser supports passive event listener. + */ +const hasPassiveEventListenerSupport: HasPassiveEventListenerSupport = () => false; + +export default hasPassiveEventListenerSupport; diff --git a/src/libs/DeviceCapabilities/hasPassiveEventListenerSupport/index.ts b/src/libs/DeviceCapabilities/hasPassiveEventListenerSupport/index.ts new file mode 100644 index 000000000000..d3c6af0766af --- /dev/null +++ b/src/libs/DeviceCapabilities/hasPassiveEventListenerSupport/index.ts @@ -0,0 +1,18 @@ +/** + * Allows us to identify whether the browser supports passive event listener. + */ +export default function hasPassiveEventListenerSupport(): boolean { + let supportsPassive = false; + try { + const opts = Object.defineProperty({}, 'passive', { + // eslint-disable-next-line getter-return + get() { + supportsPassive = true; + }, + }); + window.addEventListener('testPassive', () => {}, opts); + window.removeEventListener('testPassive', () => {}, opts); + // eslint-disable-next-line no-empty + } catch (e) {} + return supportsPassive; +} diff --git a/src/libs/DeviceCapabilities/hasPassiveEventListenerSupport/types.ts b/src/libs/DeviceCapabilities/hasPassiveEventListenerSupport/types.ts new file mode 100644 index 000000000000..2987bba0b28c --- /dev/null +++ b/src/libs/DeviceCapabilities/hasPassiveEventListenerSupport/types.ts @@ -0,0 +1,3 @@ +type HasPassiveEventListenerSupport = () => boolean; + +export default HasPassiveEventListenerSupport; diff --git a/src/libs/DeviceCapabilities/index.ts b/src/libs/DeviceCapabilities/index.ts index 3759e4ade730..51c65fef3d5b 100644 --- a/src/libs/DeviceCapabilities/index.ts +++ b/src/libs/DeviceCapabilities/index.ts @@ -1,4 +1,5 @@ import canUseTouchScreen from './canUseTouchScreen'; import hasHoverSupport from './hasHoverSupport'; +import hasPassiveEventListenerSupport from './hasPassiveEventListenerSupport'; -export {canUseTouchScreen, hasHoverSupport}; +export {canUseTouchScreen, hasHoverSupport, hasPassiveEventListenerSupport}; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index dd7175dbc6f6..3ac6a8025eb9 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -1,12 +1,10 @@ -import React from 'react'; +import React, {memo, useEffect, useRef} from 'react'; import Onyx, {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {View} from 'react-native'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import CONST from '../../../CONST'; -import compose from '../../compose'; import * as PersonalDetails from '../../actions/PersonalDetails'; import * as Pusher from '../../Pusher/pusher'; import PusherConnectionManager from '../../PusherConnectionManager'; @@ -36,6 +34,13 @@ import NotFoundPage from '../../../pages/ErrorPage/NotFoundPage'; import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions'; import DemoSetupPage from '../../../pages/DemoSetupPage'; import getCurrentUrl from '../currentUrl'; +import useWindowDimensions from '../../../hooks/useWindowDimensions'; + +const loadReportAttachments = () => require('../../../pages/home/report/ReportAttachments').default; +const loadSidebarScreen = () => require('../../../pages/home/sidebar/SidebarScreen').default; +const loadValidateLoginPage = () => require('../../../pages/ValidateLoginPage').default; +const loadLogOutPreviousUserPage = () => require('../../../pages/LogOutPreviousUserPage').default; +const loadConciergePage = () => require('../../../pages/ConciergePage').default; let timezone; let currentAccountID; @@ -89,11 +94,11 @@ Onyx.connect({ }); const RootStack = createCustomStackNavigator(); - // We want to delay the re-rendering for components(e.g. ReportActionCompose) // that depends on modal visibility until Modal is completely closed and its focused // When modal screen is focused, update modal visibility in Onyx // https://reactnavigation.org/docs/navigation-events/ + const modalScreenListeners = { focus: () => { Modal.setModalVisibility(true); @@ -124,8 +129,6 @@ const propTypes = { isBeginningDemo: PropTypes.bool, }), }), - - ...windowDimensionsPropTypes, }; const defaultProps = { @@ -138,16 +141,23 @@ const defaultProps = { demoInfo: {}, }; -class AuthScreens extends React.Component { - constructor(props) { - super(props); +function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, session, lastOpenedPublicRoomID, demoInfo}) { + const {isSmallScreenWidth} = useWindowDimensions(); + const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth); + const isInitialRender = useRef(true); + if (isInitialRender.current) { Timing.start(CONST.TIMING.HOMEPAGE_INITIAL_RENDER); + isInitialRender.current = false; } - componentDidMount() { + useEffect(() => { + const shortcutsOverviewShortcutConfig = CONST.KEYBOARD_SHORTCUTS.SHORTCUTS; + const searchShortcutConfig = CONST.KEYBOARD_SHORTCUTS.SEARCH; + const chatShortcutConfig = CONST.KEYBOARD_SHORTCUTS.NEW_CHAT; + const shouldGetAllData = isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession(); const currentUrl = getCurrentUrl(); - const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(currentUrl, this.props.session.email); + const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(currentUrl, session.email); // Sign out the current user if we're transitioning with a different user const isTransitioning = currentUrl.includes(ROUTES.TRANSITION_BETWEEN_APPS); if (isLoggingInAsNewUser && isTransitioning) { @@ -160,7 +170,7 @@ class AuthScreens extends React.Component { if (isLoadingApp) { App.openApp(); } else { - App.reconnectApp(this.props.lastUpdateIDAppliedToClient); + App.reconnectApp(lastUpdateIDAppliedToClient); } }); PusherConnectionManager.init(); @@ -177,33 +187,28 @@ class AuthScreens extends React.Component { // Note: If a Guide has enabled the memory only key mode then we do want to run OpenApp as their app will not be rehydrated with // the correct state on refresh. They are explicitly opting out of storing data they would need (i.e. reports_) to take advantage of // the optimizations performed during ReconnectApp. - const shouldGetAllData = this.props.isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession(); if (shouldGetAllData) { App.openApp(); } else { - App.reconnectApp(this.props.lastUpdateIDAppliedToClient); + App.reconnectApp(lastUpdateIDAppliedToClient); } - - App.setUpPoliciesAndNavigate(this.props.session, !this.props.isSmallScreenWidth); + App.setUpPoliciesAndNavigate(session, !isSmallScreenWidth); App.redirectThirdPartyDesktopSignIn(); // Check if we should be running any demos immediately after signing in. - if (lodashGet(this.props.demoInfo, 'money2020.isBeginningDemo', false)) { + if (lodashGet(demoInfo, 'money2020.isBeginningDemo', false)) { Navigation.navigate(ROUTES.MONEY2020, CONST.NAVIGATION.TYPE.FORCED_UP); } - if (this.props.lastOpenedPublicRoomID) { + if (lastOpenedPublicRoomID) { // Re-open the last opened public room if the user logged in from a public room link - Report.openLastOpenedPublicRoom(this.props.lastOpenedPublicRoomID); + Report.openLastOpenedPublicRoom(lastOpenedPublicRoomID); } Download.clearDownloads(); - Timing.end(CONST.TIMING.HOMEPAGE_INITIAL_RENDER); - const shortcutsOverviewShortcutConfig = CONST.KEYBOARD_SHORTCUTS.SHORTCUTS; - const searchShortcutConfig = CONST.KEYBOARD_SHORTCUTS.SEARCH; - const chatShortcutConfig = CONST.KEYBOARD_SHORTCUTS.NEW_CHAT; + Timing.end(CONST.TIMING.HOMEPAGE_INITIAL_RENDER); // Listen to keyboard shortcuts for opening certain pages - this.unsubscribeShortcutsOverviewShortcut = KeyboardShortcut.subscribe( + const unsubscribeShortcutsOverviewShortcut = KeyboardShortcut.subscribe( shortcutsOverviewShortcutConfig.shortcutKey, () => { Modal.close(() => { @@ -217,168 +222,146 @@ class AuthScreens extends React.Component { shortcutsOverviewShortcutConfig.modifiers, true, ); - this.unsubscribeSearchShortcut = KeyboardShortcut.subscribe( + + // Listen for the key K being pressed so that focus can be given to + // the chat switcher, or new group chat + // based on the key modifiers pressed and the operating system + const unsubscribeSearchShortcut = KeyboardShortcut.subscribe( searchShortcutConfig.shortcutKey, () => { - Modal.close(() => Navigation.navigate(ROUTES.SEARCH)); + Modal.close(Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.SEARCH))); }, - searchShortcutConfig.descriptionKey, - searchShortcutConfig.modifiers, + shortcutsOverviewShortcutConfig.descriptionKey, + shortcutsOverviewShortcutConfig.modifiers, true, ); - this.unsubscribeChatShortcut = KeyboardShortcut.subscribe( + + const unsubscribeChatShortcut = KeyboardShortcut.subscribe( chatShortcutConfig.shortcutKey, () => { - Modal.close(() => Navigation.navigate(ROUTES.NEW)); + Modal.close(Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.NEW))); }, chatShortcutConfig.descriptionKey, chatShortcutConfig.modifiers, true, ); - } - shouldComponentUpdate(nextProps) { - return nextProps.windowHeight !== this.props.windowHeight || nextProps.isSmallScreenWidth !== this.props.isSmallScreenWidth; - } + return () => { + unsubscribeShortcutsOverviewShortcut(); + unsubscribeSearchShortcut(); + unsubscribeChatShortcut(); + Session.cleanupSession(); + }; - componentWillUnmount() { - if (this.unsubscribeShortcutsOverviewShortcut) { - this.unsubscribeShortcutsOverviewShortcut(); - } - if (this.unsubscribeSearchShortcut) { - this.unsubscribeSearchShortcut(); - } - if (this.unsubscribeChatShortcut) { - this.unsubscribeChatShortcut(); - } - Session.cleanupSession(); - clearInterval(this.interval); - this.interval = null; - } + // Rule disabled because this effect is only for component did mount & will component unmount lifecycle event + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - render() { - const screenOptions = getRootNavigatorScreenOptions(this.props.isSmallScreenWidth); - - return ( - - - { - const SidebarScreen = require('../../../pages/home/sidebar/SidebarScreen').default; - return SidebarScreen; - }} - /> - - { - const ValidateLoginPage = require('../../../pages/ValidateLoginPage').default; - return ValidateLoginPage; - }} - /> - { - const LogOutPreviousUserPage = require('../../../pages/LogOutPreviousUserPage').default; - return LogOutPreviousUserPage; - }} - /> - { - const ConciergePage = require('../../../pages/ConciergePage').default; - return ConciergePage; - }} - /> - - - - { - const ReportAttachments = require('../../../pages/home/report/ReportAttachments').default; - return ReportAttachments; - }} - listeners={modalScreenListeners} - /> - - - - - - ); - } + return ( + + + + + + + + + + + + + + + + + ); } +AuthScreens.displayName = 'AuthScreens'; AuthScreens.propTypes = propTypes; AuthScreens.defaultProps = defaultProps; -export default compose( - withWindowDimensions, - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - lastOpenedPublicRoomID: { - key: ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID, - }, - isUsingMemoryOnlyKeys: { - key: ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS, - }, - lastUpdateIDAppliedToClient: { - key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, - }, - demoInfo: { - key: ONYXKEYS.DEMO_INFO, - }, - }), -)(AuthScreens); + +const AuthScreensMemoized = memo(AuthScreens, () => true); + +export default withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + lastOpenedPublicRoomID: { + key: ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID, + }, + isUsingMemoryOnlyKeys: { + key: ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS, + }, + lastUpdateIDAppliedToClient: { + key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, + }, + demoInfo: { + key: ONYXKEYS.DEMO_INFO, + }, +})(AuthScreensMemoized); diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 13489c396c3c..9703a4368abe 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -42,10 +42,6 @@ function canUseCustomStatus(betas: Beta[]): boolean { return betas?.includes(CONST.BETAS.CUSTOM_STATUS) || canUseAllBetas(betas); } -function canUseCategories(betas: Beta[]): boolean { - return betas?.includes(CONST.BETAS.NEW_DOT_CATEGORIES) || canUseAllBetas(betas); -} - function canUseTags(betas: Beta[]): boolean { return betas?.includes(CONST.BETAS.NEW_DOT_TAGS) || canUseAllBetas(betas); } @@ -66,7 +62,6 @@ export default { canUsePolicyRooms, canUseTasks, canUseCustomStatus, - canUseCategories, canUseTags, canUseLinkPreviews, }; diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index 29c49427bc81..ddb24d62ef26 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -17,8 +17,8 @@ Onyx.connect({ }); /** - * @param {Object} passedPersonalDetails - * @param {Array} pathToDisplayName + * @param {Object | Null} passedPersonalDetails + * @param {Array | String} pathToDisplayName * @param {String} [defaultValue] optional default display name value * @returns {String} */ diff --git a/src/libs/PolicyUtils.js b/src/libs/PolicyUtils.js deleted file mode 100644 index 2ecc818ebd23..000000000000 --- a/src/libs/PolicyUtils.js +++ /dev/null @@ -1,292 +0,0 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; -import Str from 'expensify-common/lib/str'; -import CONST from '../CONST'; -import ONYXKEYS from '../ONYXKEYS'; - -/** - * Filter out the active policies, which will exclude policies with pending deletion - * These are policies that we can use to create reports with in NewDot. - * @param {Object} policies - * @returns {Array} - */ -function getActivePolicies(policies) { - return _.filter(policies, (policy) => policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); -} - -/** - * Checks if we have any errors stored within the POLICY_MEMBERS. Determines whether we should show a red brick road error or not. - * Data structure: {accountID: {role:'user', errors: []}, accountID2: {role:'admin', errors: [{1231312313: 'Unable to do X'}]}, ...} - * - * @param {Object} policyMembers - * @returns {Boolean} - */ -function hasPolicyMemberError(policyMembers) { - return _.some(policyMembers, (member) => !_.isEmpty(member.errors)); -} - -/** - * Check if the policy has any error fields. - * - * @param {Object} policy - * @param {Object} policy.errorFields - * @return {Boolean} - */ -function hasPolicyErrorFields(policy) { - return _.some(lodashGet(policy, 'errorFields', {}), (fieldErrors) => !_.isEmpty(fieldErrors)); -} - -/** - * Check if the policy has any errors, and if it doesn't, then check if it has any error fields. - * - * @param {Object} policy - * @param {Object} policy.errors - * @param {Object} policy.errorFields - * @return {Boolean} - */ -function hasPolicyError(policy) { - return !_.isEmpty(lodashGet(policy, 'errors', {})) ? true : hasPolicyErrorFields(policy); -} - -/** - * Checks if we have any errors stored within the policy custom units. - * - * @param {Object} policy - * @returns {Boolean} - */ -function hasCustomUnitsError(policy) { - return !_.isEmpty(_.pick(lodashGet(policy, 'customUnits', {}), 'errors')); -} - -/** - * @param {Number} value - * @param {Function} toLocaleDigit - * @returns {Number} - */ -function getNumericValue(value, toLocaleDigit) { - const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')); - if (Number.isNaN(numValue)) { - return NaN; - } - return numValue.toFixed(CONST.CUSTOM_UNITS.RATE_DECIMALS); -} - -/** - * @param {Number} value - * @param {Function} toLocaleDigit - * @returns {String} - */ -function getRateDisplayValue(value, toLocaleDigit) { - const numValue = getNumericValue(value, toLocaleDigit); - if (Number.isNaN(numValue)) { - return ''; - } - return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.length); -} - -/** - * @param {Object} customUnitRate - * @param {Number} customUnitRate.rate - * @param {Function} toLocaleDigit - * @returns {String} - */ -function getUnitRateValue(customUnitRate, toLocaleDigit) { - return getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, toLocaleDigit); -} - -/** - * Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error. - * - * @param {Object} policy - * @param {String} policy.id - * @param {Object} policyMembersCollection - * @returns {String} - */ -function getPolicyBrickRoadIndicatorStatus(policy, policyMembersCollection) { - const policyMembers = lodashGet(policyMembersCollection, `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy.id}`, {}); - if (hasPolicyMemberError(policyMembers) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) { - return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; - } - return ''; -} - -/** - * Check if the policy can be displayed - * If offline, always show the policy pending deletion. - * If online, show the policy pending deletion only if there is an error. - * Note: Using a local ONYXKEYS.NETWORK subscription will cause a delay in - * updating the screen. Passing the offline status from the component. - * @param {Object} policy - * @param {Boolean} isOffline - * @returns {Boolean} - */ -function shouldShowPolicy(policy, isOffline) { - return ( - policy && - policy.isPolicyExpenseChatEnabled && - policy.role === CONST.POLICY.ROLE.ADMIN && - (isOffline || policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !_.isEmpty(policy.errors)) - ); -} - -/** - * @param {string} email - * @returns {boolean} - */ -function isExpensifyTeam(email) { - const emailDomain = Str.extractEmailDomain(email); - return emailDomain === CONST.EXPENSIFY_PARTNER_NAME || emailDomain === CONST.EMAIL.GUIDES_DOMAIN; -} - -/** - * @param {string} email - * @returns {boolean} - */ -function isExpensifyGuideTeam(email) { - const emailDomain = Str.extractEmailDomain(email); - return emailDomain === CONST.EMAIL.GUIDES_DOMAIN; -} - -/** - * Checks if the current user is an admin of the policy. - * - * @param {Object} policy - * @returns {Boolean} - */ -const isPolicyAdmin = (policy) => lodashGet(policy, 'role') === CONST.POLICY.ROLE.ADMIN; - -/** - * - * @param {String} policyID - * @param {Object} policies - * @returns {Boolean} - */ -const isPolicyMember = (policyID, policies) => _.some(policies, (policy) => lodashGet(policy, 'id') === policyID); - -/** - * @param {Object} policyMembers - * @param {Object} personalDetails - * @returns {Object} - * - * Create an object mapping member emails to their accountIDs. Filter for members without errors, and get the login email from the personalDetail object using the accountID. - * - * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. - */ -function getMemberAccountIDsForWorkspace(policyMembers, personalDetails) { - const memberEmailsToAccountIDs = {}; - _.each(policyMembers, (member, accountID) => { - if (!_.isEmpty(member.errors)) { - return; - } - const personalDetail = personalDetails[accountID]; - if (!personalDetail || !personalDetail.login) { - return; - } - memberEmailsToAccountIDs[personalDetail.login] = Number(accountID); - }); - return memberEmailsToAccountIDs; -} - -/** - * Get login list that we should not show in the workspace invite options - * - * @param {Object} policyMembers - * @param {Object} personalDetails - * @returns {Array} - */ -function getIneligibleInvitees(policyMembers, personalDetails) { - const memberEmailsToExclude = [...CONST.EXPENSIFY_EMAILS]; - _.each(policyMembers, (policyMember, accountID) => { - // Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them). - if (policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !_.isEmpty(policyMember.errors)) { - return; - } - const memberEmail = lodashGet(personalDetails, `[${accountID}].login`); - if (!memberEmail) { - return; - } - memberEmailsToExclude.push(memberEmail); - }); - - return memberEmailsToExclude; -} - -/** - * Gets the tag from policy tags, defaults to the first if no key is provided. - * - * @param {Object} policyTags - * @param {String} [tagKey] - * @returns {Object} - */ -function getTag(policyTags, tagKey) { - if (_.isEmpty(policyTags)) { - return {}; - } - - const policyTagKey = tagKey || _.first(_.keys(policyTags)); - - return lodashGet(policyTags, policyTagKey, {}); -} - -/** - * Gets the first tag name from policy tags. - * - * @param {Object} policyTags - * @returns {String} - */ -function getTagListName(policyTags) { - if (_.isEmpty(policyTags)) { - return ''; - } - - const policyTagKeys = _.keys(policyTags) || []; - - return lodashGet(policyTags, [_.first(policyTagKeys), 'name'], ''); -} - -/** - * Gets the tags of a policy for a specific key. Defaults to the first tag if no key is provided. - * - * @param {Object} policyTags - * @param {String} [tagKey] - * @returns {String} - */ -function getTagList(policyTags, tagKey) { - if (_.isEmpty(policyTags)) { - return {}; - } - - const policyTagKey = tagKey || _.first(_.keys(policyTags)); - - return lodashGet(policyTags, [policyTagKey, 'tags'], {}); -} - -/** - * @param {Object} policy - * @returns {Boolean} - */ -function isPendingDeletePolicy(policy) { - return policy.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; -} - -export { - getActivePolicies, - hasPolicyMemberError, - hasPolicyError, - hasPolicyErrorFields, - hasCustomUnitsError, - getNumericValue, - getUnitRateValue, - getPolicyBrickRoadIndicatorStatus, - shouldShowPolicy, - isExpensifyTeam, - isExpensifyGuideTeam, - isPolicyAdmin, - getMemberAccountIDsForWorkspace, - getIneligibleInvitees, - isPolicyMember, - getTag, - getTagListName, - getTagList, - isPendingDeletePolicy, -}; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts new file mode 100644 index 000000000000..e33af862f63d --- /dev/null +++ b/src/libs/PolicyUtils.ts @@ -0,0 +1,220 @@ +import {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Str from 'expensify-common/lib/str'; +import CONST from '../CONST'; +import ONYXKEYS from '../ONYXKEYS'; +import {PersonalDetails, Policy, PolicyMembers, PolicyTags} from '../types/onyx'; + +type MemberEmailsToAccountIDs = Record; +type PersonalDetailsList = Record; +type UnitRate = {rate: number}; + +/** + * Filter out the active policies, which will exclude policies with pending deletion + * These are policies that we can use to create reports with in NewDot. + */ +function getActivePolicies(policies: OnyxCollection): Policy[] | undefined { + return Object.values(policies ?? {}).filter( + (policy): policy is Policy => + policy !== null && policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + ); +} + +/** + * Checks if we have any errors stored within the POLICY_MEMBERS. Determines whether we should show a red brick road error or not. + * Data structure: {accountID: {role:'user', errors: []}, accountID2: {role:'admin', errors: [{1231312313: 'Unable to do X'}]}, ...} + */ +function hasPolicyMemberError(policyMembers: OnyxEntry): boolean { + return Object.values(policyMembers ?? {}).some((member) => Object.keys(member?.errors ?? {}).length > 0); +} + +/** + * Check if the policy has any error fields. + */ +function hasPolicyErrorFields(policy: OnyxEntry): boolean { + return Object.keys(policy?.errorFields ?? {}).some((fieldErrors) => Object.keys(fieldErrors ?? {}).length > 0); +} + +/** + * Check if the policy has any errors, and if it doesn't, then check if it has any error fields. + */ +function hasPolicyError(policy: OnyxEntry): boolean { + return Object.keys(policy?.errors ?? {}).length > 0 ? true : hasPolicyErrorFields(policy); +} + +/** + * Checks if we have any errors stored within the policy custom units. + */ +function hasCustomUnitsError(policy: OnyxEntry): boolean { + return Object.keys(policy?.customUnits?.errors ?? {}).length > 0; +} + +function getNumericValue(value: number, toLocaleDigit: (arg: string) => string): number | string { + const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')); + if (Number.isNaN(numValue)) { + return NaN; + } + return numValue.toFixed(CONST.CUSTOM_UNITS.RATE_DECIMALS); +} + +function getRateDisplayValue(value: number, toLocaleDigit: (arg: string) => string): string { + const numValue = getNumericValue(value, toLocaleDigit); + if (Number.isNaN(numValue)) { + return ''; + } + return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.toString().length); +} + +function getUnitRateValue(customUnitRate: UnitRate, toLocaleDigit: (arg: string) => string) { + return getRateDisplayValue((customUnitRate?.rate ?? 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, toLocaleDigit); +} + +/** + * Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error. + */ +function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry, policyMembersCollection: OnyxCollection): string { + const policyMembers = policyMembersCollection?.[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy?.id}`] ?? {}; + if (hasPolicyMemberError(policyMembers) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) { + return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; + } + return ''; +} + +/** + * Check if the policy can be displayed + * If offline, always show the policy pending deletion. + * If online, show the policy pending deletion only if there is an error. + * Note: Using a local ONYXKEYS.NETWORK subscription will cause a delay in + * updating the screen. Passing the offline status from the component. + */ +function shouldShowPolicy(policy: OnyxEntry, isOffline: boolean): boolean { + return ( + !!policy && + policy?.isPolicyExpenseChatEnabled && + policy?.role === CONST.POLICY.ROLE.ADMIN && + (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) + ); +} + +function isExpensifyTeam(email: string): boolean { + const emailDomain = Str.extractEmailDomain(email ?? ''); + return emailDomain === CONST.EXPENSIFY_PARTNER_NAME || emailDomain === CONST.EMAIL.GUIDES_DOMAIN; +} + +function isExpensifyGuideTeam(email: string): boolean { + const emailDomain = Str.extractEmailDomain(email ?? ''); + return emailDomain === CONST.EMAIL.GUIDES_DOMAIN; +} + +/** + * Checks if the current user is an admin of the policy. + */ +const isPolicyAdmin = (policy: OnyxEntry): boolean => policy?.role === CONST.POLICY.ROLE.ADMIN; + +const isPolicyMember = (policyID: string, policies: Record): boolean => Object.values(policies).some((policy) => policy?.id === policyID); + +/** + * Create an object mapping member emails to their accountIDs. Filter for members without errors, and get the login email from the personalDetail object using the accountID. + * + * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. + */ +function getMemberAccountIDsForWorkspace(policyMembers: OnyxEntry, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { + const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; + Object.keys(policyMembers ?? {}).forEach((accountID) => { + const member = policyMembers?.[accountID]; + if (Object.keys(member?.errors ?? {})?.length > 0) { + return; + } + const personalDetail = personalDetails?.[accountID]; + if (!personalDetail?.login) { + return; + } + memberEmailsToAccountIDs[personalDetail.login] = Number(accountID); + }); + return memberEmailsToAccountIDs; +} + +/** + * Get login list that we should not show in the workspace invite options + */ +function getIneligibleInvitees(policyMembers: OnyxEntry, personalDetails: OnyxEntry): string[] { + const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS]; + Object.keys(policyMembers ?? {}).forEach((accountID) => { + const policyMember = policyMembers?.[accountID]; + // Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them). + if (policyMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) { + return; + } + const memberEmail = personalDetails?.[accountID]?.login; + if (!memberEmail) { + return; + } + memberEmailsToExclude.push(memberEmail); + }); + + return memberEmailsToExclude; +} + +/** + * Gets the tag from policy tags, defaults to the first if no key is provided. + */ +function getTag(policyTags: OnyxEntry, tagKey?: keyof typeof policyTags) { + if (Object.keys(policyTags ?? {})?.length === 0) { + return {}; + } + + const policyTagKey = tagKey ?? Object.keys(policyTags ?? {})[0]; + + return policyTags?.[policyTagKey] ?? {}; +} + +/** + * Gets the first tag name from policy tags. + */ +function getTagListName(policyTags: OnyxEntry) { + if (Object.keys(policyTags ?? {})?.length === 0) { + return ''; + } + + const policyTagKeys = Object.keys(policyTags ?? {})[0] ?? []; + + return policyTags?.[policyTagKeys]?.name ?? ''; +} + +/** + * Gets the tags of a policy for a specific key. Defaults to the first tag if no key is provided. + */ +function getTagList(policyTags: OnyxCollection, tagKey: string) { + if (Object.keys(policyTags ?? {})?.length === 0) { + return {}; + } + + const policyTagKey = tagKey ?? Object.keys(policyTags ?? {})[0]; + + return policyTags?.[policyTagKey]?.tags ?? {}; +} + +function isPendingDeletePolicy(policy: OnyxEntry): boolean { + return policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; +} + +export { + getActivePolicies, + hasPolicyMemberError, + hasPolicyError, + hasPolicyErrorFields, + hasCustomUnitsError, + getNumericValue, + getUnitRateValue, + getPolicyBrickRoadIndicatorStatus, + shouldShowPolicy, + isExpensifyTeam, + isExpensifyGuideTeam, + isPolicyAdmin, + getMemberAccountIDsForWorkspace, + getIneligibleInvitees, + getTag, + getTagListName, + getTagList, + isPendingDeletePolicy, + isPolicyMember, +}; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 98a029bde5de..5d9b4fbdc9cf 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -267,7 +267,7 @@ function isConsecutiveActionMadeByPreviousActor(reportActions: ReportAction[] | /** * Checks if a reportAction is deprecated. */ -function isReportActionDeprecated(reportAction: OnyxEntry, key: string): boolean { +function isReportActionDeprecated(reportAction: OnyxEntry, key: string | number): boolean { if (!reportAction) { return true; } @@ -289,7 +289,7 @@ const supportedActionTypes: ActionName[] = [...Object.values(otherActionTypes), * Checks if a reportAction is fit for display, meaning that it's not deprecated, is of a valid * and supported type, it's not deleted and also not closed. */ -function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string): boolean { +function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string | number): boolean { if (!reportAction) { return false; } diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index b9510499a892..30b066ddd0f3 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -108,9 +108,9 @@ function getPolicyType(report, policies) { /** * Get the policy name from a given report * @param {Object} report - * @param {String} report.policyID - * @param {String} report.oldPolicyName - * @param {String} report.policyName + * @param {String} [report.policyID] + * @param {String} [report.oldPolicyName] + * @param {String} [report.policyName] * @param {Boolean} [returnEmptyIfNotFound] * @param {Object} [policy] * @returns {String} @@ -369,7 +369,7 @@ function isUserCreatedPolicyRoom(report) { /** * Whether the provided report is a Policy Expense chat. * @param {Object} report - * @param {String} report.chatType + * @param {String} [report.chatType] * @returns {Boolean} */ function isPolicyExpenseChat(report) { @@ -395,7 +395,7 @@ function isControlPolicyExpenseReport(report) { /** * Whether the provided report is a chat room * @param {Object} report - * @param {String} report.chatType + * @param {String} [report.chatType] * @returns {Boolean} */ function isChatRoom(report) { @@ -584,8 +584,8 @@ function findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTim /** * Whether the provided report is an archived room * @param {Object} report - * @param {Number} report.stateNum - * @param {Number} report.statusNum + * @param {Number} [report.stateNum] + * @param {Number} [report.statusNum] * @returns {Boolean} */ function isArchivedRoom(report) { @@ -2468,6 +2468,7 @@ function getIOUReportActionMessage(iouReportID, type, total, comment, currency, * @param {Boolean} [isSendMoneyFlow] - Whether this is send money flow * @param {Object} [receipt] * @param {Boolean} [isOwnPolicyExpenseChat] - Whether this is an expense report create from the current user's policy expense chat + * @param {String} [created] - Action created time * @returns {Object} */ function buildOptimisticIOUReportAction( @@ -2483,6 +2484,7 @@ function buildOptimisticIOUReportAction( isSendMoneyFlow = false, receipt = {}, isOwnPolicyExpenseChat = false, + created = DateUtils.getDBTime(), ) { const IOUReportID = iouReportID || generateReportID(); @@ -2540,7 +2542,7 @@ function buildOptimisticIOUReportAction( ], reportActionID: NumberUtils.rand64(), shouldShow: true, - created: DateUtils.getDBTime(), + created, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, whisperedToAccountIDs: _.contains([CONST.IOU.RECEIPT_STATE.SCANREADY, CONST.IOU.RECEIPT_STATE.SCANNING], receipt.state) ? [currentUserAccountID] : [], }; @@ -2849,9 +2851,10 @@ function buildOptimisticChatReport( /** * Returns the necessary reportAction onyx data to indicate that the chat has been created optimistically * @param {String} emailCreatingAction + * @param {String} [created] - Action created time * @returns {Object} */ -function buildOptimisticCreatedReportAction(emailCreatingAction) { +function buildOptimisticCreatedReportAction(emailCreatingAction, created = DateUtils.getDBTime()) { return { reportActionID: NumberUtils.rand64(), actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, @@ -2878,7 +2881,7 @@ function buildOptimisticCreatedReportAction(emailCreatingAction) { ], automatic: false, avatar: lodashGet(allPersonalDetails, [currentUserAccountID, 'avatar'], UserUtils.getDefaultAvatarURL(currentUserAccountID)), - created: DateUtils.getDBTime(), + created, shouldShow: true, }; } @@ -3203,8 +3206,8 @@ function canSeeDefaultRoom(report, policies, betas) { /** * @param {Object} report - * @param {Array} policies - * @param {Array} betas + * @param {Object | null} policies + * @param {Array | null} betas * @param {Object} allReportActions * @returns {Boolean} */ @@ -3241,7 +3244,7 @@ function shouldHideReport(report, currentReportId) { * filter out the majority of reports before filtering out very specific minority of reports. * * @param {Object} report - * @param {String} currentReportId + * @param {String | Null | Undefined} currentReportId * @param {Boolean} isInGSDMode * @param {String[]} betas * @param {Object} policies @@ -3259,6 +3262,7 @@ function shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, !report || !report.reportID || !report.type || + report.reportName === undefined || report.isHidden || (report.participantAccountIDs && report.participantAccountIDs.length === 0 && diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.ts similarity index 59% rename from src/libs/SidebarUtils.js rename to src/libs/SidebarUtils.ts index 6b9e6d10fd16..bfe7d2281049 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.ts @@ -1,8 +1,7 @@ /* eslint-disable rulesdir/prefer-underscore-method */ import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; +import {ValueOf} from 'type-fest'; import ONYXKEYS from '../ONYXKEYS'; import * as ReportUtils from './ReportUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; @@ -13,9 +12,16 @@ import * as CollectionUtils from './CollectionUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as UserUtils from './UserUtils'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; +import ReportAction, {ReportActions} from '../types/onyx/ReportAction'; +import Beta from '../types/onyx/Beta'; +import Policy from '../types/onyx/Policy'; +import Report from '../types/onyx/Report'; +import {PersonalDetails} from '../types/onyx'; +import * as OnyxCommon from '../types/onyx/OnyxCommon'; + +const visibleReportActionItems: ReportActions = {}; +const lastReportActions: ReportActions = {}; -const visibleReportActionItems = {}; -const lastReportActions = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, callback: (actions, key) => { @@ -24,38 +30,37 @@ Onyx.connect({ } const reportID = CollectionUtils.extractCollectionItemID(key); - const actionsArray = ReportActionsUtils.getSortedReportActions(_.toArray(actions)); - lastReportActions[reportID] = _.last(actionsArray); + const actionsArray: ReportAction[] = ReportActionsUtils.getSortedReportActions(Object.values(actions)); + lastReportActions[reportID] = actionsArray[actionsArray.length - 1]; // The report is only visible if it is the last action not deleted that // does not match a closed or created state. - const reportActionsForDisplay = _.filter( - actionsArray, + const reportActionsForDisplay = actionsArray.filter( (reportAction, actionKey) => ReportActionsUtils.shouldReportActionBeVisible(reportAction, actionKey) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); - visibleReportActionItems[reportID] = _.last(reportActionsForDisplay); + visibleReportActionItems[reportID] = reportActionsForDisplay[reportActionsForDisplay.length - 1]; }, }); // Session can remain stale because the only way for the current user to change is to // sign out and sign in, which would clear out all the Onyx // data anyway and cause SidebarLinks to rerender. -let currentUserAccountID; +let currentUserAccountID: number | undefined; Onyx.connect({ key: ONYXKEYS.SESSION, - callback: (val) => { - if (!val) { + callback: (session) => { + if (!session) { return; } - currentUserAccountID = val.accountID; + currentUserAccountID = session.accountID; }, }); -let resolveSidebarIsReadyPromise; +let resolveSidebarIsReadyPromise: (args?: unknown[]) => void; let sidebarIsReadyPromise = new Promise((resolve) => { resolveSidebarIsReadyPromise = resolve; @@ -67,15 +72,15 @@ function resetIsSidebarLoadedReadyPromise() { }); } -function isSidebarLoadedReady() { +function isSidebarLoadedReady(): Promise { return sidebarIsReadyPromise; } -function compareStringDates(stringA, stringB) { - if (stringA < stringB) { +function compareStringDates(a: string, b: string): 0 | 1 | -1 { + if (a < b) { return -1; } - if (stringA > stringB) { + if (a > b) { return 1; } return 0; @@ -86,10 +91,10 @@ function setIsSidebarLoadedReady() { } // Define a cache object to store the memoized results -const reportIDsCache = new Map(); +const reportIDsCache = new Map(); // Function to set a key-value pair while maintaining the maximum key limit -function setWithLimit(map, key, value) { +function setWithLimit(map: Map, key: TKey, value: TValue) { if (map.size >= 5) { // If the map has reached its limit, remove the first (oldest) key-value pair const firstKey = map.keys().next().value; @@ -102,20 +107,20 @@ function setWithLimit(map, key, value) { let hasInitialReportActions = false; /** - * @param {String} currentReportId - * @param {Object} allReportsDict - * @param {Object} betas - * @param {String[]} policies - * @param {String} priorityMode - * @param {Object} allReportActions - * @returns {String[]} An array of reportIDs sorted in the proper order + * @returns An array of reportIDs sorted in the proper order */ -function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, priorityMode, allReportActions) { +function getOrderedReportIDs( + currentReportId: string | null, + allReports: Record, + betas: Beta[], + policies: Record, + priorityMode: ValueOf, + allReportActions: Record, +): string[] { // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( - // eslint-disable-next-line es/no-optional-chaining - [currentReportId, allReportsDict, betas, policies, priorityMode, allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], - (key, value) => { + [currentReportId, allReports, betas, policies, priorityMode, allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], + (key, value: unknown) => { /** * Exclude 'participantAccountIDs', 'participants' and 'lastMessageText' not to overwhelm a cached key value with huge data, * which we don't need to store in a cacheKey @@ -123,13 +128,15 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p if (key === 'participantAccountIDs' || key === 'participants' || key === 'lastMessageText') { return undefined; } + return value; }, ); // Check if the result is already in the cache - if (reportIDsCache.has(cachedReportsKey) && hasInitialReportActions) { - return reportIDsCache.get(cachedReportsKey); + const cachedIDs = reportIDsCache.get(cachedReportsKey); + if (cachedIDs && hasInitialReportActions) { + return cachedIDs; } // This is needed to prevent caching when Onyx is empty for a second render @@ -137,7 +144,7 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReportsDict); + const allReportsDictValues = Object.values(allReports); // Filter out all the reports that shouldn't be displayed const reportsToDisplay = allReportsDictValues.filter((report) => ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies, allReportActions, true)); @@ -158,7 +165,7 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p report.displayName = ReportUtils.getReportName(report); // eslint-disable-next-line no-param-reassign - report.iouReportAmount = ReportUtils.getMoneyRequestReimbursableTotal(report, allReportsDict); + report.iouReportAmount = ReportUtils.getMoneyRequestReimbursableTotal(report, allReports); }); // The LHN is split into five distinct groups, and each group is sorted a little differently. The groups will ALWAYS be in this order: @@ -171,11 +178,11 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p // 5. Archived reports // - Sorted by lastVisibleActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode - const pinnedReports = []; - const outstandingIOUReports = []; - const draftReports = []; - const nonArchivedReports = []; - const archivedReports = []; + const pinnedReports: Report[] = []; + const outstandingIOUReports: Report[] = []; + const draftReports: Report[] = []; + const nonArchivedReports: Report[] = []; + const archivedReports: Report[] = []; reportsToDisplay.forEach((report) => { if (report.isPinned) { pinnedReports.push(report); @@ -191,47 +198,121 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p }); // Sort each group of reports accordingly - pinnedReports.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase())); - outstandingIOUReports.sort((a, b) => b.iouReportAmount - a.iouReportAmount || a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase())); - draftReports.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase())); + pinnedReports.sort((a, b) => (a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0)); + outstandingIOUReports.sort((a, b) => { + const compareAmounts = a?.iouReportAmount && b?.iouReportAmount ? b.iouReportAmount - a.iouReportAmount : 0; + const compareDisplayNames = a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0; + return compareAmounts || compareDisplayNames; + }); + draftReports.sort((a, b) => (a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0)); if (isInDefaultMode) { - nonArchivedReports.sort( - (a, b) => compareStringDates(b.lastVisibleActionCreated, a.lastVisibleActionCreated) || a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()), - ); + nonArchivedReports.sort((a, b) => { + const compareDates = a?.lastVisibleActionCreated && b?.lastVisibleActionCreated ? compareStringDates(b.lastVisibleActionCreated, a.lastVisibleActionCreated) : 0; + const compareDisplayNames = a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0; + return compareDates || compareDisplayNames; + }); // For archived reports ensure that most recent reports are at the top by reversing the order - archivedReports.sort((a, b) => compareStringDates(b.lastVisibleActionCreated, a.lastVisibleActionCreated)); + archivedReports.sort((a, b) => (a?.lastVisibleActionCreated && b?.lastVisibleActionCreated ? compareStringDates(b.lastVisibleActionCreated, a.lastVisibleActionCreated) : 0)); } else { - nonArchivedReports.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase())); - archivedReports.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase())); + nonArchivedReports.sort((a, b) => (a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0)); + archivedReports.sort((a, b) => (a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0)); } // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [].concat(pinnedReports, outstandingIOUReports, draftReports, nonArchivedReports, archivedReports).map((report) => report.reportID); + const LHNReports = [...pinnedReports, ...outstandingIOUReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); setWithLimit(reportIDsCache, cachedReportsKey, LHNReports); return LHNReports; } +type OptionData = { + text?: string | null; + alternateText?: string | null; + pendingAction?: OnyxCommon.PendingAction | null; + allReportErrors?: OnyxCommon.Errors | null; + brickRoadIndicator?: typeof CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR | '' | null; + icons?: Icon[] | null; + tooltipText?: string | null; + ownerAccountID?: number | null; + subtitle?: string | null; + participantsList?: PersonalDetails[] | null; + login?: string | null; + accountID?: number | null; + managerID?: number | null; + reportID?: string | null; + policyID?: string | null; + status?: string | null; + type?: string | null; + stateNum?: ValueOf | null; + statusNum?: ValueOf | null; + phoneNumber?: string | null; + isUnread?: boolean | null; + isUnreadWithMention?: boolean | null; + hasDraftComment?: boolean | null; + keyForList?: string | null; + searchText?: string | null; + isPinned?: boolean | null; + hasOutstandingIOU?: boolean | null; + iouReportID?: string | null; + isIOUReportOwner?: boolean | null; + iouReportAmount?: number | null; + isChatRoom?: boolean | null; + isArchivedRoom?: boolean | null; + shouldShowSubscript?: boolean | null; + isPolicyExpenseChat?: boolean | null; + isMoneyRequestReport?: boolean | null; + isExpenseRequest?: boolean | null; + isWaitingOnBankAccount?: boolean | null; + isAllowedToComment?: boolean | null; + isThread?: boolean | null; + isTaskReport?: boolean | null; + isWaitingForTaskCompleteFromAssignee?: boolean | null; + parentReportID?: string | null; + notificationPreference?: string | number | null; + displayNamesWithTooltips?: DisplayNamesWithTooltip[] | null; + chatType?: ValueOf | null; +}; + +type DisplayNamesWithTooltip = { + displayName?: string; + avatar?: string; + login?: string; + accountID?: number; + pronouns?: string; +}; + +type ActorDetails = { + displayName?: string; + accountID?: number; +}; + +type Icon = { + source?: string; + id?: number; + type?: string; + name?: string; +}; + /** * Gets all the data necessary for rendering an OptionRowLHN component - * - * @param {Object} report - * @param {Object} reportActions - * @param {Object} personalDetails - * @param {String} preferredLocale - * @param {Object} [policy] - * @param {Object} parentReportAction - * @returns {Object} */ -function getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction) { +function getOptionData( + report: Report, + reportActions: Record, + personalDetails: Record, + preferredLocale: ValueOf, + policy: Policy, + parentReportAction: ReportAction, +): OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do // a null check here and return early. if (!report || !personalDetails) { return; } - const result = { + + const result: OptionData = { text: null, alternateText: null, pendingAction: null, @@ -270,9 +351,8 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, isAllowedToComment: true, chatType: null, }; - - const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(report.participantAccountIDs, personalDetails)); - const personalDetail = participantPersonalDetailList[0] || {}; + const participantPersonalDetailList: PersonalDetails[] = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(report.participantAccountIDs ?? [], personalDetails)); + const personalDetail = participantPersonalDetailList[0] ?? {}; result.isThread = ReportUtils.isChatThread(report); result.isChatRoom = ReportUtils.isChatRoom(report); @@ -286,8 +366,8 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : null; - result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions); - result.brickRoadIndicator = !_.isEmpty(result.allReportErrors) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; + result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; + result.brickRoadIndicator = Object.keys(result.allReportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.ownerAccountID = report.ownerAccountID; result.managerID = report.managerID; result.reportID = report.reportID; @@ -300,31 +380,31 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, result.isPinned = report.isPinned; result.iouReportID = report.iouReportID; result.keyForList = String(report.reportID); - result.tooltipText = ReportUtils.getReportParticipantsTitle(report.participantAccountIDs || []); + result.tooltipText = ReportUtils.getReportParticipantsTitle(report.participantAccountIDs ?? []); result.hasOutstandingIOU = report.hasOutstandingIOU; - result.parentReportID = report.parentReportID || null; + result.parentReportID = report.parentReportID ?? null; result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; - result.notificationPreference = report.notificationPreference || null; + result.notificationPreference = report.notificationPreference ?? null; result.isAllowedToComment = !ReportUtils.shouldDisableWriteActions(report); result.chatType = report.chatType; const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; const subtitle = ReportUtils.getChatRoomSubtitle(report); - const login = Str.removeSMSDomain(lodashGet(personalDetail, 'login', '')); - const status = lodashGet(personalDetail, 'status', ''); + const login = Str.removeSMSDomain(personalDetail?.login ?? ''); + const status = personalDetail?.status ?? ''; const formattedLogin = Str.isSMSLogin(login) ? LocalePhoneNumber.formatPhoneNumber(login) : login; // We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade. - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips((participantPersonalDetailList || []).slice(0, 10), hasMultipleParticipants); + const displayNamesWithTooltips: DisplayNamesWithTooltip[] = ReportUtils.getDisplayNamesWithTooltips((participantPersonalDetailList || []).slice(0, 10), hasMultipleParticipants); const lastMessageTextFromReport = OptionsListUtils.getLastMessageTextForReport(report); // If the last actor's details are not currently saved in Onyx Collection, // then try to get that from the last report action if that action is valid // to get data from. - let lastActorDetails = personalDetails[report.lastActorAccountID] || null; + let lastActorDetails: ActorDetails | null = report.lastActorAccountID && personalDetails?.[report.lastActorAccountID] ? personalDetails[report.lastActorAccountID] : null; if (!lastActorDetails && visibleReportActionItems[report.reportID]) { - const lastActorDisplayName = lodashGet(visibleReportActionItems[report.reportID], 'person[0].text'); + const lastActorDisplayName = visibleReportActionItems[report.reportID]?.person?.[0]?.text; lastActorDetails = lastActorDisplayName ? { displayName: lastActorDisplayName, @@ -332,42 +412,35 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, } : null; } - const lastActorDisplayName = - hasMultipleParticipants && lastActorDetails && lastActorDetails.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? lastActorDetails.displayName : ''; + const lastActorDisplayName = hasMultipleParticipants && lastActorDetails?.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? lastActorDetails.displayName : ''; let lastMessageText = lastMessageTextFromReport; + const reportAction = lastReportActions?.[report.reportID]; if (result.isArchivedRoom) { - const archiveReason = - (lastReportActions[report.reportID] && lastReportActions[report.reportID].originalMessage && lastReportActions[report.reportID].originalMessage.reason) || - CONST.REPORT.ARCHIVE_REASON.DEFAULT; + const archiveReason = (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED && reportAction?.originalMessage?.reason) || CONST.REPORT.ARCHIVE_REASON.DEFAULT; lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { - displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), + displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), policyName: ReportUtils.getPolicyName(report, false, policy), }); } if ((result.isChatRoom || result.isPolicyExpenseChat || result.isThread || result.isTaskReport) && !result.isArchivedRoom) { const lastAction = visibleReportActionItems[report.reportID]; - if (lastAction && lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED) { - const newName = lodashGet(lastAction, 'originalMessage.newName', ''); + + if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED) { + const newName = lastAction?.originalMessage?.newName ?? ''; result.alternateText = Localize.translate(preferredLocale, 'newRoomPage.roomRenamedTo', {newName}); - } else if (lastAction && lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED) { + } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED) { result.alternateText = `${Localize.translate(preferredLocale, 'task.messages.reopened')}`; - } else if (lastAction && lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED) { + } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED) { result.alternateText = `${Localize.translate(preferredLocale, 'task.messages.completed')}`; } else if ( - lastAction && - _.includes( - [ - CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM, - CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM, - CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM, - CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.REMOVE_FROM_ROOM, - ], - lastAction.actionName, - ) + lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || + lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM || + lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM || + lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.REMOVE_FROM_ROOM ) { - const targetAccountIDs = lodashGet(lastAction, 'originalMessage.targetAccountIDs', []); + const targetAccountIDs = lastAction?.originalMessage?.targetAccountIDs ?? []; const verb = lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM ? 'invited' @@ -375,7 +448,7 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, const users = targetAccountIDs.length > 1 ? 'users' : 'user'; result.alternateText = `${verb} ${targetAccountIDs.length} ${users}`; - const roomName = lodashGet(lastAction, 'originalMessage.roomName', ''); + const roomName = lastAction?.originalMessage?.roomName ?? ''; if (roomName) { const preposition = lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM @@ -383,7 +456,7 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, : ' from'; result.alternateText += `${preposition} ${roomName}`; } - } else if (lastAction && lastAction.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) { + } else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) { result.alternateText = `${lastActorDisplayName}: ${lastMessageText}`; } else { result.alternateText = lastAction && lastMessageTextFromReport.length > 0 ? lastMessageText : Localize.translate(preferredLocale, 'report.noActivityYet'); @@ -394,19 +467,23 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, // We also add a fullstop after the final name, the word "and" before the final name and commas between all previous names. lastMessageText = Localize.translate(preferredLocale, 'reportActionsView.beginningOfChatHistory') + - _.map(displayNamesWithTooltips, ({displayName, pronouns}, index) => { - const formattedText = _.isEmpty(pronouns) ? displayName : `${displayName} (${pronouns})`; - - if (index === displayNamesWithTooltips.length - 1) { - return `${formattedText}.`; - } - if (index === displayNamesWithTooltips.length - 2) { - return `${formattedText} ${Localize.translate(preferredLocale, 'common.and')}`; - } - if (index < displayNamesWithTooltips.length - 2) { - return `${formattedText},`; - } - }).join(' '); + displayNamesWithTooltips + .map(({displayName, pronouns}, index) => { + const formattedText = !pronouns ? displayName : `${displayName} (${pronouns})`; + + if (index === displayNamesWithTooltips.length - 1) { + return `${formattedText}.`; + } + if (index === displayNamesWithTooltips.length - 2) { + return `${formattedText} ${Localize.translate(preferredLocale, 'common.and')}`; + } + if (index < displayNamesWithTooltips.length - 2) { + return `${formattedText},`; + } + + return ''; + }) + .join(' '); } result.alternateText = lastMessageText || formattedLogin; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 44f8094ca13d..abd646fc19f1 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -396,9 +396,6 @@ function getAllReportTransactions(reportID?: string): Transaction[] { return transactions.filter((transaction) => `${transaction.reportID}` === `${reportID}`); } -/** - * Checks if a waypoint has a valid address - */ function waypointHasValidAddress(waypoint: RecentWaypoint | Waypoint): boolean { return !!waypoint?.address?.trim(); } diff --git a/src/libs/UnreadIndicatorUpdater/index.js b/src/libs/UnreadIndicatorUpdater/index.js index 09fa82612314..9acf1a17bbc6 100644 --- a/src/libs/UnreadIndicatorUpdater/index.js +++ b/src/libs/UnreadIndicatorUpdater/index.js @@ -3,12 +3,13 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; import updateUnread from './updateUnread/index'; import * as ReportUtils from '../ReportUtils'; +import CONST from '../../CONST'; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, callback: (reportsFromOnyx) => { - const unreadReports = _.filter(reportsFromOnyx, ReportUtils.isUnread); + const unreadReports = _.filter(reportsFromOnyx, (report) => ReportUtils.isUnread(report) && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); updateUnread(_.size(unreadReports)); }, }); diff --git a/src/libs/actions/Card.js b/src/libs/actions/Card.js index 92b23e2103ee..97c902876a3a 100644 --- a/src/libs/actions/Card.js +++ b/src/libs/actions/Card.js @@ -1,7 +1,7 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API'; - +import CONST from '../../CONST'; /** * @param {Number} cardID */ @@ -146,4 +146,29 @@ function clearCardListErrors(cardID) { Onyx.merge(ONYXKEYS.CARD_LIST, {[cardID]: {errors: null, isLoading: false}}); } -export {requestReplacementExpensifyCard, activatePhysicalExpensifyCard, clearCardListErrors, reportVirtualExpensifyCardFraud}; +/** + * Makes an API call to get virtual card details (pan, cvv, expiration date, address) + * This function purposefully uses `makeRequestWithSideEffects` method. For security reason + * card details cannot be persisted in Onyx and have to be asked for each time a user want's to + * reveal them. + * + * @param {String} cardID - virtual card ID + * + * @returns {Promise} - promise with card details object + */ +function revealVirtualCardDetails(cardID) { + return new Promise((resolve, reject) => { + // eslint-disable-next-line rulesdir/no-api-side-effects-method + API.makeRequestWithSideEffects('RevealVirtualCardDetails', {cardID}) + .then((response) => { + if (response.jsonCode !== CONST.JSON_CODE.SUCCESS) { + reject(); + return; + } + resolve(response); + }) + .catch(reject); + }); +} + +export {requestReplacementExpensifyCard, activatePhysicalExpensifyCard, clearCardListErrors, reportVirtualExpensifyCardFraud, revealVirtualCardDetails}; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index b4742c53066a..714ea7b9aa9f 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -525,8 +525,9 @@ function getMoneyRequestInformation( // 3. IOU action for the iouReport // 4. REPORTPREVIEW action for the chatReport // Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat + const currentTime = DateUtils.getDBTime(); const optimisticCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); - const optimisticCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); + const optimisticCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail, DateUtils.subtractMillisecondsFromDateTime(currentTime, 1)); const iouAction = ReportUtils.buildOptimisticIOUReportAction( CONST.IOU.REPORT_ACTION_TYPE.CREATE, amount, @@ -539,6 +540,8 @@ function getMoneyRequestInformation( false, false, receiptObject, + false, + currentTime, ); let reportPreviewAction = isNewIOUReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID); @@ -1122,8 +1125,9 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco // 3. IOU action for the iouReport // 4. REPORTPREVIEW action for the chatReport // Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat + const currentTime = DateUtils.getDBTime(); const oneOnOneCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); - const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); + const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit, DateUtils.subtractMillisecondsFromDateTime(currentTime, 1)); const oneOnOneIOUAction = ReportUtils.buildOptimisticIOUReportAction( CONST.IOU.REPORT_ACTION_TYPE.CREATE, splitAmount, @@ -1133,6 +1137,11 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco oneOnOneTransaction.transactionID, '', oneOnOneIOUReport.reportID, + undefined, + undefined, + undefined, + undefined, + currentTime, ); // Add optimistic personal details for new participants @@ -1809,6 +1818,8 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC optimisticPolicyRecentlyUsedTags[tagListName] = [transactionChanges.tag, ...uniquePolicyRecentlyUsedTags]; } + const isScanning = TransactionUtils.hasReceipt(updatedTransaction) && TransactionUtils.isReceiptBeingScanned(updatedTransaction); + // STEP 4: Compose the optimistic data const currentTime = DateUtils.getDBTime(); const optimisticData = [ @@ -1842,6 +1853,28 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC lastVisibleActionCreated: currentTime, }, }, + ...(!isScanning + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, + value: { + [transactionThread.parentReportActionID]: { + whisperedToAccountIDs: [], + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.parentReportID}`, + value: { + [iouReport.parentReportActionID]: { + whisperedToAccountIDs: [], + }, + }, + }, + ] + : []), ]; if (!_.isEmpty(optimisticPolicyRecentlyUsedTags)) { @@ -2863,7 +2896,7 @@ function setUpDistanceTransaction() { */ function navigateToNextPage(iou, iouType, report, path = '') { const moneyRequestID = `${iouType}${report.reportID || ''}`; - const shouldReset = !_.isEmpty(report.reportID) && iou.id !== moneyRequestID; + const shouldReset = iou.id !== moneyRequestID; // If the money request ID in Onyx does not match the ID from params, we want to start a new request // with the ID from params. We need to clear the participants in case the new request is initiated from FAB. diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 9044f43eabb9..0e063d3e0c48 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -33,6 +33,7 @@ Onyx.connect({ _.each(policyReports, ({reportID}) => { cleanUpMergeQueries[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] = {hasDraft: false}; cleanUpSetQueries[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] = null; + cleanUpSetQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}`] = null; }); Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, cleanUpMergeQueries); Onyx.multiSet(cleanUpSetQueries); @@ -116,6 +117,12 @@ function deleteWorkspace(policyID, reports, policyName) { }, })), + ..._.map(reports, ({reportID}) => ({ + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}`, + value: null, + })), + // Add closed actions to all chat reports linked to this policy ..._.map(reports, ({reportID, ownerAccountID}) => { // Announce & admin chats have FAKE owners, but workspace chats w/ users do have owners. @@ -583,6 +590,9 @@ function clearAvatarErrors(policyID) { * @param {String} currency */ function updateGeneralSettings(policyID, name, currency) { + const policy = allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + const distanceUnit = _.find(_.values(policy.customUnits), (unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceRate = _.find(_.values(distanceUnit ? distanceUnit.rates : {}), (rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); const optimisticData = [ { // We use SET because it's faster than merge and avoids a race condition when setting the currency and navigating the user to the Bank account page in confirmCurrencyChangeAndHideModal @@ -601,6 +611,21 @@ function updateGeneralSettings(policyID, name, currency) { }, name, outputCurrency: currency, + ...(distanceUnit + ? { + customUnits: { + [distanceUnit.customUnitID]: { + ...distanceUnit, + rates: { + [distanceRate.customUnitRateID]: { + ...distanceRate, + currency, + }, + }, + }, + }, + } + : {}), }, }, ]; @@ -626,6 +651,13 @@ function updateGeneralSettings(policyID, name, currency) { errorFields: { generalSettings: ErrorUtils.getMicroSecondOnyxError('workspace.editor.genericFailureMessage'), }, + ...(distanceUnit + ? { + customUnits: { + [distanceUnit.customUnitID]: distanceUnit, + }, + } + : {}), }, }, ]; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 29ebcaff121f..fcef11132283 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1149,14 +1149,17 @@ function deleteReportComment(reportID, reportAction) { }, ]; - // Update optimistic data for parent report action if the report is a child report - const optimisticParentReportData = ReportUtils.getOptimisticDataForParentReportAction( - originalReportID, - optimisticReport.lastVisibleActionCreated, - CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - ); - if (!_.isEmpty(optimisticParentReportData)) { - optimisticData.push(optimisticParentReportData); + // Update optimistic data for parent report action if the report is a child report and the reportAction has no visible child + const childVisibleActionCount = reportAction.childVisibleActionCount || 0; + if (childVisibleActionCount === 0) { + const optimisticParentReportData = ReportUtils.getOptimisticDataForParentReportAction( + originalReportID, + optimisticReport.lastVisibleActionCreated, + CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + ); + if (!_.isEmpty(optimisticParentReportData)) { + optimisticData.push(optimisticParentReportData); + } } // Check to see if the report action we are deleting is the first comment on a thread report. In this case, we need to trigger @@ -1339,7 +1342,7 @@ function editReportComment(reportID, originalReportAction, textForNewComment) { */ function saveReportActionDraft(reportID, reportAction, draftMessage) { const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); - Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}_${reportAction.reportActionID}`, draftMessage); + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, {[reportAction.reportActionID]: draftMessage}); } /** diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index c03335959e71..7015fa44da3a 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -99,6 +99,9 @@ function signOutAndRedirectToSignIn() { signOut(); redirectToSignIn(); } else { + if (Navigation.isActiveRoute(ROUTES.SIGN_IN_MODAL)) { + return; + } Navigation.navigate(ROUTES.SIGN_IN_MODAL); Linking.getInitialURL().then((url) => { const reportID = ReportUtils.getReportIDFromLink(url); diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index c0e794b85fd9..cc2b58f78b75 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -356,11 +356,9 @@ function reopenTask(taskReport) { /** * @param {object} report - * @param {Number} ownerAccountID * @param {Object} editedTask - * @param {Object} assigneeChatReport - The chat report between you and the assignee */ -function editTaskAndNavigate(report, ownerAccountID, {title, description, assignee = '', assigneeAccountID = 0}, assigneeChatReport = null) { +function editTaskAndNavigate(report, {title, description}) { // Create the EditedReportAction on the task const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); @@ -370,9 +368,6 @@ function editTaskAndNavigate(report, ownerAccountID, {title, description, assign // Description can be unset, so we default to an empty string if so const reportDescription = (!_.isUndefined(description) ? description : lodashGet(report, 'description', '')).trim(); - let assigneeChatReportOnyxData; - const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : 0; - const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -385,12 +380,9 @@ function editTaskAndNavigate(report, ownerAccountID, {title, description, assign value: { reportName, description: reportDescription, - managerID: assigneeAccountID || report.managerID, - managerEmail: assignee || report.managerEmail, pendingFields: { ...(title && {reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(description && {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), - ...(assigneeAccountID && {managerID: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }, }, }, @@ -403,7 +395,6 @@ function editTaskAndNavigate(report, ownerAccountID, {title, description, assign pendingFields: { ...(title && {reportName: null}), ...(description && {description: null}), - ...(assigneeAccountID && {managerID: null}), }, }, }, @@ -420,46 +411,17 @@ function editTaskAndNavigate(report, ownerAccountID, {title, description, assign value: { reportName: report.reportName, description: report.description, - assignee: report.managerEmail, - assigneeAccountID: report.managerID, }, }, ]; - // If we make a change to the assignee, we want to add a comment to the assignee's chat - // Check if the assignee actually changed - if (assigneeAccountID && assigneeAccountID !== report.managerID && assigneeAccountID !== ownerAccountID && assigneeChatReport) { - assigneeChatReportOnyxData = ReportUtils.getTaskAssigneeChatOnyxData( - currentUserAccountID, - assignee, - assigneeAccountID, - report.reportID, - assigneeChatReportID, - report.parentReportID, - reportName, - assigneeChatReport, - ); - optimisticData.push(...assigneeChatReportOnyxData.optimisticData); - successData.push(...assigneeChatReportOnyxData.successData); - failureData.push(...assigneeChatReportOnyxData.failureData); - } - API.write( 'EditTask', { taskReportID: report.reportID, title: reportName, description: reportDescription, - assignee: assignee || report.managerEmail, - assigneeAccountID: assigneeAccountID || report.managerID, editedTaskReportActionID: editTaskReportAction.reportActionID, - assigneeChatReportID, - assigneeChatReportActionID: - assigneeChatReportOnyxData && assigneeChatReportOnyxData.optimisticAssigneeAddComment - ? assigneeChatReportOnyxData.optimisticAssigneeAddComment.reportAction.reportActionID - : 0, - assigneeChatCreatedReportActionID: - assigneeChatReportOnyxData && assigneeChatReportOnyxData.optimisticChatCreatedReportAction ? assigneeChatReportOnyxData.optimisticChatCreatedReportAction.reportActionID : 0, }, {optimisticData, successData, failureData}, ); diff --git a/src/libs/convertToLTRForComposer/index.android.ts b/src/libs/convertToLTRForComposer/index.android.ts new file mode 100644 index 000000000000..09e7f2e5cd87 --- /dev/null +++ b/src/libs/convertToLTRForComposer/index.android.ts @@ -0,0 +1,8 @@ +import ConvertToLTRForComposer from './types'; + +/** + * Android only - Do not convert RTL text to a LTR text for input box using Unicode controls. + * Android does not properly support bidirectional text for mixed content for input box + */ +const convertToLTRForComposer: ConvertToLTRForComposer = (text) => text; +export default convertToLTRForComposer; diff --git a/src/libs/convertToLTRForComposer/index.ts b/src/libs/convertToLTRForComposer/index.ts new file mode 100644 index 000000000000..eb14bfa8c11a --- /dev/null +++ b/src/libs/convertToLTRForComposer/index.ts @@ -0,0 +1,34 @@ +import CONST from '../../CONST'; +import ConvertToLTRForComposer from './types'; + +function hasLTRorRTLCharacters(text: string): boolean { + // Regular expressions to match LTR and RTL character ranges. + // eslint-disable-next-line no-control-regex + const ltrPattern = /[\u0001-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/; + const rtlPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/; + + return ltrPattern.test(text) || rtlPattern.test(text); +} + +// Converts a given text to ensure it starts with the LTR (Left-to-Right) marker. +const convertToLTRForComposer: ConvertToLTRForComposer = (text) => { + // Ensure the text contains LTR or RTL characters to avoid an unwanted special character at the beginning, even after a backspace deletion. + if (!hasLTRorRTLCharacters(text)) { + return ''; + } + + // Check if the text contains only spaces. If it does, we do not concatenate it with CONST.UNICODE.LTR, + // as doing so would alter the normal behavior of the input box. + if (/^\s*$/.test(text)) { + return text; + } + + // Check if the text already starts with the LTR marker (if so, return as is). + if (text.startsWith(CONST.UNICODE.LTR)) { + return text; + } + + // Add the LTR marker to the beginning of the text. + return `${CONST.UNICODE.LTR}${text}`; +}; +export default convertToLTRForComposer; diff --git a/src/libs/convertToLTRForComposer/types.ts b/src/libs/convertToLTRForComposer/types.ts new file mode 100644 index 000000000000..c6edeaaba446 --- /dev/null +++ b/src/libs/convertToLTRForComposer/types.ts @@ -0,0 +1,3 @@ +type ConvertToLTRForComposer = (text: string) => string; + +export default ConvertToLTRForComposer; diff --git a/src/libs/migrateOnyx.js b/src/libs/migrateOnyx.js index aaa706e71fb2..2ac9f0d92d91 100644 --- a/src/libs/migrateOnyx.js +++ b/src/libs/migrateOnyx.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import Log from './Log'; import PersonalDetailsByAccountID from './migrations/PersonalDetailsByAccountID'; import RenameReceiptFilename from './migrations/RenameReceiptFilename'; +import KeyReportActionsDraftByReportActionID from './migrations/KeyReportActionsDraftByReportActionID'; export default function () { const startTime = Date.now(); @@ -9,7 +10,7 @@ export default function () { return new Promise((resolve) => { // Add all migrations to an array so they are executed in order - const migrationPromises = [PersonalDetailsByAccountID, RenameReceiptFilename]; + const migrationPromises = [PersonalDetailsByAccountID, RenameReceiptFilename, KeyReportActionsDraftByReportActionID]; // Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the // previous promise to finish before moving onto the next one. diff --git a/src/libs/migrations/KeyReportActionsDraftByReportActionID.js b/src/libs/migrations/KeyReportActionsDraftByReportActionID.js new file mode 100644 index 000000000000..63282b8743dc --- /dev/null +++ b/src/libs/migrations/KeyReportActionsDraftByReportActionID.js @@ -0,0 +1,60 @@ +import _ from 'underscore'; +import Onyx from 'react-native-onyx'; +import Log from '../Log'; +import ONYXKEYS from '../../ONYXKEYS'; + +/** + * This migration updates reportActionsDrafts data to be keyed by reportActionID. + * + * Before: reportActionsDrafts_reportID_reportActionID: value + * After: reportActionsDrafts_reportID: {[reportActionID]: value} + * + * @returns {Promise} + */ +export default function () { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS, + waitForCollectionCallback: true, + callback: (allReportActionsDrafts) => { + Onyx.disconnect(connectionID); + + if (!allReportActionsDrafts) { + Log.info('[Migrate Onyx] Skipped migration KeyReportActionsDraftByReportActionID because there were no reportActionsDrafts'); + return resolve(); + } + + const newReportActionsDrafts = {}; + _.each(allReportActionsDrafts, (reportActionDraft, onyxKey) => { + if (!_.isString(reportActionDraft)) { + return; + } + newReportActionsDrafts[onyxKey] = null; + + if (_.isEmpty(reportActionDraft)) { + return; + } + + const reportActionID = onyxKey.split('_').pop(); + const newOnyxKey = onyxKey.replace(`_${reportActionID}`, ''); + + // If newReportActionsDrafts[newOnyxKey] isn't set, fall back on the migrated draft if there is one + const currentActionsDrafts = newReportActionsDrafts[newOnyxKey] || allReportActionsDrafts[newOnyxKey]; + newReportActionsDrafts[newOnyxKey] = { + ...currentActionsDrafts, + [reportActionID]: reportActionDraft, + }; + }); + + if (_.isEmpty(newReportActionsDrafts)) { + Log.info('[Migrate Onyx] Skipped migration KeyReportActionsDraftByReportActionID because there are no actions drafts to migrate'); + return resolve(); + } + + Log.info(`[Migrate Onyx] Re-keying reportActionsDrafts by reportActionID for ${_.keys(newReportActionsDrafts).length} actions drafts`); + // eslint-disable-next-line rulesdir/prefer-actions-set-data + return Onyx.multiSet(newReportActionsDrafts).then(resolve); + }, + }); + }); +} diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index f039afeb085f..eedd5bcd10d4 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -104,7 +104,7 @@ function EditRequestPage({betas, report, route, parentReport, policyCategories, const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); // A flag for showing the categories page - const shouldShowCategories = isPolicyExpenseChat && Permissions.canUseCategories(betas) && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); + const shouldShowCategories = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); // A flag for showing the tags page const shouldShowTags = isPolicyExpenseChat && Permissions.canUseTags(betas) && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagList))); diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.js b/src/pages/PrivateNotes/PrivateNotesEditPage.js index d8b91c27c501..962163d3e379 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.js +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.js @@ -17,7 +17,6 @@ import ONYXKEYS from '../../ONYXKEYS'; import TextInput from '../../components/TextInput'; import CONST from '../../CONST'; import Text from '../../components/Text'; -import Form from '../../components/Form'; import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView'; import reportPropTypes from '../reportPropTypes'; import personalDetailsPropType from '../personalDetailsPropType'; @@ -27,6 +26,8 @@ import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import updateMultilineInputRange from '../../libs/UpdateMultilineInputRange'; import ROUTES from '../../ROUTES'; import * as ReportUtils from '../../libs/ReportUtils'; +import InputWrapper from '../../components/Form/InputWrapper'; +import FormProvider from '../../components/Form/FormProvider'; const propTypes = { /** All of the personal details for everyone */ @@ -135,7 +136,7 @@ function PrivateNotesEditPage({route, personalDetailsList, session, report}) { shouldShowBackButton onCloseButtonPress={() => Navigation.dismissModal()} /> -
Report.clearPrivateNotesError(report.reportID, route.params.accountID)} style={[styles.mb3]} > - - + ); diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js index f3b683094043..d6a6b79d3273 100644 --- a/src/pages/ShareCodePage.js +++ b/src/pages/ShareCodePage.js @@ -1,6 +1,7 @@ import React from 'react'; import {View, ScrollView} from 'react-native'; import _ from 'underscore'; +import PropTypes from 'prop-types'; import ScreenWrapper from '../components/ScreenWrapper'; import HeaderWithBackButton from '../components/HeaderWithBackButton'; import Navigation from '../libs/Navigation/Navigation'; @@ -20,16 +21,18 @@ import CONST from '../CONST'; import ContextMenuItem from '../components/ContextMenuItem'; import * as UserUtils from '../libs/UserUtils'; import ROUTES from '../ROUTES'; -import withEnvironment, {environmentPropTypes} from '../components/withEnvironment'; +import withEnvironment from '../components/withEnvironment'; import * as Url from '../libs/Url'; const propTypes = { /** The report currently being looked at */ report: reportPropTypes, + /** The string value representing the URL of the current environment */ + environmentURL: PropTypes.string.isRequired, + ...withLocalizePropTypes, ...withCurrentUserPersonalDetailsPropTypes, - ...environmentPropTypes, }; const defaultProps = { diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index e88f6cd0b756..5b1a7f897c3e 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, {memo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -271,21 +271,23 @@ HeaderView.propTypes = propTypes; HeaderView.displayName = 'HeaderView'; HeaderView.defaultProps = defaultProps; -export default compose( - withWindowDimensions, - withLocalize, - withOnyx({ - guideCalendarLink: { - key: ONYXKEYS.ACCOUNT, - selector: (account) => (account && account.guideCalendarLink) || null, - initialValue: null, - }, - parentReport: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || report.reportID}`, - selector: reportWithoutHasDraftSelector, - }, - session: { - key: ONYXKEYS.SESSION, - }, - }), -)(HeaderView); +export default memo( + compose( + withWindowDimensions, + withLocalize, + withOnyx({ + guideCalendarLink: { + key: ONYXKEYS.ACCOUNT, + selector: (account) => (account && account.guideCalendarLink) || null, + initialValue: null, + }, + parentReport: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || report.reportID}`, + selector: reportWithoutHasDraftSelector, + }, + session: { + key: ONYXKEYS.SESSION, + }, + }), + )(HeaderView), +); diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 1b6dd9186453..f7ab1767aade 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -182,10 +182,14 @@ function ReportScreen({ const isTopMostReportId = currentReportID === getReportID(route); const didSubscribeToReportLeavingEvents = useRef(false); + const goBack = useCallback(() => { + Navigation.goBack(ROUTES.HOME, false, true); + }, []); + let headerView = ( Navigation.goBack(ROUTES.HOME, false, true)} + onNavigationMenuButtonClicked={goBack} personalDetails={personalDetails} report={report} /> diff --git a/src/pages/home/ReportScreenContext.js b/src/pages/home/ReportScreenContext.js deleted file mode 100644 index 1e8d30cf7585..000000000000 --- a/src/pages/home/ReportScreenContext.js +++ /dev/null @@ -1,6 +0,0 @@ -import {createContext} from 'react'; - -const ActionListContext = createContext(); -const ReactionListContext = createContext(); - -export {ActionListContext, ReactionListContext}; diff --git a/src/pages/home/ReportScreenContext.ts b/src/pages/home/ReportScreenContext.ts new file mode 100644 index 000000000000..83f76d8d8e2f --- /dev/null +++ b/src/pages/home/ReportScreenContext.ts @@ -0,0 +1,17 @@ +import {RefObject, createContext} from 'react'; +import {FlatList, GestureResponderEvent} from 'react-native'; + +type ReactionListRef = { + showReactionList: (event: GestureResponderEvent | undefined, reactionListAnchor: Element, emojiName: string, reportActionID: string) => void; + hideReactionList: () => void; + isActiveReportAction: (actionID: number | string) => boolean; +}; + +type ActionListContextType = RefObject> | null; +type ReactionListContextType = RefObject | null; + +const ActionListContext = createContext(null); +const ReactionListContext = createContext(null); + +export {ActionListContext, ReactionListContext}; +export type {ReactionListRef, ActionListContextType, ReactionListContextType}; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 51e460fc365b..ec2f08df502d 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -128,7 +128,9 @@ export default [ const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT; const isReportPreviewAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW; const isIOUAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !ReportActionsUtils.isSplitBillAction(reportAction); - return (isCommentAction || isReportPreviewAction || isIOUAction) && !ReportUtils.isThreadFirstChat(reportAction, reportID); + const isModifiedExpenseAction = ReportActionsUtils.isModifiedExpenseAction(reportAction); + const isTaskAction = ReportActionsUtils.isTaskAction(reportAction); + return (isCommentAction || isReportPreviewAction || isIOUAction || isModifiedExpenseAction || isTaskAction) && !ReportUtils.isThreadFirstChat(reportAction, reportID); }, onPress: (closePopover, {reportAction, reportID}) => { if (closePopover) { diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index e987eff4c7e8..d342fc225d63 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -238,7 +238,7 @@ function PopoverReportActionContextMenu(_props, ref) { Report.deleteReportComment(reportIDRef.current, reportActionRef.current); } setIsDeleteCommentConfirmModalVisible(false); - }, [reportActionRef]); + }, []); const hideDeleteModal = () => { callbackWhenDeleteModalHide.current = () => (onCancelDeleteModal.current = runAndResetCallback(onCancelDeleteModal.current)); diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index fcb49277ef5b..d62674eb6fda 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -36,6 +36,7 @@ import focusWithDelay from '../../../../libs/focusWithDelay'; import useDebounce from '../../../../hooks/useDebounce'; import updateMultilineInputRange from '../../../../libs/UpdateMultilineInputRange'; import * as InputFocus from '../../../../libs/actions/InputFocus'; +import convertToLTRForComposer from '../../../../libs/convertToLTRForComposer'; const {RNTextInputReset} = NativeModules; @@ -213,7 +214,6 @@ function ComposerWithSuggestions({ (commentValue, shouldDebounceSaveComment) => { raiseIsScrollLikelyLayoutTriggered(); const {text: newComment, emojis} = EmojiUtils.replaceAndExtractEmojis(commentValue, preferredSkinTone, preferredLocale); - if (!_.isEmpty(emojis)) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (!_.isEmpty(newEmojis)) { @@ -225,9 +225,10 @@ function ComposerWithSuggestions({ debouncedUpdateFrequentlyUsedEmojis(); } } + const newCommentConverted = convertToLTRForComposer(newComment); emojisPresentBefore.current = emojis; - setIsCommentEmpty(!!newComment.match(/^(\s)*$/)); - setValue(newComment); + setIsCommentEmpty(!!newCommentConverted.match(/^(\s)*$/)); + setValue(newCommentConverted); if (commentValue !== newComment) { const remainder = ComposerUtils.getCommonSuffixLength(commentValue, newComment); setSelection({ @@ -237,22 +238,22 @@ function ComposerWithSuggestions({ } // Indicate that draft has been created. - if (commentRef.current.length === 0 && newComment.length !== 0) { + if (commentRef.current.length === 0 && newCommentConverted.length !== 0) { Report.setReportWithDraft(reportID, true); } // The draft has been deleted. - if (newComment.length === 0) { + if (newCommentConverted.length === 0) { Report.setReportWithDraft(reportID, false); } - commentRef.current = newComment; + commentRef.current = newCommentConverted; if (shouldDebounceSaveComment) { - debouncedSaveReportComment(reportID, newComment); + debouncedSaveReportComment(reportID, newCommentConverted); } else { - Report.saveReportComment(reportID, newComment || ''); + Report.saveReportComment(reportID, newCommentConverted || ''); } - if (newComment) { + if (newCommentConverted) { debouncedBroadcastUserIsTyping(reportID); } }, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index dd4d51653546..891f7fbc903b 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -342,120 +342,122 @@ function ReportActionCompose({ }, [isSendDisabled, resetFullComposerSize, submitForm, animatedRef, isReportReadyForDisplay]); return ( - - - + + {shouldShowReportRecipientLocalTime && hasReportRecipient && } - + + + - setIsAttachmentPreviewActive(true)} - onModalHide={onAttachmentPreviewClose} + - {({displayFileInModal}) => ( - <> - - - { - if (isAttachmentPreviewActive) { - return; - } - const data = lodashGet(e, ['dataTransfer', 'items', 0]); - displayFileInModal(data); - }} - /> - + setIsAttachmentPreviewActive(true)} + onModalHide={onAttachmentPreviewClose} + > + {({displayFileInModal}) => ( + <> + + + { + if (isAttachmentPreviewActive) { + return; + } + const data = lodashGet(e, ['dataTransfer', 'items', 0]); + displayFileInModal(data); + }} + /> + + )} + + {DeviceCapabilities.canUseTouchScreen() && isMediumScreenWidth ? null : ( + composerRef.current.replaceSelectionWithText(...args)} + emojiPickerID={report.reportID} + /> )} - - {DeviceCapabilities.canUseTouchScreen() && isMediumScreenWidth ? null : ( - composerRef.current.replaceSelectionWithText(...args)} - emojiPickerID={report.reportID} + - )} - - - - {!isSmallScreenWidth && } - - - - + + + {!isSmallScreenWidth && } + + + + + ); } diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater.js b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater.js index 09f9d368bdcc..4347d6bde8b3 100644 --- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater.js +++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater.js @@ -44,6 +44,11 @@ function SilentCommentUpdater({comment, commentRef, report, value, updateComment const {preferredLocale} = useLocalize(); const prevPreferredLocale = usePrevious(preferredLocale); + useEffect(() => { + updateComment(comment); + // eslint-disable-next-line react-hooks/exhaustive-deps -- We need to run this on mount + }, []); + useEffect(() => { // Value state does not have the same value as comment props when the comment gets changed from another tab. // In this case, we should synchronize the value between tabs. diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 3afdb437a49a..d44c7b8ee4d1 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -725,8 +725,8 @@ export default compose( propName: 'draftMessage', transformValue: (drafts, props) => { const originalReportID = ReportUtils.getOriginalReportID(props.report.reportID, props.action); - const draftKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}_${props.action.reportActionID}`; - return lodashGet(drafts, draftKey, ''); + const draftKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`; + return lodashGet(drafts, [draftKey, props.action.reportActionID], ''); }, }), withOnyx({ diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 4b73ce3b21db..be2f10f2f053 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -269,6 +269,11 @@ function ReportActionItemMessageEdit(props) { [props.action.message, debouncedSaveDraft, debouncedUpdateFrequentlyUsedEmojis, props.preferredSkinTone, preferredLocale], ); + useEffect(() => { + updateDraft(draft); + // eslint-disable-next-line react-hooks/exhaustive-deps -- run this only when language is changed + }, [props.action.reportActionID, preferredLocale]); + /** * Delete the draft of the comment being edited. This will take the comment out of "edit mode" with the old content. */ @@ -286,7 +291,7 @@ function ReportActionItemMessageEdit(props) { // Scroll to the last comment after editing to make sure the whole comment is clearly visible in the report. if (props.index === 0) { const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { - reportScrollManager.scrollToIndex({animated: true, index: props.index}, false); + reportScrollManager.scrollToIndex(props.index, false); keyboardDidHideListener.remove(); }); } @@ -401,7 +406,7 @@ function ReportActionItemMessageEdit(props) { style={[styles.textInputCompose, styles.flex1, styles.bgTransparent]} onFocus={() => { setIsFocused(true); - reportScrollManager.scrollToIndex({animated: true, index: props.index}, true); + reportScrollManager.scrollToIndex(props.index, true); setShouldShowComposeInputKeyboardAware(false); // Clear active report action when another action gets focused diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index fc189a3aef36..fc38b102b8e8 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -122,7 +122,10 @@ function ReportActionItemSingle(props) { id: secondaryAccountId, }; } else if (!isWorkspaceActor) { - secondaryAvatar = ReportUtils.getIcons(props.report, {})[props.report.isOwnPolicyExpenseChat ? 0 : 1]; + const avatarIconIndex = props.report.isOwnPolicyExpenseChat || ReportUtils.isPolicyExpenseChat(props.report) ? 0 : 1; + const reportIcons = ReportUtils.getIcons(props.report, {}); + + secondaryAvatar = reportIcons[avatarIconIndex]; } const icon = {source: avatarSource, type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR, name: primaryDisplayName, id: isWorkspaceActor ? '' : actorAccountID}; diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 8111cfa4b644..108e75051696 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -20,6 +20,7 @@ import reportPropTypes from '../../reportPropTypes'; import PopoverReactionList from './ReactionList/PopoverReactionList'; import getIsReportFullyVisible from '../../../libs/getIsReportFullyVisible'; import {ReactionListContext} from '../ReportScreenContext'; +import useInitialValue from '../../../hooks/useInitialValue'; const propTypes = { /** The report currently being looked at */ @@ -71,9 +72,9 @@ function ReportActionsView(props) { const didLayout = useRef(false); const didSubscribeToReportTypingEvents = useRef(false); const isFirstRender = useRef(true); - const hasCachedActions = useRef(_.size(props.reportActions) > 0); + const hasCachedActions = useInitialValue(() => _.size(props.reportActions) > 0); + const mostRecentIOUReportActionID = useInitialValue(() => ReportActionsUtils.getMostRecentIOURequestActionID(props.reportActions)); - const mostRecentIOUReportActionID = useRef(ReportActionsUtils.getMostRecentIOURequestActionID(props.reportActions)); const prevNetworkRef = useRef(props.network); const prevIsSmallScreenWidthRef = useRef(props.isSmallScreenWidth); @@ -204,7 +205,7 @@ function ReportActionsView(props) { } didLayout.current = true; - Timing.end(CONST.TIMING.SWITCH_REPORT, hasCachedActions.current ? CONST.TIMING.WARM : CONST.TIMING.COLD); + Timing.end(CONST.TIMING.SWITCH_REPORT, hasCachedActions ? CONST.TIMING.WARM : CONST.TIMING.COLD); // Capture the init measurement only once not per each chat switch as the value gets overwritten if (!ReportActionsView.initMeasured) { @@ -226,7 +227,7 @@ function ReportActionsView(props) { report={props.report} onLayout={recordTimeToMeasureItemLayout} sortedReportActions={props.reportActions} - mostRecentIOUReportActionID={mostRecentIOUReportActionID.current} + mostRecentIOUReportActionID={mostRecentIOUReportActionID} loadOlderChats={loadOlderChats} loadNewerChats={loadNewerChats} isLoadingInitialReportActions={props.isLoadingInitialReportActions} diff --git a/src/pages/home/report/withReportOrNotFound.js b/src/pages/home/report/withReportOrNotFound.js deleted file mode 100644 index 43f3caa645e7..000000000000 --- a/src/pages/home/report/withReportOrNotFound.js +++ /dev/null @@ -1,127 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import getComponentDisplayName from '../../../libs/getComponentDisplayName'; -import NotFoundPage from '../../ErrorPage/NotFoundPage'; -import ONYXKEYS from '../../../ONYXKEYS'; -import reportPropTypes from '../../reportPropTypes'; -import FullscreenLoadingIndicator from '../../../components/FullscreenLoadingIndicator'; -import * as ReportUtils from '../../../libs/ReportUtils'; - -export default function (shouldRequireReportID = true) { - const propTypes = { - /** The HOC takes an optional ref as a prop and passes it as a ref to the wrapped component. - * That way, if a ref is passed to a component wrapped in the HOC, the ref is a reference to the wrapped component, not the HOC. */ - forwardedRef: PropTypes.func, - - /** The report currently being looked at */ - report: reportPropTypes, - - /** The policies which the user has access to */ - policies: PropTypes.objectOf( - PropTypes.shape({ - /** The policy name */ - name: PropTypes.string, - - /** The type of the policy */ - type: PropTypes.string, - }), - ), - - /** Route params */ - route: PropTypes.shape({ - params: PropTypes.shape({ - /** Report ID passed via route */ - reportID: PropTypes.string, - }), - }).isRequired, - - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - - /** Indicated whether the report data is loading */ - isLoadingReportData: PropTypes.bool, - }; - - const defaultProps = { - forwardedRef: () => {}, - report: {}, - policies: {}, - betas: [], - isLoadingReportData: true, - }; - - return (WrappedComponent) => { - // eslint-disable-next-line rulesdir/no-negated-variables - function WithReportOrNotFound(props) { - const contentShown = React.useRef(false); - - const isReportIdInRoute = !_.isUndefined(props.route.params.reportID); - - // If we should require reportID or we have a reportID in the route, we will check the reportID is valid or not - if (shouldRequireReportID || isReportIdInRoute) { - const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData && (_.isEmpty(props.report) || !props.report.reportID); - // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = _.isEmpty(props.report) || !props.report.reportID || !ReportUtils.canAccessReport(props.report, props.policies, props.betas); - - // If the content was shown but it's not anymore that means the report was deleted and we are probably navigating out of this screen. - // Return null for this case to avoid rendering FullScreenLoadingIndicator or NotFoundPage when animating transition. - if (shouldShowNotFoundPage && contentShown.current) { - return null; - } - - if (shouldShowFullScreenLoadingIndicator) { - return ; - } - - if (shouldShowNotFoundPage) { - return ; - } - } - - if (!contentShown.current) { - contentShown.current = true; - } - - const rest = _.omit(props, ['forwardedRef']); - return ( - - ); - } - - WithReportOrNotFound.propTypes = propTypes; - WithReportOrNotFound.defaultProps = defaultProps; - WithReportOrNotFound.displayName = `withReportOrNotFound(${getComponentDisplayName(WrappedComponent)})`; - - // eslint-disable-next-line rulesdir/no-negated-variables - const WithReportOrNotFoundWithRef = React.forwardRef((props, ref) => ( - - )); - - WithReportOrNotFoundWithRef.displayName = 'WithReportOrNotFoundWithRef'; - - return withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, - }, - isLoadingReportData: { - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - }, - betas: { - key: ONYXKEYS.BETAS, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - })(WithReportOrNotFoundWithRef); - }; -} diff --git a/src/pages/home/report/withReportOrNotFound.tsx b/src/pages/home/report/withReportOrNotFound.tsx new file mode 100644 index 000000000000..28d6707b085f --- /dev/null +++ b/src/pages/home/report/withReportOrNotFound.tsx @@ -0,0 +1,89 @@ +/* eslint-disable rulesdir/no-negated-variables */ +import React, {ComponentType, ForwardedRef, RefAttributes} from 'react'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import {RouteProp} from '@react-navigation/native'; +import getComponentDisplayName from '../../../libs/getComponentDisplayName'; +import NotFoundPage from '../../ErrorPage/NotFoundPage'; +import ONYXKEYS from '../../../ONYXKEYS'; +import FullscreenLoadingIndicator from '../../../components/FullscreenLoadingIndicator'; +import * as ReportUtils from '../../../libs/ReportUtils'; +import * as OnyxTypes from '../../../types/onyx'; + +type OnyxProps = { + /** The report currently being looked at */ + report: OnyxEntry; + /** The policies which the user has access to */ + policies: OnyxEntry; + /** Beta features list */ + betas: OnyxEntry; + /** Indicated whether the report data is loading */ + isLoadingReportData: OnyxEntry; +}; + +type ComponentProps = OnyxProps & { + route: RouteProp<{params: {reportID: string}}>; +}; + +export default function ( + shouldRequireReportID = true, +): ( + WrappedComponent: React.ComponentType>, +) => React.ComponentType, keyof OnyxProps>> { + return function (WrappedComponent: ComponentType>) { + function WithReportOrNotFound(props: TProps, ref: ForwardedRef) { + const contentShown = React.useRef(false); + + const isReportIdInRoute = props.route.params.reportID?.length; + + if (shouldRequireReportID || isReportIdInRoute) { + const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData && (!Object.entries(props.report ?? {}).length || !props.report?.reportID); + + const shouldShowNotFoundPage = + !Object.entries(props.report ?? {}).length || !props.report?.reportID || !ReportUtils.canAccessReport(props.report, props.policies, props.betas, {}); + + // If the content was shown but it's not anymore that means the report was deleted and we are probably navigating out of this screen. + // Return null for this case to avoid rendering FullScreenLoadingIndicator or NotFoundPage when animating transition. + if (shouldShowNotFoundPage && contentShown.current) { + return null; + } + + if (shouldShowFullScreenLoadingIndicator) { + return ; + } + + if (shouldShowNotFoundPage) { + return ; + } + } + + if (!contentShown.current) { + contentShown.current = true; + } + + return ( + + ); + } + + WithReportOrNotFound.displayName = `withReportOrNotFound(${getComponentDisplayName(WrappedComponent)})`; + + return withOnyx, OnyxProps>({ + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, + }, + isLoadingReportData: { + key: ONYXKEYS.IS_LOADING_REPORT_DATA, + }, + betas: { + key: ONYXKEYS.BETAS, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + })(React.forwardRef(WithReportOrNotFound)); + }; +} diff --git a/src/pages/iou/ReceiptSelector/NavigationAwareCamera.js b/src/pages/iou/ReceiptSelector/NavigationAwareCamera.js index 2b4ef44dfd8d..e9cb81003979 100644 --- a/src/pages/iou/ReceiptSelector/NavigationAwareCamera.js +++ b/src/pages/iou/ReceiptSelector/NavigationAwareCamera.js @@ -72,14 +72,4 @@ NavigationAwareCamera.propTypes = propTypes; NavigationAwareCamera.displayName = 'NavigationAwareCamera'; NavigationAwareCamera.defaultProps = defaultProps; -const NavigationAwareCameraWithRef = React.forwardRef((props, ref) => ( - -)); - -NavigationAwareCameraWithRef.displayName = 'NavigationAwareCameraWithRef'; - -export default NavigationAwareCameraWithRef; +export default NavigationAwareCamera; diff --git a/src/pages/iou/ReceiptSelector/NavigationAwareCamera.native.js b/src/pages/iou/ReceiptSelector/NavigationAwareCamera.native.js index 0569a8236140..69678622aae5 100644 --- a/src/pages/iou/ReceiptSelector/NavigationAwareCamera.native.js +++ b/src/pages/iou/ReceiptSelector/NavigationAwareCamera.native.js @@ -80,14 +80,4 @@ const NavigationAwareCamera = React.forwardRef(({cameraTabIndex, isInTabNavigato NavigationAwareCamera.propTypes = propTypes; NavigationAwareCamera.displayName = 'NavigationAwareCamera'; -const NavigationAwareCameraWithRef = React.forwardRef((props, ref) => ( - -)); - -NavigationAwareCameraWithRef.displayName = 'NavigationAwareCameraWithRef'; - -export default NavigationAwareCameraWithRef; +export default NavigationAwareCamera; diff --git a/src/pages/iou/WaypointEditor.js b/src/pages/iou/WaypointEditor.js index a123976b326e..03ce3cba28bd 100644 --- a/src/pages/iou/WaypointEditor.js +++ b/src/pages/iou/WaypointEditor.js @@ -45,6 +45,9 @@ const propTypes = { recentWaypoints: PropTypes.arrayOf( PropTypes.shape({ + /** The name of the location */ + name: PropTypes.string, + /** A description of the location (usually the address) */ description: PropTypes.string, @@ -163,6 +166,7 @@ function WaypointEditor({route: {params: {iouType = '', transactionID = '', wayp lat: values.lat, lng: values.lng, address: values.address, + name: values.name, }; Transaction.saveWaypoint(transactionID, waypointIndex, waypoint, isEditingWaypoint); @@ -264,6 +268,7 @@ export default withOnyx({ // that the google autocomplete component expects for it's "predefined places" feature. selector: (waypoints) => _.map(waypoints ? waypoints.slice(0, 5) : [], (waypoint) => ({ + name: waypoint.name, description: waypoint.address, geometry: { location: { diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.js index 55e76727e500..0a7b9d917464 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.js +++ b/src/pages/iou/steps/MoneyRequestAmountForm.js @@ -136,10 +136,17 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu if (!_.isEmpty(formError)) { setFormError(''); } + + // setCurrentAmount contains another setState(setSelection) making it error-prone since it is leading to setSelection being called twice for a single setCurrentAmount call. This solution introducing the hasSelectionBeenSet flag was chosen for its simplicity and lower risk of future errors https://github.com/Expensify/App/issues/23300#issuecomment-1766314724. + + let hasSelectionBeenSet = false; setCurrentAmount((prevAmount) => { const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces); const isForwardDelete = prevAmount.length > strippedAmount.length && forwardDeletePressedRef.current; - setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); + if (!hasSelectionBeenSet) { + hasSelectionBeenSet = true; + setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); + } return strippedAmount; }); }, @@ -283,7 +290,8 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu ) : null}