diff --git a/.eslintignore b/.eslintignore index d3e8a6328bc4..396bfd28c614 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ **/node_modules/* **/dist/* .github/actions/**/index.js" +docs/vendor/** diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c106a25c8ab1..4f78ee6b69bf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,2 @@ # Every PR gets a review from an internal Expensify engineer * @Expensify/pullerbear - -# Every PR that touches redirects gets reviewed by ring0 -docs/redirects.csv @Expensify/infra \ No newline at end of file diff --git a/.github/actions/composite/setupGitForOSBotifyApp/action.yml b/.github/actions/composite/setupGitForOSBotifyApp/action.yml index 52fb097d254e..94ea94d27505 100644 --- a/.github/actions/composite/setupGitForOSBotifyApp/action.yml +++ b/.github/actions/composite/setupGitForOSBotifyApp/action.yml @@ -33,7 +33,7 @@ runs: fi - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 if: steps.key_check.outputs.key_exists != 'true' with: sparse-checkout: | diff --git a/.github/actions/composite/setupNode/action.yml b/.github/actions/composite/setupNode/action.yml index 57c7e4d91379..7e1b5fbbae90 100644 --- a/.github/actions/composite/setupNode/action.yml +++ b/.github/actions/composite/setupNode/action.yml @@ -4,7 +4,7 @@ description: Set up Node runs: using: composite steps: - - uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: npm diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js index abf0712103a5..b7ab57e68974 100644 --- a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js @@ -194,20 +194,38 @@ const {getPreviousVersion, SEMANTIC_VERSION_LEVELS} = __nccwpck_require__(8007); */ function fetchTag(tag) { const previousPatchVersion = getPreviousVersion(tag, SEMANTIC_VERSION_LEVELS.PATCH); - try { - let command = `git fetch origin tag ${tag} --no-tags`; + let shouldRetry = true; + let needsRepack = false; + while (shouldRetry) { + try { + if (needsRepack) { + // We have seen some scenarios where this fixes the git fetch. + // Why? Who knows... https://github.com/Expensify/App/pull/31459 + execSync('git repack -d'); + } - // Exclude commits reachable from the previous patch version (i.e: previous checklist), - // so that we don't have to fetch the full history - // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case - if (previousPatchVersion !== tag) { - command += ` --shallow-exclude=${previousPatchVersion}`; - } + let command = `git fetch origin tag ${tag} --no-tags`; - console.log(`Running command: ${command}`); - execSync(command); - } catch (e) { - console.error(e); + // Exclude commits reachable from the previous patch version (i.e: previous checklist), + // so that we don't have to fetch the full history + // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case + if (previousPatchVersion !== tag) { + command += ` --shallow-exclude=${previousPatchVersion}`; + } + + console.log(`Running command: ${command}`); + execSync(command); + shouldRetry = false; + } catch (e) { + console.error(e); + if (!needsRepack) { + console.log('Attempting to repack and retry...'); + needsRepack = true; + } else { + console.error("Repack didn't help, giving up..."); + shouldRetry = false; + } + } } } diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js index dbe109d99e32..1217c5e97de4 100644 --- a/.github/actions/javascript/getDeployPullRequestList/index.js +++ b/.github/actions/javascript/getDeployPullRequestList/index.js @@ -136,20 +136,38 @@ const {getPreviousVersion, SEMANTIC_VERSION_LEVELS} = __nccwpck_require__(8007); */ function fetchTag(tag) { const previousPatchVersion = getPreviousVersion(tag, SEMANTIC_VERSION_LEVELS.PATCH); - try { - let command = `git fetch origin tag ${tag} --no-tags`; + let shouldRetry = true; + let needsRepack = false; + while (shouldRetry) { + try { + if (needsRepack) { + // We have seen some scenarios where this fixes the git fetch. + // Why? Who knows... https://github.com/Expensify/App/pull/31459 + execSync('git repack -d'); + } - // Exclude commits reachable from the previous patch version (i.e: previous checklist), - // so that we don't have to fetch the full history - // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case - if (previousPatchVersion !== tag) { - command += ` --shallow-exclude=${previousPatchVersion}`; - } + let command = `git fetch origin tag ${tag} --no-tags`; - console.log(`Running command: ${command}`); - execSync(command); - } catch (e) { - console.error(e); + // Exclude commits reachable from the previous patch version (i.e: previous checklist), + // so that we don't have to fetch the full history + // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case + if (previousPatchVersion !== tag) { + command += ` --shallow-exclude=${previousPatchVersion}`; + } + + console.log(`Running command: ${command}`); + execSync(command); + shouldRetry = false; + } catch (e) { + console.error(e); + if (!needsRepack) { + console.log('Attempting to repack and retry...'); + needsRepack = true; + } else { + console.error("Repack didn't help, giving up..."); + shouldRetry = false; + } + } } } diff --git a/.github/libs/GitUtils.js b/.github/libs/GitUtils.js index 7bc600470dd1..42a7ecff1263 100644 --- a/.github/libs/GitUtils.js +++ b/.github/libs/GitUtils.js @@ -9,20 +9,38 @@ const {getPreviousVersion, SEMANTIC_VERSION_LEVELS} = require('../libs/versionUp */ function fetchTag(tag) { const previousPatchVersion = getPreviousVersion(tag, SEMANTIC_VERSION_LEVELS.PATCH); - try { - let command = `git fetch origin tag ${tag} --no-tags`; - - // Exclude commits reachable from the previous patch version (i.e: previous checklist), - // so that we don't have to fetch the full history - // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case - if (previousPatchVersion !== tag) { - command += ` --shallow-exclude=${previousPatchVersion}`; - } + let shouldRetry = true; + let needsRepack = false; + while (shouldRetry) { + try { + if (needsRepack) { + // We have seen some scenarios where this fixes the git fetch. + // Why? Who knows... https://github.com/Expensify/App/pull/31459 + execSync('git repack -d'); + } - console.log(`Running command: ${command}`); - execSync(command); - } catch (e) { - console.error(e); + let command = `git fetch origin tag ${tag} --no-tags`; + + // Exclude commits reachable from the previous patch version (i.e: previous checklist), + // so that we don't have to fetch the full history + // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case + if (previousPatchVersion !== tag) { + command += ` --shallow-exclude=${previousPatchVersion}`; + } + + console.log(`Running command: ${command}`); + execSync(command); + shouldRetry = false; + } catch (e) { + console.error(e); + if (!needsRepack) { + console.log('Attempting to repack and retry...'); + needsRepack = true; + } else { + console.error("Repack didn't help, giving up..."); + shouldRetry = false; + } + } } } diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 68b98ab625be..c904a459d1c0 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -42,12 +42,12 @@ Due to the large, ever-growing history of this repo, do not do any full-fetches ```yaml # Bad -- uses: actions/checkout@v3 +- uses: actions/checkout@v4 with: fetch-depth: 0 # Good -- uses: actions/checkout@v3 +- uses: actions/checkout@v4 ``` ```sh diff --git a/.github/workflows/cherryPick.yml b/.github/workflows/cherryPick.yml index 43f3c64554bc..92480a94ba53 100644 --- a/.github/workflows/cherryPick.yml +++ b/.github/workflows/cherryPick.yml @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout staging branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: staging token: ${{ secrets.OS_BOTIFY_TOKEN }} diff --git a/.github/workflows/createNewVersion.yml b/.github/workflows/createNewVersion.yml index c9c97d5355fb..812ec200bd88 100644 --- a/.github/workflows/createNewVersion.yml +++ b/.github/workflows/createNewVersion.yml @@ -68,7 +68,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: Check out - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: main # The OS_BOTIFY_COMMIT_TOKEN is a personal access token tied to osbotify diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 78040f237689..4aa1a6a27d1a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest if: github.ref == 'refs/heads/production' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 name: Checkout with: ref: production diff --git a/.github/workflows/deployBlocker.yml b/.github/workflows/deployBlocker.yml index f42d19ca8241..6356d7f65a4d 100644 --- a/.github/workflows/deployBlocker.yml +++ b/.github/workflows/deployBlocker.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index 9d94bf900615..318198981097 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -22,7 +22,7 @@ jobs: outputs: VERSION: ${{ steps.getMostRecentRelease.outputs.VERSION }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get most recent release version id: getMostRecentRelease @@ -78,7 +78,7 @@ jobs: outputs: DELTA_REF: ${{ steps.getDeltaRef.outputs.DELTA_REF }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get pull request details id: getPullRequestDetails @@ -154,7 +154,7 @@ jobs: needs: [buildBaseline, buildDelta] name: Run E2E tests in AWS device farm steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main diff --git a/.github/workflows/finishReleaseCycle.yml b/.github/workflows/finishReleaseCycle.yml index f8b68786aaab..6c3f3dfd7603 100644 --- a/.github/workflows/finishReleaseCycle.yml +++ b/.github/workflows/finishReleaseCycle.yml @@ -13,7 +13,7 @@ jobs: isValid: ${{ fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) && !fromJSON(steps.checkDeployBlockers.outputs.HAS_DEPLOY_BLOCKERS) }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: main token: ${{ secrets.OS_BOTIFY_TOKEN }} @@ -77,7 +77,7 @@ jobs: if: ${{ fromJSON(needs.validate.outputs.isValid) }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: staging token: ${{ secrets.OS_BOTIFY_TOKEN }} @@ -119,7 +119,7 @@ jobs: needs: [updateProduction, createNewPatchVersion] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: main token: ${{ secrets.OS_BOTIFY_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3072b3354a84..22a60992e7c7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main diff --git a/.github/workflows/lockDeploys.yml b/.github/workflows/lockDeploys.yml index 6ca025bb2a25..6a2812a4f92a 100644 --- a/.github/workflows/lockDeploys.yml +++ b/.github/workflows/lockDeploys.yml @@ -10,7 +10,7 @@ jobs: runs-on: macos-12 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: main token: ${{ secrets.OS_BOTIFY_TOKEN }} diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index d494ea0d008b..19c5cf9c90ef 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -41,7 +41,7 @@ jobs: needs: validateActor steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main @@ -62,7 +62,7 @@ jobs: runs-on: ubuntu-latest-xl steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure MapBox SDK run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} @@ -146,7 +146,7 @@ jobs: runs-on: macos-12-xl steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main @@ -185,7 +185,7 @@ jobs: runs-on: macos-13-xlarge steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure MapBox SDK run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} @@ -297,7 +297,7 @@ jobs: runs-on: ubuntu-latest-xl steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main @@ -367,7 +367,7 @@ jobs: needs: [android, desktop, iOS, web] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set version run: echo "VERSION=$(npm run print-version --silent)" >> "$GITHUB_ENV" @@ -428,7 +428,7 @@ jobs: needs: [android, desktop, iOS, web] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index bae843e74709..54fd1a830b8b 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -86,7 +86,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: Checkout main - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: main token: ${{ secrets.OS_BOTIFY_TOKEN }} diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml index b259ff9052b6..4aaa6fb2ce8c 100644 --- a/.github/workflows/reassurePerformanceTests.yml +++ b/.github/workflows/reassurePerformanceTests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup NodeJS uses: Expensify/App/.github/actions/composite/setupNode@main diff --git a/.github/workflows/shellCheck.yml b/.github/workflows/shellCheck.yml index 609541e9a660..366caa8a0d19 100644 --- a/.github/workflows/shellCheck.yml +++ b/.github/workflows/shellCheck.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Lint shell scripts with ShellCheck run: npm run shellcheck diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa47a2f61d4a..9c2e9486150b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: name: test (job ${{ fromJSON(matrix.chunk) }}) steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest name: Storybook tests steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Expensify/App/.github/actions/composite/setupNode@main @@ -57,7 +57,7 @@ jobs: name: Shell tests steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index b79b687e638e..1f266c59d0d1 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -221,7 +221,7 @@ jobs: runs-on: macos-12-xl steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} @@ -264,7 +264,7 @@ jobs: runs-on: ubuntu-latest-xl steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 3e54975433f6..c09db594e243 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -5,14 +5,14 @@ on: pull_request: types: [opened, synchronize] branches-ignore: [staging, production] - paths: ['**.ts', '**.tsx', 'package.json', 'package-lock.json', 'tsconfig.json'] + paths: ['**.js', '**.ts', '**.tsx', 'package.json', 'package-lock.json', 'tsconfig.json'] jobs: typecheck: if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Expensify/App/.github/actions/composite/setupNode@main @@ -20,3 +20,12 @@ jobs: run: npm run typecheck env: CI: true + + - name: Check for new JavaScript files + run: | + git fetch origin main --no-tags --depth=1 + count_new_js=$(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/libs/*.js' 'src/hooks/*.js' 'src/styles/*.js' 'src/languages/*.js' | wc -l) + if [ "$count_new_js" -gt "0" ]; then + echo "ERROR: Found new JavaScript files in the /src/libs, /src/hooks, /src/styles, or /src/languages directories; use TypeScript instead." + exit 1 + fi diff --git a/.github/workflows/validateDocsRoutes.yml b/.github/workflows/validateDocsRoutes.yml index 14c08e087565..702c48fbc068 100644 --- a/.github/workflows/validateDocsRoutes.yml +++ b/.github/workflows/validateDocsRoutes.yml @@ -11,7 +11,7 @@ jobs: if: github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Expensify/App/.github/actions/composite/setupNode@main diff --git a/.github/workflows/validateGithubActions.yml b/.github/workflows/validateGithubActions.yml index 5cfc4670620f..c493e26bc514 100644 --- a/.github/workflows/validateGithubActions.yml +++ b/.github/workflows/validateGithubActions.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main diff --git a/.github/workflows/verifyPodfile.yml b/.github/workflows/verifyPodfile.yml index d98780e3e829..08f9c3a5223b 100644 --- a/.github/workflows/verifyPodfile.yml +++ b/.github/workflows/verifyPodfile.yml @@ -15,7 +15,7 @@ jobs: runs-on: macos-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node uses: Expensify/App/.github/actions/composite/setupNode@main - name: Verify podfile diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml index 43e0e1650381..1ea81129fc15 100644 --- a/.github/workflows/welcome.yml +++ b/.github/workflows/welcome.yml @@ -10,7 +10,7 @@ jobs: if: ${{ github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get merged pull request id: getMergedPullRequest diff --git a/.nvmrc b/.nvmrc index d9289897d305..b8c9fdcbe36b 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.15.1 +16.15.1 \ No newline at end of file diff --git a/__mocks__/fs.js b/__mocks__/fs.js new file mode 100644 index 000000000000..cca0aa9520ec --- /dev/null +++ b/__mocks__/fs.js @@ -0,0 +1,3 @@ +const {fs} = require('memfs'); + +module.exports = fs; diff --git a/__mocks__/fs/promises.js b/__mocks__/fs/promises.js new file mode 100644 index 000000000000..1a58f0f013ac --- /dev/null +++ b/__mocks__/fs/promises.js @@ -0,0 +1,3 @@ +const {fs} = require('memfs'); + +module.exports = fs.promises; diff --git a/android/app/build.gradle b/android/app/build.gradle index af7f43adad0d..274a0d55eb37 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001039900 - versionName "1.3.99-0" + versionCode 1001040003 + versionName "1.4.0-3" } flavorDimensions "default" diff --git a/assets/images/empty-state__attach-receipt.svg b/assets/images/empty-state__attach-receipt.svg new file mode 100644 index 000000000000..6b50afbdbf0b --- /dev/null +++ b/assets/images/empty-state__attach-receipt.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md index 112c3b9617c9..f2ff837d7638 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md @@ -1,5 +1,91 @@ --- -title: Connect Company Cards -description: Connect Company Cards +title: Company-Card-Settings.md +description: Company card settings --- -## Resource Coming Soon! +# Overview +Once you’ve imported your company cards via commercial card feed, direct bank feed, or CSV import, the next step is to configure the cards’ settings. + +As a Domain Admin, you can access the company card settings by navigating to Settings > Domains> Domain Name > Company Card > Settings. + +If you cannot access Domains, you will need to request Domain Admin access my the Domain Admin. + +# How to configure company card settings +You can manage company cards and set and adjust the settings from the Domains page by navigating to Settings > Domains > [Domain name] > Settings + +## Reimbursable preference + +You can control how your employees' company card expenses are flagged for reimbursement: + +Force Yes: All expenses will be marked as reimbursable, and employees cannot change this setting. +Force No: All expenses will be marked as non-reimbursable, and employees cannot change this setting. +Do Not Force: Expenses will default to either reimbursable or non-reimbursable (your choice), but employees can adjust if necessary. + +## Liability type + +Choose the liability type that suits your needs: + +Corporate Liability: Users cannot delete company card expenses. +Personal Liability: Users are allowed to delete company card expenses. + +If you update the settings on an existing company card feed, the changes will apply to expenses imported after the date the setting is saved. The update will not affect previously imported expenses. + +## Preferred policy + +Setting a preferred policy for a company card feed will ensure that the imported transactions are added to a report on the policy you set. This setting is useful when members are on multiple policies and need to ensure their company card expenses are reported to a particular policy. + +# How to use Scheduled Submit with company cards +All expenses must be placed on a report if they need to be approved; with Scheduled Submit, you no longer need to worry about the arduous task of employees creating their expenses, adding them to a report, and submitting them manually. All they need to do is SmartScan their receipts and Concierge will take care of the rest, on a variety of schedules that you can set according to your preferences! + +Concierge won't automatically submit expenses on reports that have Expense Violations. Instead, these expenses will be moved to a new report, creating an additional report for the current reporting period. + +An employee can add comments in the Expense Comment field or at the bottom of the report to clarify any details. + +## Enable Scheduled Submit +Scheduled Submit is enabled in the Group Policy by navigating to Settings > Policies > Group > Policy Name > Reports > Scheduled Submit +Use the toggle to enable Scheduled Submit +Choose your desired frequency + +If Scheduled Submit is disabled on the group policy level (or set to a manual frequency), and you have noticed expense reports are still automatically submitted to the group policy, it's likely Scheduled Submit is enabled on the user’s Individual Policy settings. + +# How to connect company cards to an accounting integration + +If you're using a connected accounting system such as NetSuite, Xero, Intacct, Quickbooks Desktop, or QuickBooks Online, you can also connect the card to export to a specific credit card GL account. First, connect the card itself, and once completed, follow the steps below: +Go to Settings > Domains > Domain name > Company Cards +Click Edit Exports on the right-hand side of the card table and select the GL account you want to export expenses to +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. + +# How to export company card expenses to a connected accounting integration + +## Pooled GL account + +To export credit card expenses to a pooled GL account: +Go to Settings > Policies > Group > Policy Name > Connections > Accounting Integrations > Configure +Select Credit Card / Charge Card / Bank Transaction as your Non-reimbursable export option. +Please review the Export Settings page for exporting Expense Reports to NetSuite +Select the Vendor/liability account you want to export all non-reimbursable expenses to. + +## Individual GL account + +Go to Settings > Domain > Domain name > Company Cards +Click the Export Settings cog to the right-hand side of the card and select the GL account you want to export expenses to. +You're all done! After the account is set, exported expenses will be mapped to the specific account selected. + +# Deep Dive +## Identifying company card transactions +When you link your credit cards to Expensify, the transactions will appear in each user's account on the Expenses page as soon as they're posted. You can identify transactions from centrally managed cards by seeing the locked card icon next to them. That icon indicates that they’re company card expenses: +[add image here] + +## Importing historical transactions + +After a card is connected via direct connection or via Approved! banks, Expensify will import 30-90 days' worth of historical transactions to your account (the timeframe is based on your bank's discretion). Any historical expenses beyond that date range can be imported using the CSV spreadsheet import method. + +## Using eReceipts +Expensify eReceipts serve as digital substitutes for paper receipts in your purchase transactions, eliminating the necessity to retain physical receipts or utilize SmartScanning receipts. In the case of Expensify Card transactions, eReceipts are automatically generated for all amounts. For other card programs, eReceipts are specifically generated for purchases amounting to $75 or less, provided the transactions are in USD. +To ensure seamless automatic importation, it's essential to maintain your transactions in US Dollars. Additionally, eReceipts can be directly imported from your bank account. Please be aware that CSV/OFX imported files of bank transactions do not support eReceipts. +It's important to note that eReceipts are not generated for lodging expenses. Moreover, due to incomplete or inaccurate category information from certain banks, there may be instances of invalid eReceipts being generated for hotel purchases. If you choose to re-categorize expenses, a similar situation may arise. It's crucial to remember that our Expensify eReceipt Guarantee excludes coverage for hotel and motel expenses. + +# FAQ +## What plan/subscription is required in order to manage corporate cards? +Group Policy (Collect or Control plan only) +## When do my company card transactions import to Expensify? +Credit card transactions are imported to Expensify once they’re posted to the bank account. This usually takes 1-3 business days between the point of purchase and when the transactions populate in your account. diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md b/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md index e08aaa3d6094..4f660588d432 100644 --- a/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md @@ -1,5 +1,44 @@ --- title: Free Trial -description: Free Trial +description: Learn more about your free trial with Expensify. --- -## Resource Coming Soon! + +# Overview +New customers can take advantage of a seven-day Free Trial on a group Workspace. This trial period allows you to fully explore Expensify's features and capabilities before deciding on a subscription. +During the trial, your organization will have complete access to all the features and functionality offered by the Collect or Control workspace plan. This post provides a step-by-step guide on how to begin, oversee, and successfully conclude your organization's Expensify Free Trial. + +# How to start a Free Trial +1. Sign up for a new Expensify account at expensify.com. +2. After you've signed up for a new Expensify account, you will see a task on your Home page asking if you are using Expensify for your business or as an individual. + a. **Note**: If you select “Individual”, Expensify is free for individuals for up to 25 SmartScans per month. Selecting Individual will **not** start a Free Trial. More details on individual subscriptions can be found [here](https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription). +3. Select the Business option. +4. Select which Expensify features you'd like to set up for your organization. +5. Congratulations, your seven-day Free Trial has started! + +Once you've made these selections, we'll automatically enroll you in a Free Trial and create a Group Workspace, which will trigger new tasks on your Home page to walk you through how to configure Expensify for your organization. If you have any questions about a specific task or need assistance setting up your company, you can speak with your designated Setup Specialist by clicking “Support” on the left-hand navigation menu and selecting their name. This will allow you to message your Setup Specialist, and request a call if you need. + +# How to unlock additional Free Trial weeks +When you begin a Free Trial, you'll have an initial seven-day period before you need to provide your billing information to continue using Expensify. Luckily, Expensify offers the option to extend your Free Trial by an additional five weeks! + +To access these extra free weeks, all you need to do is complete the tasks on your Home page marked with the "Free Week!" banner. Each task completed in this category will automatically add seven more days to your trial. You can easily keep track of the remaining days of your Free Trial by checking the top right-hand corner of your Expensify Home page. + +# How to make the most of your Free Trial +- Complete all of the "Free Week!" tasks right away. These tasks are crucial for establishing your organization's Workspace, and finishing them will give you a clear idea of how much time you have left in your Free Trial. + +- Every Free Trial has dedicated access to a Setup Specialist who can help you set up your account to your preferences. We highly recommend booking a call with your dedicated Setup Specialist as soon as you start your Free Trial. If you ever need assistance with a setup task, your tasks also include demo videos. + +- Invite a few employees to join Expensify as early as possible during the Free Trial. Bringing employees on board and having them submit expenses will allow you to fully experience how all of the features and functionalities of Expensify work together to save time. We provide excellent resources to help employees get started with Expensify. + +- Establish a connection between Expensify and your accounting system from the outset. By doing this early, you can start testing Expensify comprehensively from end to end. + +# FAQ +## What happens when my Free Trial ends? +If you’ve already added a billing card to Expensify, you will automatically start your organization’s Expensify subscription after your Free Trial ends. At the beginning of the following month, we'll bill the card you have on file for your subscription, adjusting the charge to exclude the Free Trial period. +If your Free Trial concludes without a billing card on file, you will see a notification on your Home page saying, 'Your Free Trial has expired.' +If you still have outstanding 'Free Week!' tasks, completing them will extend your Free Trial by additional days. +If you continue without adding a billing card, you will be granted a five-day grace period after the following billing cycle before all Group Workspace functionality is disabled. To continue using Expensify's Group Workspace features, you will need to input your billing card information and initiate a subscription. + +## How can I downgrade my account after my Free Trial ends? +If you’d like to downgrade to an individual account after your Free Trial has ended, you will need to delete any Group Workspace that you have created. This action will remove the Workspaces, subscription, and any amount owed. You can do this in one of two ways from the Expensify web app: +- Select the “Downgrade” option on the billing card task on your Home page. +- Go to **Settings > Workspaces > [Workspace name]**, then click the gear button next to the Workspace and select Delete. diff --git a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md index 42a8a914e5bc..5431355dd790 100644 --- a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md +++ b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md @@ -66,7 +66,7 @@ A Workspace admin can see Processing, Approved, and Reimbursed expenses as long If employees submit expense reports on a workspace where you are not an admin, you will not have visibility into those expenses. Additionally, if an expense is left unreported, a workspace admin will not be able to see that expense until it’s been added to a report. A Workspace admin can edit the tags and categories on an expense, but if they want to edit the amount, date, or merchant name, the expense will need to be in a Processing state or rejected back to the submitter for changes. -We have more about company card expense reconciliation in this support article. +We have more about company card expense reconciliation in this [support article](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation). ## Can I edit multiple expenses at once? Yes! Select the expenses you want to edit and click **Edit Multiple**. diff --git a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md b/docs/articles/new-expensify/getting-started/Expensify-Lounge.md deleted file mode 100644 index bdccbe927769..000000000000 --- a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Welcome to the Expensify Lounge! -description: How to get the most out of the Expensify Lounge. -redirect_from: articles/other/Expensify-Lounge/ ---- - - -# What is the Expensify Lounge? -The Expensify Lounge is a place where people go to Get Shit Done. It's a beautiful environment with great coffee and a group of people to collaborate with. Check out this guide on how to best utilize the Expensify Lounge! - -# The Two Rules -### Rule #1 - Get Shit Done - -The Lounge is a space for people to get work done. It is optimized to be the perfect environment for you to focus on your work, collaborate with others, and advance your most wild and creative ideas. To make this a reality, we ask our members to keep the following in mind: - -- **#focus** - Use the space for how it was designed and do not distract from others' focus. The space is beautiful, social, and collaborative, but it was created to help our members work effectively. -- **#urgency** - Working remotely is great, but there's nothing like real-time collaboration with your colleagues. Use the lounge to meet with co-workers IRL to continue the progress on whatever it is you're working on. -- **#results** - Don't mistake time for effort or effort for output. Upon arrival, visualize what you want to accomplish, and don't leave until it's done. - -## Rule #2 - Don’t Ruin it for Everyone Else - -We want this place to be incredible, innovative, and always elvoving. To achieve that, we have some general guidelines: - -- **#writeitdown** - If you can help others learn from you, do so. Write a blog post, a document, or a post in Expensify Chat to share with others. This includes making the Expensify Lounge a better space. Feel free to write down any improvements so we can make it better. -- **#showup** - If you are in the lounge, be fully present. Meet others, and collaborate in social rooms. The point is to build a community of people who are focused on getting shit done; you’ll get out what you put in. -- **#oneteam** - Providing an inclusive community is our priority, and we do not tolerate any form of discrimination. Aim to go out of your way to include people who want to be included. -- **#nocreeps** - Do not make people feel uncomfortable with your words or actions. If you are made to feel uncomfortable or notice this happening to someone else, you can use the escalation process outlined in the FAQ section. - -# How to Use the Expensify Lounge -Keeping those two rules in mind, below is a guide on how our members can get the most out of the lounge. - -### Rule #1 - Getting Shit Done -- **Order drinks from Concierge** - [Write Concierge here](https://new.expensify.com/concierge) to ask lounge questions or order beverages. Concierge will bring your order directly to you! -- **Using an office** - Offices are first come, first serve. If an office is open, feel free to use it! Please keep office use to under an hour. We currently do not allow reserving offices. -- **Lounge hours** - The lounge will be open from 8am-6pm PT, Monday through Friday and closed on some major holidays. You can review our Google Maps profile to check our holiday hours. -- **Make the lounge better** - Make any suggestions to improve the lounge experience in [#announce - Expensify Lounge](https://new.expensify.com/r/8292963527436014). - -## Rule #2 - Not Ruining it for Everyone Else -- **Offices are for calls** - Please do not occupy an office unless you have a call or collaborative meeting happening, and don't stay in an office for longer than an hour. -- **Respect other people** - Please do not be too loud or distracting while others are trying to work. While collaborating in Expensify Chat, be respectful of others’ viewpoints and keep a positive environment. -- **Stay home if you’re sick** - If you feel sick, please do not visit the lounge, or consider wearing a mask in public areas. -- **If you see something, say something** - If you are made to feel uncomfortable or witness others being made uncomfortable, let Concierge know. If this is happening in Expensify Chat, use our moderation tools (outlined below in the FAQ) to apply the applicable level of moderation. - -We’re so happy you are here to live rich, have fun, and save the world with us. Now, go enjoy the Expensify Lounge, and let's Get Shit Done! - -# FAQs - -#### What is Concierge? - -Concierge is our automated system that answers member questions in real-time. Questions regarding the local lounge will be routed directly to the lounge's Concierge. You can send Concierge a message if you have a drink request or general questions. They’ll take care of everything for you! - -#### Who is invited to the Expensify Lounge? - -Everyone is invited to the Expensify Lounge! Whether you're an existing customer, or you're someone looking for a great space to Get Shit Done, we'd love to have you. - -#### How do I escalate something that's making me or someone else uncomfortable? - -If you see something in Expensify Chat that should be escalated, you can use the escalation feature to mark a chat as: -- **Spam or Inconsiderate**: This will send a whisper to the sender of the message warning them of the violation, and the message will have a flag applied to it which will be visible to all users. Concierge will not review these flags. -- **Intimidating or Bullying**: The message will be immediately hidden, and the content will be reviewed by our team. After reviewing the message, and it's confirmed intimidation or bullying, the message will be permanently hidden and we'll communicate the violation to the sender of the message. -- **Harassment or Assault**: The message will be immediately hidden and reviewed by our team. The user will be sent a message to warning them of the violation, and Concierge can block the user if that's deemed necessary. - -If you witness something in-person, please write to Concierge referencing which lounge you are in, and they will escalate the issue appropriately. - -#### Where are other Expensify Lounge locations? - -Right now, we only have the San Francisco Lounge, but be on the lookout for more coming soon! diff --git a/docs/redirects.csv b/docs/redirects.csv index 3bcfb594bedb..82add37c330c 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -17,4 +17,10 @@ https://community.expensify.com/discussion/5802/deep-dive-understanding-math-and https://community.expensify.com/discussion/5796/deep-dive-user-level-formula,https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates https://community.expensify.com/discussion/4750/how-to-create-a-custom-export,https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates https://community.expensify.com/discussion/4642/how-to-export-reports-to-a-custom-template,https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates - +https://community.expensify.com/discussion/5648/deep-dive-policy-users-and-roles,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles +https://community.expensify.com/discussion/5740/deep-dive-what-expense-information-is-available-based-on-role,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles +https://community.expensify.com/discussion/4472/how-to-set-or-edit-a-user-role,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles +https://community.expensify.com/discussion/5655/deep-dive-what-is-a-vacation-delegate,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate +https://community.expensify.com/discussion/5194/how-to-assign-a-vacation-delegate-for-an-employee-through-domains,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate +https://community.expensify.com/discussion/5190/how-to-individually-assign-a-vacation-delegate-from-account-settings,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate +https://community.expensify.com/discussion/5274/how-to-set-up-an-adp-indirect-integration-with-expensify,https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/ADP diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index d40d36701731..9f7e4a190690 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.99 + 1.4.0 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.99.0 + 1.4.0.3 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 4e3ca3ebce6d..c5801e5606a8 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.99 + 1.4.0 CFBundleSignature ???? CFBundleVersion - 1.3.99.0 + 1.4.0.3 diff --git a/package-lock.json b/package-lock.json index 47f1ce14b2d9..ab5d586e12e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.99-0", + "version": "1.4.0-3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.99-0", + "version": "1.4.0-3", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -92,7 +92,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.115", + "react-native-onyx": "1.0.118", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -165,7 +165,6 @@ "@types/js-yaml": "^4.0.5", "@types/lodash": "^4.14.195", "@types/mapbox-gl": "^2.7.13", - "@types/mock-fs": "^4.13.1", "@types/pusher-js": "^5.1.0", "@types/react": "^18.2.12", "@types/react-beautiful-dnd": "^13.1.4", @@ -213,8 +212,8 @@ "jest-cli": "29.4.1", "jest-environment-jsdom": "^29.4.1", "jest-transformer-svg": "^2.0.1", + "memfs": "^4.6.0", "metro-react-native-babel-preset": "0.76.8", - "mock-fs": "^4.13.0", "onchange": "^7.1.0", "portfinder": "^1.0.28", "prettier": "^2.8.8", @@ -241,8 +240,8 @@ "yaml": "^2.2.1" }, "engines": { - "node": "16.15.1", - "npm": "8.11.0" + "node": ">=16.15.1 <=20.9.0", + "npm": ">=8.11.0 <=10.1.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -12823,6 +12822,18 @@ "semver": "bin/semver.js" } }, + "node_modules/@storybook/builder-webpack5/node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/@storybook/builder-webpack5/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -16801,6 +16812,18 @@ "semver": "bin/semver.js" } }, + "node_modules/@storybook/manager-webpack5/node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/@storybook/manager-webpack5/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -19413,15 +19436,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mock-fs": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz", - "integrity": "sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -30232,9 +30246,10 @@ "license": "MIT" }, "node_modules/fast-diff": { - "version": "1.2.0", - "dev": true, - "license": "Apache-2.0" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true }, "node_modules/fast-equals": { "version": "4.0.3", @@ -30976,6 +30991,18 @@ "dev": true, "license": "MIT" }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", @@ -31121,9 +31148,10 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.3", - "dev": true, - "license": "Unlicense" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", + "dev": true }, "node_modules/fs-write-stream-atomic": { "version": "1.0.10", @@ -32759,6 +32787,15 @@ "which": "bin/which" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, "node_modules/hyphenate-style-name": { "version": "1.0.4", "license": "BSD-3-Clause" @@ -37359,6 +37396,13 @@ "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==", "license": "MIT" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true, + "peer": true + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -38587,14 +38631,70 @@ } }, "node_modules/memfs": { - "version": "3.4.7", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.6.0.tgz", + "integrity": "sha512-I6mhA1//KEZfKRQT9LujyW6lRbX7RkC24xKododIDO3AGShcaFAMKElv1yFGWX8fD4UaSiwasr3NeQ5TdtHY1A==", "dev": true, - "license": "Unlicense", "dependencies": { - "fs-monkey": "^1.0.3" + "json-joy": "^9.2.0", + "thingies": "^1.11.1" }, "engines": { "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/memfs/node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/memfs/node_modules/json-joy": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/json-joy/-/json-joy-9.9.1.tgz", + "integrity": "sha512-/d7th2nbQRBQ/nqTkBe6KjjvDciSwn9UICmndwk3Ed/Bk9AqkTRm4PnLVfXG4DKbT0rEY0nKnwE7NqZlqKE6kg==", + "dev": true, + "dependencies": { + "arg": "^5.0.2", + "hyperdyperid": "^1.2.0" + }, + "bin": { + "jj": "bin/jj.js", + "json-pack": "bin/json-pack.js", + "json-pack-test": "bin/json-pack-test.js", + "json-patch": "bin/json-patch.js", + "json-patch-test": "bin/json-patch-test.js", + "json-pointer": "bin/json-pointer.js", + "json-pointer-test": "bin/json-pointer-test.js", + "json-unpack": "bin/json-unpack.js" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "quill-delta": "^5", + "rxjs": "7", + "tslib": "2" + } + }, + "node_modules/memfs/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" } }, "node_modules/memoize-one": { @@ -40887,13 +40987,6 @@ "node": ">=10" } }, - "node_modules/mock-fs": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz", - "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==", - "dev": true, - "license": "MIT" - }, "node_modules/move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -43651,6 +43744,21 @@ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" }, + "node_modules/quill-delta": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", + "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", + "dev": true, + "peer": true, + "dependencies": { + "fast-diff": "^1.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/raf-schd": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", @@ -44323,17 +44431,17 @@ } }, "node_modules/react-native-onyx": { - "version": "1.0.115", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.115.tgz", - "integrity": "sha512-uPrJcw3Ta/EFL3Mh3iUggZ7EeEwLTSSSc5iUkKAA+a9Y8kBo8+6MWup9VCM/4wgysZbf3VHUGJCWQ8H3vWKgUg==", + "version": "1.0.118", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.118.tgz", + "integrity": "sha512-w54jO+Bpu1ElHsrxZXIIpcBqNkrUvuVCQmwWdfOW5LvO4UwsPSwmMxzExbUZ4ip+7CROmm10IgXFaAoyfeYSVQ==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", "underscore": "^1.13.6" }, "engines": { - "node": ">=16.15.1 <=18.17.1", - "npm": ">=8.11.0 <=9.6.7" + "node": ">=16.15.1 <=20.9.0", + "npm": ">=8.11.0 <=10.1.0" }, "peerDependencies": { "idb-keyval": "^6.2.1", @@ -49296,6 +49404,18 @@ "dev": true, "license": "MIT" }, + "node_modules/thingies": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.12.0.tgz", + "integrity": "sha512-AiGqfYC1jLmJagbzQGuoZRM48JPsr9yB734a7K6wzr34NMhjUPrWSQrkF7ZBybf3yCerCL2Gcr02kMv4NmaZfA==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -51690,6 +51810,18 @@ "node": ">= 10" } }, + "node_modules/webpack-dev-server/node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/webpack-dev-server/node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", @@ -61813,6 +61945,15 @@ } } }, + "memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.4" + } + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -64738,6 +64879,15 @@ } } }, + "memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.4" + } + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -66601,15 +66751,6 @@ "version": "3.0.5", "dev": true }, - "@types/mock-fs": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz", - "integrity": "sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -74528,7 +74669,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-diff": { - "version": "1.2.0", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, "fast-equals": { @@ -75059,6 +75202,15 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.4" + } + }, "schema-utils": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", @@ -75158,7 +75310,9 @@ } }, "fs-monkey": { - "version": "1.0.3", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", "dev": true }, "fs-write-stream-atomic": { @@ -76329,6 +76483,12 @@ } } }, + "hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true + }, "hyphenate-style-name": { "version": "1.0.4" }, @@ -79519,6 +79679,13 @@ "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==" }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true, + "peer": true + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -80428,10 +80595,41 @@ } }, "memfs": { - "version": "3.4.7", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.6.0.tgz", + "integrity": "sha512-I6mhA1//KEZfKRQT9LujyW6lRbX7RkC24xKododIDO3AGShcaFAMKElv1yFGWX8fD4UaSiwasr3NeQ5TdtHY1A==", "dev": true, "requires": { - "fs-monkey": "^1.0.3" + "json-joy": "^9.2.0", + "thingies": "^1.11.1" + }, + "dependencies": { + "arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "json-joy": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/json-joy/-/json-joy-9.9.1.tgz", + "integrity": "sha512-/d7th2nbQRBQ/nqTkBe6KjjvDciSwn9UICmndwk3Ed/Bk9AqkTRm4PnLVfXG4DKbT0rEY0nKnwE7NqZlqKE6kg==", + "dev": true, + "requires": { + "arg": "^5.0.2", + "hyperdyperid": "^1.2.0" + } + }, + "rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "peer": true, + "requires": { + "tslib": "^2.1.0" + } + } } }, "memoize-one": { @@ -82086,12 +82284,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, - "mock-fs": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz", - "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==", - "dev": true - }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -84053,6 +84245,18 @@ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" }, + "quill-delta": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", + "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", + "dev": true, + "peer": true, + "requires": { + "fast-diff": "^1.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.isequal": "^4.5.0" + } + }, "raf-schd": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", @@ -84618,9 +84822,9 @@ } }, "react-native-onyx": { - "version": "1.0.115", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.115.tgz", - "integrity": "sha512-uPrJcw3Ta/EFL3Mh3iUggZ7EeEwLTSSSc5iUkKAA+a9Y8kBo8+6MWup9VCM/4wgysZbf3VHUGJCWQ8H3vWKgUg==", + "version": "1.0.118", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.118.tgz", + "integrity": "sha512-w54jO+Bpu1ElHsrxZXIIpcBqNkrUvuVCQmwWdfOW5LvO4UwsPSwmMxzExbUZ4ip+7CROmm10IgXFaAoyfeYSVQ==", "requires": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -88122,6 +88326,13 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "thingies": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.12.0.tgz", + "integrity": "sha512-AiGqfYC1jLmJagbzQGuoZRM48JPsr9yB734a7K6wzr34NMhjUPrWSQrkF7ZBybf3yCerCL2Gcr02kMv4NmaZfA==", + "dev": true, + "requires": {} + }, "throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -89919,6 +90130,15 @@ "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", "dev": true }, + "memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.4" + } + }, "schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", diff --git a/package.json b/package.json index 4f6cf28831fe..b43685374dd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.99-0", + "version": "1.4.0-3", "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.", @@ -139,7 +139,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.115", + "react-native-onyx": "1.0.118", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -212,7 +212,6 @@ "@types/js-yaml": "^4.0.5", "@types/lodash": "^4.14.195", "@types/mapbox-gl": "^2.7.13", - "@types/mock-fs": "^4.13.1", "@types/pusher-js": "^5.1.0", "@types/react": "^18.2.12", "@types/react-beautiful-dnd": "^13.1.4", @@ -260,8 +259,8 @@ "jest-cli": "29.4.1", "jest-environment-jsdom": "^29.4.1", "jest-transformer-svg": "^2.0.1", + "memfs": "^4.6.0", "metro-react-native-babel-preset": "0.76.8", - "mock-fs": "^4.13.0", "onchange": "^7.1.0", "portfinder": "^1.0.28", "prettier": "^2.8.8", @@ -302,7 +301,7 @@ ] }, "engines": { - "node": "16.15.1", - "npm": "8.11.0" + "node": ">=16.15.1 <=20.9.0", + "npm": ">=8.11.0 <=10.1.0" } } diff --git a/patches/react-native-web+0.19.9+004+fix-pointer-events.patch b/patches/react-native-web+0.19.9+004+fix-pointer-events.patch new file mode 100644 index 000000000000..a457fbcfe36c --- /dev/null +++ b/patches/react-native-web+0.19.9+004+fix-pointer-events.patch @@ -0,0 +1,22 @@ +diff --git a/node_modules/react-native-web/dist/exports/StyleSheet/compiler/index.js b/node_modules/react-native-web/dist/exports/StyleSheet/compiler/index.js +index bdcecc2..63f1364 100644 +--- a/node_modules/react-native-web/dist/exports/StyleSheet/compiler/index.js ++++ b/node_modules/react-native-web/dist/exports/StyleSheet/compiler/index.js +@@ -353,7 +353,7 @@ function createAtomicRules(identifier, property, value) { + var _block2 = createDeclarationBlock({ + pointerEvents: 'none' + }); +- rules.push(selector + ">*" + _block2); ++ rules.push(selector + " *" + _block2); + } + } else if (value === 'none' || value === 'box-none') { + finalValue = 'none!important'; +@@ -361,7 +361,7 @@ function createAtomicRules(identifier, property, value) { + var _block3 = createDeclarationBlock({ + pointerEvents: 'auto' + }); +- rules.push(selector + ">*" + _block3); ++ rules.push(selector + " *" + _block3); + } + } + var _block4 = createDeclarationBlock({ diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh index c67c37a527a2..88ab17e7a2bd 100755 --- a/scripts/build-desktop.sh +++ b/scripts/build-desktop.sh @@ -14,16 +14,15 @@ else fi SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}") -LOCAL_PACKAGES=$(npm bin) source "$SCRIPTS_DIR/shellUtils.sh"; title "Bundling Desktop js Bundle Using Webpack" info " • ELECTRON_ENV: $ELECTRON_ENV" info " • ENV file: $ENV_FILE" info "" -"$LOCAL_PACKAGES/webpack" --config config/webpack/webpack.desktop.js --env envFile=$ENV_FILE +npx webpack --config config/webpack/webpack.desktop.js --env envFile=$ENV_FILE title "Building Desktop App Archive Using Electron" info "" shift 1 -"$LOCAL_PACKAGES/electron-builder" --config config/electronBuilder.config.js "$@" +npx electron-builder --config config/electronBuilder.config.js "$@" diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index 566b6c709423..ec4ddd623929 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -9,8 +9,8 @@ import useNetwork from '@hooks/useNetwork'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import Log from '@libs/Log'; import {plaidDataPropTypes} from '@pages/ReimbursementAccount/plaidDataPropTypes'; -import styles from '@styles/styles'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import * as App from '@userActions/App'; import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; @@ -83,6 +83,8 @@ function AddPlaidBankAccount({ allowDebit, isPlaidDisabled, }) { + const theme = useTheme(); + const styles = useThemeStyles(); const subscribedKeyboardShortcuts = useRef([]); const previousNetworkState = useRef(); @@ -186,7 +188,7 @@ function AddPlaidBankAccount({ {lodashGet(plaidData, 'isLoading') && ( diff --git a/src/components/AddressSearch/CurrentLocationButton.js b/src/components/AddressSearch/CurrentLocationButton.js index 326b82d31e8f..3c7feb8fb70c 100644 --- a/src/components/AddressSearch/CurrentLocationButton.js +++ b/src/components/AddressSearch/CurrentLocationButton.js @@ -7,8 +7,8 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import useLocalize from '@hooks/useLocalize'; import getButtonState from '@libs/getButtonState'; import colors from '@styles/colors'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; +import useThemeStyles from '@styles/useThemeStyles'; const propTypes = { /** Callback that runs when location button is clicked */ @@ -24,6 +24,7 @@ const defaultProps = { }; function CurrentLocationButton({onPress, isDisabled}) { + const styles = useThemeStyles(); const {translate} = useLocalize(); return ( diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 61460a93650e..73472beeb48d 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -14,9 +14,9 @@ import * as ApiUtils from '@libs/ApiUtils'; import compose from '@libs/compose'; import getCurrentPosition from '@libs/getCurrentPosition'; import * as GooglePlacesUtils from '@libs/GooglePlacesUtils'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import CurrentLocationButton from './CurrentLocationButton'; @@ -144,6 +144,8 @@ const defaultProps = { // Relevant thread: https://expensify.slack.com/archives/C03TQ48KC/p1634088400387400 // Reference: https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/609#issuecomment-886133839 function AddressSearch(props) { + const theme = useTheme(); + const styles = useThemeStyles(); const [displayListViewBorder, setDisplayListViewBorder] = useState(false); const [isTyping, setIsTyping] = useState(false); const [isFocused, setIsFocused] = useState(false); @@ -392,7 +394,7 @@ function AddressSearch(props) { listLoaderComponent={ @@ -489,8 +491,8 @@ function AddressSearch(props) { }} numberOfLines={2} isRowScrollable={false} - listHoverColor={themeColors.border} - listUnderlayColor={themeColors.buttonPressedBG} + listHoverColor={theme.border} + listUnderlayColor={theme.buttonPressedBG} onLayout={(event) => { // We use the height of the element to determine if we should hide the border of the listView dropdown // to prevent a lingering border when there are no address suggestions. diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js index 43fd5e6a1b98..bd88712432a8 100644 --- a/src/components/AmountTextInput.js +++ b/src/components/AmountTextInput.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import refPropTypes from './refPropTypes'; import TextInput from './TextInput'; @@ -39,6 +39,7 @@ const defaultProps = { }; function AmountTextInput(props) { + const styles = useThemeStyles(); return ( () => { ReportActionContextMenu.hideContextMenu(); diff --git a/src/components/AnonymousReportFooter.js b/src/components/AnonymousReportFooter.js index 2dc4159d1627..387e2ab01930 100644 --- a/src/components/AnonymousReportFooter.js +++ b/src/components/AnonymousReportFooter.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {Text, View} from 'react-native'; import reportPropTypes from '@pages/reportPropTypes'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import * as Session from '@userActions/Session'; import AvatarWithDisplayName from './AvatarWithDisplayName'; import Button from './Button'; @@ -29,6 +29,7 @@ const defaultProps = { }; function AnonymousReportFooter(props) { + const styles = useThemeStyles(); return ( diff --git a/src/components/ArchivedReportFooter.js b/src/components/ArchivedReportFooter.js index 52484355a242..b1fac827d273 100644 --- a/src/components/ArchivedReportFooter.js +++ b/src/components/ArchivedReportFooter.js @@ -9,7 +9,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import personalDetailsPropType from '@pages/personalDetailsPropType'; import reportPropTypes from '@pages/reportPropTypes'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import Banner from './Banner'; @@ -50,6 +50,7 @@ const defaultProps = { }; function ArchivedReportFooter(props) { + const styles = useThemeStyles(); const archiveReason = lodashGet(props.reportClosedAction, 'originalMessage.reason', CONST.REPORT.ARCHIVE_REASON.DEFAULT); let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetails, [props.report.ownerAccountID, 'displayName']); diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index f82fec156f9f..4ab81ae462c9 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -19,11 +19,10 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import useNativeDriver from '@libs/useNativeDriver'; import reportPropTypes from '@pages/reportPropTypes'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import * as IOU from '@userActions/IOU'; -import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -111,6 +110,8 @@ const defaultProps = { }; function AttachmentModal(props) { + const theme = useTheme(); + const styles = useThemeStyles(); const onModalHideCallbackRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(props.defaultOpen); const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false); @@ -359,12 +360,8 @@ function AttachmentModal(props) { } const menuItems = []; const parentReportAction = props.parentReportActions[props.report.parentReportActionID]; - const isDeleted = ReportActionsUtils.isDeletedAction(parentReportAction); - const isSettled = ReportUtils.isSettled(props.parentReport.reportID); - const isAdmin = Policy.isAdminOfFreePolicy([props.policy]) && ReportUtils.isExpenseReport(props.parentReport); - const isRequestor = ReportUtils.isMoneyRequestReport(props.parentReport) && lodashGet(props.session, 'accountID', null) === parentReportAction.actorAccountID; - const canEdit = !isSettled && !isDeleted && (isAdmin || isRequestor); + const canEdit = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, props.parentReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT); if (canEdit) { menuItems.push({ icon: Expensicons.Camera, @@ -411,7 +408,7 @@ function AttachmentModal(props) { onSubmit={submitAndClose} onClose={closeModal} isVisible={isModalOpen} - backgroundColor={themeColors.componentBG} + backgroundColor={theme.componentBG} onModalShow={() => { props.onModalShow(); setShouldLoadAttachment(true); diff --git a/src/components/AttachmentPicker/index.native.js b/src/components/AttachmentPicker/index.native.js index 0e723d4cf048..5b955ee69dd3 100644 --- a/src/components/AttachmentPicker/index.native.js +++ b/src/components/AttachmentPicker/index.native.js @@ -14,7 +14,7 @@ import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as FileUtils from '@libs/fileDownload/FileUtils'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import {defaultProps as baseDefaultProps, propTypes as basePropTypes} from './attachmentPickerPropTypes'; import launchCamera from './launchCamera'; @@ -101,6 +101,7 @@ const getDataForUpload = (fileData) => { * @returns {JSX.Element} */ function AttachmentPicker({type, children, shouldHideCameraOption}) { + const styles = useThemeStyles(); const [isVisible, setIsVisible] = useState(false); const completeAttachmentSelection = useRef(); diff --git a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js index 673bb7c224e2..dd2713a38b2b 100644 --- a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js +++ b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {PixelRatio, View} from 'react-native'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; const propTypes = { /** Cell Container styles */ @@ -14,6 +14,7 @@ const defaultProps = { }; function AttachmentCarouselCellRenderer(props) { + const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const modalStyles = styles.centeredModalStyles(isSmallScreenWidth, true); const style = [props.style, styles.h100, {width: PixelRatio.roundToNearestPixel(windowWidth - (modalStyles.marginHorizontal + modalStyles.borderWidth) * 2)}]; diff --git a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js b/src/components/Attachments/AttachmentCarousel/CarouselButtons.js index f11bbcc9b187..14a6ea268468 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselButtons.js @@ -8,8 +8,8 @@ import * as Expensicons from '@components/Icon/Expensicons'; import Tooltip from '@components/Tooltip'; import useLocalize from '@hooks/useLocalize'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import styles from '@styles/styles'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; const propTypes = { /** Where the arrows should be visible */ @@ -36,6 +36,8 @@ const defaultProps = { }; function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward, cancelAutoHideArrow, autoHideArrow}) { + const theme = useTheme(); + const styles = useThemeStyles(); const isBackDisabled = page === 0; const isForwardDisabled = page === _.size(attachments) - 1; @@ -51,7 +53,7 @@ function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward small innerStyles={[styles.arrowIcon]} icon={Expensicons.BackArrow} - iconFill={themeColors.text} + iconFill={theme.text} iconStyles={[styles.mr0]} onPress={onBack} onPressIn={cancelAutoHideArrow} @@ -67,7 +69,7 @@ function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward small innerStyles={[styles.arrowIcon]} icon={Expensicons.ArrowRight} - iconFill={themeColors.text} + iconFill={theme.text} iconStyles={[styles.mr0]} onPress={onForward} onPressIn={cancelAutoHideArrow} diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index 38f70057be61..b6cc0cbf21a4 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -9,7 +9,7 @@ import SafeAreaConsumer from '@components/SafeAreaConsumer'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import ReportAttachmentsContext from '@pages/home/report/ReportAttachmentsContext'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; const propTypes = { @@ -49,6 +49,7 @@ const defaultProps = { }; function CarouselItem({item, isFocused, onPress}) { + const styles = useThemeStyles(); const {translate} = useLocalize(); const {isAttachmentHidden} = useContext(ReportAttachmentsContext); // eslint-disable-next-line es/no-nullish-coalescing-operators diff --git a/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js b/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js index 0839462d4f23..cc1e20cb44e0 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js +++ b/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js @@ -15,7 +15,7 @@ import Animated, { withDecay, withSpring, } from 'react-native-reanimated'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import AttachmentCarouselPagerContext from './AttachmentCarouselPagerContext'; import ImageWrapper from './ImageWrapper'; @@ -60,6 +60,7 @@ const imageTransformerDefaultProps = { }; function ImageTransformer({imageWidth, imageHeight, imageScaleX, imageScaleY, scaledImageWidth, scaledImageHeight, isActive, children}) { + const styles = useThemeStyles(); const {canvasWidth, canvasHeight, onTap, onSwipe, onSwipeSuccess, pagerRef, shouldPagerScroll, isScrolling, onPinchGestureChange} = useContext(AttachmentCarouselPagerContext); const minImageScale = useMemo(() => Math.min(imageScaleX, imageScaleY), [imageScaleX, imageScaleY]); diff --git a/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js b/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js index 3a27d80c5509..b0a8b1f0d083 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js +++ b/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js @@ -2,13 +2,14 @@ import PropTypes from 'prop-types'; import React from 'react'; import {StyleSheet} from 'react-native'; import Animated from 'react-native-reanimated'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; const imageWrapperPropTypes = { children: PropTypes.node.isRequired, }; function ImageWrapper({children}) { + const styles = useThemeStyles(); return ( diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js index c024b025c80e..27790121aab0 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js @@ -3,8 +3,8 @@ import React, {useEffect, useRef} from 'react'; import {FlatList} from 'react-native-gesture-handler'; import Animated, {Easing, FadeOutDown, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import {propTypes} from './autoCompleteSuggestionsPropTypes'; @@ -29,6 +29,7 @@ const measureHeightOfSuggestionRows = (numRows, isSuggestionPickerLarge) => { }; function BaseAutoCompleteSuggestions(props) { + const styles = useThemeStyles(); const rowHeight = useSharedValue(0); const scrollRef = useRef(null); /** diff --git a/src/components/AutoEmailLink.js b/src/components/AutoEmailLink.js index eece1a16ca5a..bffd2493aa5d 100644 --- a/src/components/AutoEmailLink.js +++ b/src/components/AutoEmailLink.js @@ -2,7 +2,7 @@ import {CONST} from 'expensify-common/lib/CONST'; import PropTypes from 'prop-types'; import React from 'react'; import _ from 'underscore'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import Text from './Text'; import TextLink from './TextLink'; @@ -22,6 +22,7 @@ const defaultProps = { */ function AutoEmailLink(props) { + const styles = useThemeStyles(); return ( {_.map(props.text.split(CONST.REG_EXP.EXTRACT_EMAIL), (str, index) => { diff --git a/src/components/AutoUpdateTime.js b/src/components/AutoUpdateTime.js index c85f14ed2c29..1970839ec320 100644 --- a/src/components/AutoUpdateTime.js +++ b/src/components/AutoUpdateTime.js @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import DateUtils from '@libs/DateUtils'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import Text from './Text'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; @@ -23,6 +23,7 @@ const propTypes = { }; function AutoUpdateTime(props) { + const styles = useThemeStyles(); /** * @returns {Date} Returns the locale Date object */ diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 5e414486cc70..0052400bf51a 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -5,9 +5,9 @@ import _ from 'underscore'; import useNetwork from '@hooks/useNetwork'; import * as ReportUtils from '@libs/ReportUtils'; import stylePropTypes from '@styles/stylePropTypes'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; @@ -55,13 +55,15 @@ const defaultProps = { iconAdditionalStyles: [], containerStyles: [], size: CONST.AVATAR_SIZE.DEFAULT, - fill: themeColors.icon, + fill: undefined, fallbackIcon: Expensicons.FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, name: '', }; function Avatar(props) { + const theme = useTheme(); + const styles = useThemeStyles(); const [imageError, setImageError] = useState(false); useNetwork({onReconnect: () => setImageError(false)}); @@ -84,7 +86,7 @@ function Avatar(props) { const iconStyle = props.imageStyles && props.imageStyles.length ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; - const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name).fill : props.fill; + const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name).fill : props.fill || theme.icon; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(props.name) : props.fallbackIcon || Expensicons.FallbackAvatar; return ( @@ -95,11 +97,11 @@ function Avatar(props) { src={imageError ? fallbackAvatar : props.source} height={iconSize} width={iconSize} - fill={imageError ? themeColors.offline : iconFillColor} + fill={imageError ? theme.offline : iconFillColor} additionalStyles={[ StyleUtils.getAvatarBorderStyle(props.size, props.type), isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name) : {}, - imageError ? StyleUtils.getBackgroundColorStyle(themeColors.fallbackIconColor) : {}, + imageError ? StyleUtils.getBackgroundColorStyle(theme.fallbackIconColor) : {}, ...props.iconAdditionalStyles, ]} /> diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index 9b2b92aa9cee..a37f228a0d0d 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -17,9 +17,9 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; import compose from '@libs/compose'; import cropOrRotateImage from '@libs/cropOrRotateImage'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ImageCropView from './ImageCropView'; import Slider from './Slider'; @@ -61,6 +61,8 @@ const defaultProps = { // This component can't be written using class since reanimated API uses hooks. function AvatarCropModal(props) { + const theme = useTheme(); + const styles = useThemeStyles(); const originalImageWidth = useSharedValue(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE); const originalImageHeight = useSharedValue(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE); const translateY = useSharedValue(0); @@ -381,7 +383,7 @@ function AvatarCropModal(props) { {/* To avoid layout shift we should hide this component until the image container & image is initialized */} {!isImageInitialized || !isImageContainerInitialized ? ( @@ -402,8 +404,9 @@ function AvatarCropModal(props) { + diff --git a/src/components/AvatarCropModal/ImageCropView.js b/src/components/AvatarCropModal/ImageCropView.js index cb135cc76c69..a50409da64f4 100644 --- a/src/components/AvatarCropModal/ImageCropView.js +++ b/src/components/AvatarCropModal/ImageCropView.js @@ -6,8 +6,8 @@ import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import ControlSelection from '@libs/ControlSelection'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; +import useThemeStyles from '@styles/useThemeStyles'; import gestureHandlerPropTypes from './gestureHandlerPropTypes'; const propTypes = { @@ -50,6 +50,7 @@ const defaultProps = { }; function ImageCropView(props) { + const styles = useThemeStyles(); const containerStyle = StyleUtils.getWidthAndHeightStyle(props.containerSize, props.containerSize); const originalImageHeight = props.originalImageHeight; diff --git a/src/components/AvatarCropModal/Slider.js b/src/components/AvatarCropModal/Slider.js index 4281da1e7b99..9df6ac3c0498 100644 --- a/src/components/AvatarCropModal/Slider.js +++ b/src/components/AvatarCropModal/Slider.js @@ -6,7 +6,7 @@ import Animated, {useAnimatedStyle} from 'react-native-reanimated'; import Tooltip from '@components/Tooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import ControlSelection from '@libs/ControlSelection'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import gestureHandlerPropTypes from './gestureHandlerPropTypes'; const propTypes = { @@ -26,6 +26,7 @@ const defaultProps = { // This component can't be written using class since reanimated API uses hooks. function Slider(props) { + const styles = useThemeStyles(); const sliderValue = props.sliderValue; const [tooltipIsVisible, setTooltipIsVisible] = useState(true); diff --git a/src/components/AvatarSkeleton.js b/src/components/AvatarSkeleton.js index 2a633833f228..d2706447f756 100644 --- a/src/components/AvatarSkeleton.js +++ b/src/components/AvatarSkeleton.js @@ -1,15 +1,16 @@ import React from 'react'; import {Circle} from 'react-native-svg'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; import SkeletonViewContentLoader from './SkeletonViewContentLoader'; function AvatarSkeleton() { + const theme = useTheme(); return ( { }; function AvatarWithDisplayName(props) { + const theme = useTheme(); + const styles = useThemeStyles(); const title = ReportUtils.getReportName(props.report); const subtitle = ReportUtils.getChatRoomSubtitle(props.report); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(props.report); @@ -99,7 +101,7 @@ function AvatarWithDisplayName(props) { const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(props.report); const isExpenseRequest = ReportUtils.isExpenseRequest(props.report); const defaultSubscriptSize = isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : props.size; - const avatarBorderColor = props.isAnonymous ? themeColors.highlightBG : themeColors.componentBG; + const avatarBorderColor = props.isAnonymous ? theme.highlightBG : theme.componentBG; const headerView = ( diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 87bd382e806b..893a02288e77 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -9,8 +9,6 @@ import * as FileUtils from '@libs/fileDownload/FileUtils'; import getImageResolution from '@libs/fileDownload/getImageResolution'; import SpinningIndicatorAnimation from '@styles/animation/SpinningIndicatorAnimation'; import stylePropTypes from '@styles/stylePropTypes'; -import styles from '@styles/styles'; -import themeColors from '@styles/themes/default'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import AttachmentModal from './AttachmentModal'; @@ -26,6 +24,8 @@ import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Tooltip from './Tooltip/PopoverAnchorTooltip'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import withNavigationFocus from './withNavigationFocus'; +import withTheme, {withThemePropTypes} from './withTheme'; +import withThemeStyles, {withThemeStylesPropTypes} from './withThemeStyles'; const propTypes = { /** Avatar source to display */ @@ -95,6 +95,8 @@ const propTypes = { isFocused: PropTypes.bool.isRequired, ...withLocalizePropTypes, + ...withThemeStylesPropTypes, + ...withThemePropTypes, }; const defaultProps = { @@ -253,8 +255,8 @@ class AvatarWithImagePicker extends React.Component { const additionalStyles = _.isArray(this.props.style) ? this.props.style : [this.props.style]; return ( - - + + {this.props.source ? ( )} - + @@ -364,7 +366,7 @@ class AvatarWithImagePicker extends React.Component { {this.state.validationError && ( @@ -386,4 +388,4 @@ class AvatarWithImagePicker extends React.Component { AvatarWithImagePicker.propTypes = propTypes; AvatarWithImagePicker.defaultProps = defaultProps; -export default compose(withLocalize, withNavigationFocus)(AvatarWithImagePicker); +export default compose(withLocalize, withNavigationFocus, withThemeStyles, withTheme)(AvatarWithImagePicker); diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js index 05ca65fc64da..f3607b69a73f 100644 --- a/src/components/AvatarWithIndicator.js +++ b/src/components/AvatarWithIndicator.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import * as UserUtils from '@libs/UserUtils'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import Avatar from './Avatar'; import AvatarSkeleton from './AvatarSkeleton'; import * as Expensicons from './Icon/Expensicons'; @@ -30,6 +30,7 @@ const defaultProps = { }; function AvatarWithIndicator(props) { + const styles = useThemeStyles(); return ( diff --git a/src/components/Badge.js b/src/components/Badge.js deleted file mode 100644 index 49b330ae37b2..000000000000 --- a/src/components/Badge.js +++ /dev/null @@ -1,79 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import styles from '@styles/styles'; -import * as StyleUtils from '@styles/StyleUtils'; -import CONST from '@src/CONST'; -import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; -import Text from './Text'; - -const propTypes = { - /** Is Success type */ - success: PropTypes.bool, - - /** Is Error type */ - error: PropTypes.bool, - - /** Whether badge is clickable */ - pressable: PropTypes.bool, - - /** Text to display in the Badge */ - text: PropTypes.string.isRequired, - - /** Text to display in the Badge */ - environment: PropTypes.string, - - /** Styles for Badge */ - // eslint-disable-next-line react/forbid-prop-types - badgeStyles: PropTypes.arrayOf(PropTypes.object), - - /** Styles for Badge Text */ - // eslint-disable-next-line react/forbid-prop-types - textStyles: PropTypes.arrayOf(PropTypes.object), - - /** Callback to be called on onPress */ - onPress: PropTypes.func, -}; - -const defaultProps = { - success: false, - error: false, - pressable: false, - badgeStyles: [], - textStyles: [], - onPress: undefined, - environment: CONST.ENVIRONMENT.DEV, -}; - -function Badge(props) { - const textStyles = props.success || props.error ? styles.textWhite : undefined; - const Wrapper = props.pressable ? PressableWithoutFeedback : View; - const wrapperStyles = ({pressed}) => [ - styles.badge, - styles.ml2, - StyleUtils.getBadgeColorStyle(props.success, props.error, pressed, props.environment === CONST.ENVIRONMENT.ADHOC), - ...props.badgeStyles, - ]; - - return ( - - - {props.text} - - - ); -} - -Badge.displayName = 'Badge'; -Badge.propTypes = propTypes; -Badge.defaultProps = defaultProps; -export default Badge; diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx new file mode 100644 index 000000000000..22c056dfdfc4 --- /dev/null +++ b/src/components/Badge.tsx @@ -0,0 +1,66 @@ +import React, {useCallback} from 'react'; +import {GestureResponderEvent, PressableStateCallbackType, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import * as StyleUtils from '@styles/StyleUtils'; +import useThemeStyles from '@styles/useThemeStyles'; +import CONST from '@src/CONST'; +import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; +import Text from './Text'; + +type BadgeProps = { + /** Is Success type */ + success?: boolean; + + /** Is Error type */ + error?: boolean; + + /** Whether badge is clickable */ + pressable?: boolean; + + /** Text to display in the Badge */ + text: string; + + /** Text to display in the Badge */ + environment?: string; + + /** Styles for Badge */ + badgeStyles?: StyleProp; + + /** Styles for Badge Text */ + textStyles?: StyleProp; + + /** Callback to be called on onPress */ + onPress: (event?: GestureResponderEvent | KeyboardEvent) => void; +}; + +function Badge({success = false, error = false, pressable = false, text, environment = CONST.ENVIRONMENT.DEV, badgeStyles, textStyles, onPress = () => {}}: BadgeProps) { + const styles = useThemeStyles(); + const textColorStyles = success || error ? styles.textWhite : undefined; + const Wrapper = pressable ? PressableWithoutFeedback : View; + + const wrapperStyles: (state: PressableStateCallbackType) => StyleProp = useCallback( + ({pressed}) => [styles.badge, styles.ml2, StyleUtils.getBadgeColorStyle(success, error, pressed, environment === CONST.ENVIRONMENT.ADHOC), badgeStyles], + [styles.badge, styles.ml2, success, error, environment, badgeStyles], + ); + + return ( + + + {text} + + + ); +} + +Badge.displayName = 'Badge'; + +export default Badge; diff --git a/src/components/Banner.js b/src/components/Banner.js index 23226e21eb51..2fcb866334e0 100644 --- a/src/components/Banner.js +++ b/src/components/Banner.js @@ -3,8 +3,8 @@ import React, {memo} from 'react'; import {View} from 'react-native'; import compose from '@libs/compose'; import getButtonState from '@libs/getButtonState'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import Hoverable from './Hoverable'; import Icon from './Icon'; @@ -56,6 +56,7 @@ const defaultProps = { }; function Banner(props) { + const styles = useThemeStyles(); return ( {(isHovered) => { diff --git a/src/components/BaseMiniContextMenuItem.js b/src/components/BaseMiniContextMenuItem.js index b8d7a4a7484b..04a569ba7f36 100644 --- a/src/components/BaseMiniContextMenuItem.js +++ b/src/components/BaseMiniContextMenuItem.js @@ -5,8 +5,8 @@ import _ from 'underscore'; import DomUtils from '@libs/DomUtils'; import getButtonState from '@libs/getButtonState'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; +import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Tooltip from './Tooltip/PopoverAnchorTooltip'; @@ -50,6 +50,7 @@ const defaultProps = { * @returns {JSX.Element} */ function BaseMiniContextMenuItem(props) { + const styles = useThemeStyles(); return ( diff --git a/src/components/BlockingViews/FullPageNotFoundView.js b/src/components/BlockingViews/FullPageNotFoundView.js index 5232b5eca8dd..b82474aa0694 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.js +++ b/src/components/BlockingViews/FullPageNotFoundView.js @@ -5,7 +5,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Illustrations from '@components/Icon/Illustrations'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import ROUTES from '@src/ROUTES'; import BlockingView from './BlockingView'; @@ -53,6 +53,7 @@ const defaultProps = { // eslint-disable-next-line rulesdir/no-negated-variables function FullPageNotFoundView({children, shouldShow, titleKey, subtitleKey, linkKey, onBackButtonPress, shouldShowLink, shouldShowBackButton, onLinkPress}) { + const styles = useThemeStyles(); const {translate} = useLocalize(); if (shouldShow) { return ( diff --git a/src/components/Button/index.js b/src/components/Button/index.js index 5fe7dd1fe812..b9aaf8868924 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -10,9 +10,9 @@ import Text from '@components/Text'; import withNavigationFallback from '@components/withNavigationFallback'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import HapticFeedback from '@libs/HapticFeedback'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import validateSubmitShortcut from './validateSubmitShortcut'; @@ -127,7 +127,7 @@ const defaultProps = { shouldShowRightIcon: false, icon: null, iconRight: Expensicons.ArrowRight, - iconFill: themeColors.textLight, + iconFill: undefined, iconStyles: [], iconRightStyles: [], isLoading: false, @@ -201,6 +201,8 @@ function Button({ accessibilityLabel, forwardedRef, }) { + const theme = useTheme(); + const styles = useThemeStyles(); const isFocused = useIsFocused(); const keyboardShortcutCallback = useCallback( @@ -254,7 +256,7 @@ function Button({ @@ -265,7 +267,7 @@ function Button({ @@ -334,7 +336,7 @@ function Button({ {renderContent()} {isLoading && ( )} diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.js index 7c88d9202b78..15f2e2f4d6de 100644 --- a/src/components/ButtonWithDropdownMenu.js +++ b/src/components/ButtonWithDropdownMenu.js @@ -3,9 +3,9 @@ import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import Button from './Button'; import Icon from './Icon'; @@ -72,6 +72,8 @@ const defaultProps = { }; function ButtonWithDropdownMenu(props) { + const theme = useTheme(); + const styles = useThemeStyles(); const [selectedItemIndex, setSelectedItemIndex] = useState(0); const [isMenuVisible, setIsMenuVisible] = useState(false); const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); @@ -134,7 +136,7 @@ function ButtonWithDropdownMenu(props) { diff --git a/src/components/CardPreview.js b/src/components/CardPreview.js index 9f59ca140ce5..df944d930a92 100644 --- a/src/components/CardPreview.js +++ b/src/components/CardPreview.js @@ -4,7 +4,7 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import ExpensifyCardImage from '@assets/images/expensify-card.svg'; import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import Text from './Text'; @@ -33,6 +33,7 @@ const defaultProps = { }; function CardPreview({privatePersonalDetails: {legalFirstName, legalLastName}, session: {email}}) { + const styles = useThemeStyles(); usePrivatePersonalDetails(); const cardHolder = legalFirstName && legalLastName ? `${legalFirstName} ${legalLastName}` : email; diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js index 156007aea76e..ff7087df91dd 100644 --- a/src/components/CategoryPicker/index.js +++ b/src/components/CategoryPicker/index.js @@ -5,12 +5,13 @@ import _ from 'underscore'; import OptionsSelector from '@components/OptionsSelector'; import useLocalize from '@hooks/useLocalize'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, propTypes} from './categoryPickerPropTypes'; function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, onSubmit}) { + const styles = useThemeStyles(); const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); diff --git a/src/components/Checkbox.js b/src/components/Checkbox.js index 5734ad2fed26..4b9ce922aacb 100644 --- a/src/components/Checkbox.js +++ b/src/components/Checkbox.js @@ -2,9 +2,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import stylePropTypes from '@styles/stylePropTypes'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; @@ -67,6 +67,8 @@ const defaultProps = { }; function Checkbox(props) { + const theme = useTheme(); + const styles = useThemeStyles(); const handleSpaceKey = (event) => { if (event.code !== 'Space') { return; @@ -115,7 +117,7 @@ function Checkbox(props) { {props.isChecked && ( diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js index 86dba1d2a932..146e37ceb730 100644 --- a/src/components/CheckboxWithLabel.js +++ b/src/components/CheckboxWithLabel.js @@ -2,11 +2,12 @@ import PropTypes from 'prop-types'; import React, {useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import Checkbox from './Checkbox'; import FormHelpMessage from './FormHelpMessage'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; +import refPropTypes from './refPropTypes'; import Text from './Text'; /** @@ -54,7 +55,7 @@ const propTypes = { defaultValue: PropTypes.bool, /** React ref being forwarded to the Checkbox input */ - forwardedRef: PropTypes.func, + forwardedRef: refPropTypes, /** The ID used to uniquely identify the input in a Form */ /* eslint-disable-next-line react/no-unused-prop-types */ @@ -83,6 +84,7 @@ const defaultProps = { }; function CheckboxWithLabel(props) { + const styles = useThemeStyles(); // We need to pick the first value that is strictly a boolean // https://github.com/Expensify/App/issues/16885#issuecomment-1520846065 const [isChecked, setIsChecked] = useState(() => _.find([props.value, props.defaultValue, props.isChecked], (value) => _.isBoolean(value))); diff --git a/src/components/CommunicationsLink.js b/src/components/CommunicationsLink.js index f09fecea5239..dbbe5737b3aa 100644 --- a/src/components/CommunicationsLink.js +++ b/src/components/CommunicationsLink.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import Clipboard from '@libs/Clipboard'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import ContextMenuItem from './ContextMenuItem'; import * as Expensicons from './Icon/Expensicons'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; @@ -26,6 +26,7 @@ const defaultProps = { }; function CommunicationsLink(props) { + const styles = useThemeStyles(); return ( diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index d02fdd2563b1..4c61a5b5bba5 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -16,9 +16,9 @@ import updateIsFullComposerAvailable from '@libs/ComposerUtils/updateIsFullCompo import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; const propTypes = { @@ -57,7 +57,7 @@ const propTypes = { isDisabled: PropTypes.bool, /** Set focus to this component the first time it renders. - Override this in case you need to set focus on one field out of many, or when you want to disable autoFocus */ + Override this in case you need to set focus on one field out of many, or when you want to disable autoFocus */ autoFocus: PropTypes.bool, /** Update selection position on change */ @@ -169,6 +169,8 @@ function Composer({ isComposerFullSize, ...props }) { + const theme = useTheme(); + const styles = useThemeStyles(); const {windowWidth} = useWindowDimensions(); const textRef = useRef(null); const textInput = useRef(null); @@ -448,7 +450,8 @@ function Composer({ StyleUtils.getComposeTextAreaPadding(numberOfLines, isComposerFullSize), Browser.isMobileSafari() || Browser.isSafari() ? styles.rtlTextRenderForSafari : {}, ], - [style, maxLines, numberOfLines, isComposerFullSize], + + [numberOfLines, maxLines, styles.overflowHidden, styles.rtlTextRenderForSafari, style, isComposerFullSize], ); return ( @@ -456,7 +459,7 @@ function Composer({ (textInput.current = el)} selection={selection} style={inputStyleMemo} diff --git a/src/components/ConfirmContent.js b/src/components/ConfirmContent.js index 6142322848d0..ff8ee4f861a4 100644 --- a/src/components/ConfirmContent.js +++ b/src/components/ConfirmContent.js @@ -4,7 +4,7 @@ import {View} from 'react-native'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import Button from './Button'; import Header from './Header'; @@ -87,6 +87,7 @@ const defaultProps = { }; function ConfirmContent(props) { + const styles = useThemeStyles(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); diff --git a/src/components/ConfirmationPage.js b/src/components/ConfirmationPage.js index 22e29dca519d..ac56ea3d22e9 100644 --- a/src/components/ConfirmationPage.js +++ b/src/components/ConfirmationPage.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import Button from './Button'; import FixedFooter from './FixedFooter'; import Lottie from './Lottie'; @@ -39,6 +39,7 @@ const defaultProps = { }; function ConfirmationPage(props) { + const styles = useThemeStyles(); return ( <> diff --git a/src/components/ConnectBankAccountButton.js b/src/components/ConnectBankAccountButton.js index 2c66bcc200da..6afd3d57d4e6 100644 --- a/src/components/ConnectBankAccountButton.js +++ b/src/components/ConnectBankAccountButton.js @@ -3,7 +3,7 @@ import React from 'react'; import {View} from 'react-native'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import * as ReimbursementAccount from '@userActions/ReimbursementAccount'; import Button from './Button'; import * as Expensicons from './Icon/Expensicons'; @@ -30,6 +30,7 @@ const defaultProps = { }; function ConnectBankAccountButton(props) { + const styles = useThemeStyles(); const activeRoute = Navigation.getActiveRouteWithoutParams(); return props.network.isOffline ? ( diff --git a/src/components/ContextMenuItem.js b/src/components/ContextMenuItem.js index 80d4855392a4..d0a43badc5e3 100644 --- a/src/components/ContextMenuItem.js +++ b/src/components/ContextMenuItem.js @@ -4,8 +4,8 @@ import useThrottledButtonState from '@hooks/useThrottledButtonState'; import useWindowDimensions from '@hooks/useWindowDimensions'; import getButtonState from '@libs/getButtonState'; import getContextMenuItemStyles from '@styles/getContextMenuItemStyles'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; +import useThemeStyles from '@styles/useThemeStyles'; import BaseMiniContextMenuItem from './BaseMiniContextMenuItem'; import Icon from './Icon'; import MenuItem from './MenuItem'; @@ -53,6 +53,7 @@ const defaultProps = { }; function ContextMenuItem({onPress, successIcon, successText, icon, text, isMini, description, isAnonymousAction, isFocused, innerRef}) { + const styles = useThemeStyles(); const {windowWidth} = useWindowDimensions(); const [isThrottledButtonActive, setThrottledButtonInactive] = useThrottledButtonState(); diff --git a/src/components/CountrySelector.js b/src/components/CountrySelector.js index c2426c5b7b0b..13fc215f1d8c 100644 --- a/src/components/CountrySelector.js +++ b/src/components/CountrySelector.js @@ -3,7 +3,7 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import ROUTES from '@src/ROUTES'; import FormHelpMessage from './FormHelpMessage'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; @@ -33,6 +33,7 @@ const defaultProps = { }; function CountrySelector({errorText, value: countryCode, onInputChange, forwardedRef}) { + const styles = useThemeStyles(); const {translate} = useLocalize(); const title = countryCode ? translate(`allCountries.${countryCode}`) : ''; diff --git a/src/components/CurrencySymbolButton.js b/src/components/CurrencySymbolButton.js index ca7816a9f117..4d43ec3d93e0 100644 --- a/src/components/CurrencySymbolButton.js +++ b/src/components/CurrencySymbolButton.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import useLocalize from '@hooks/useLocalize'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Text from './Text'; @@ -16,6 +16,7 @@ const propTypes = { }; function CurrencySymbolButton({onCurrencyButtonPress, currencySymbol}) { + const styles = useThemeStyles(); const {translate} = useLocalize(); return ( diff --git a/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx b/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx index 3a87702b48e4..685db8031330 100644 --- a/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx +++ b/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx @@ -3,9 +3,9 @@ import {View} from 'react-native'; import {Circle, Rect} from 'react-native-svg'; import {ValueOf} from 'type-fest'; import SkeletonViewContentLoader from '@components/SkeletonViewContentLoader'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -23,12 +23,9 @@ type CurrentUserPersonalDetailsSkeletonViewProps = { foregroundColor?: string; }; -function CurrentUserPersonalDetailsSkeletonView({ - shouldAnimate = true, - avatarSize = CONST.AVATAR_SIZE.LARGE, - backgroundColor = themeColors.highlightBG, - foregroundColor = themeColors.border, -}: CurrentUserPersonalDetailsSkeletonViewProps) { +function CurrentUserPersonalDetailsSkeletonView({shouldAnimate = true, avatarSize = CONST.AVATAR_SIZE.LARGE, backgroundColor, foregroundColor}: CurrentUserPersonalDetailsSkeletonViewProps) { + const theme = useTheme(); + const styles = useThemeStyles(); const avatarPlaceholderSize = StyleUtils.getAvatarSize(avatarSize); const avatarPlaceholderRadius = avatarPlaceholderSize / 2; const spaceBetweenAvatarAndHeadline = styles.mb3.marginBottom + styles.mt1.marginTop + (variables.lineHeightXXLarge - variables.fontSizeXLarge) / 2; @@ -39,8 +36,8 @@ function CurrentUserPersonalDetailsSkeletonView({ {formattedBalance}; } diff --git a/src/components/CustomStatusBar/index.js b/src/components/CustomStatusBar/index.js index 2ffd763bf088..a724c71059ef 100644 --- a/src/components/CustomStatusBar/index.js +++ b/src/components/CustomStatusBar/index.js @@ -1,23 +1,24 @@ import React, {useEffect} from 'react'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import StatusBar from '@libs/StatusBar'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; function CustomStatusBar() { + const theme = useTheme(); useEffect(() => { Navigation.isNavigationReady().then(() => { // Set the status bar colour depending on the current route. // If we don't have any colour defined for a route, fall back to // appBG color. const currentRoute = navigationRef.getCurrentRoute(); - let currentScreenBackgroundColor = themeColors.appBG; - if (currentRoute && 'name' in currentRoute && currentRoute.name in themeColors.PAGE_BACKGROUND_COLORS) { - currentScreenBackgroundColor = themeColors.PAGE_BACKGROUND_COLORS[currentRoute.name]; + let currentScreenBackgroundColor = theme.appBG; + if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_BACKGROUND_COLORS) { + currentScreenBackgroundColor = theme.PAGE_BACKGROUND_COLORS[currentRoute.name]; } StatusBar.setBarStyle('light-content', true); StatusBar.setBackgroundColor(currentScreenBackgroundColor); }); - }, []); + }, [theme.PAGE_BACKGROUND_COLORS, theme.appBG]); return ; } diff --git a/src/components/DatePicker/index.android.js b/src/components/DatePicker/index.android.js index 17d1e2e14e71..5e7086fb78ad 100644 --- a/src/components/DatePicker/index.android.js +++ b/src/components/DatePicker/index.android.js @@ -3,11 +3,12 @@ import {format, parseISO} from 'date-fns'; import React, {forwardRef, useCallback, useImperativeHandle, useRef, useState} from 'react'; import {Keyboard} from 'react-native'; import TextInput from '@components/TextInput'; -import styles from '@styles/styles'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import {defaultProps, propTypes} from './datepickerPropTypes'; const DatePicker = forwardRef(({value, defaultValue, label, placeholder, errorText, containerStyles, disabled, onBlur, onInputChange, maxDate, minDate}, outerRef) => { + const styles = useThemeStyles(); const ref = useRef(); const [isPickerVisible, setIsPickerVisible] = useState(false); diff --git a/src/components/DatePicker/index.ios.js b/src/components/DatePicker/index.ios.js index 8b884c29b07f..44a825aa8183 100644 --- a/src/components/DatePicker/index.ios.js +++ b/src/components/DatePicker/index.ios.js @@ -7,12 +7,14 @@ import Popover from '@components/Popover'; import TextInput from '@components/TextInput'; import useKeyboardState from '@hooks/useKeyboardState'; import useLocalize from '@hooks/useLocalize'; -import styles from '@styles/styles'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import {defaultProps, propTypes} from './datepickerPropTypes'; function DatePicker({value, defaultValue, innerRef, onInputChange, preferredLocale, minDate, maxDate, label, disabled, onBlur, placeholder, containerStyles, errorText}) { + const theme = useTheme(); + const styles = useThemeStyles(); const dateValue = value || defaultValue; const [isPickerVisible, setIsPickerVisible] = useState(false); const [selectedDate, setSelectedDate] = useState(dateValue ? new Date(dateValue) : new Date()); @@ -104,12 +106,13 @@ function DatePicker({value, defaultValue, innerRef, onInputChange, preferredLoca