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 cdb95bd66779..c09db594e243 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -12,7 +12,7 @@ jobs: 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 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 6827448c5053..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 1001040000 - versionName "1.4.0-0" + versionCode 1001040003 + versionName "1.4.0-3" } flavorDimensions "default" 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/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 0de3f7cb2671..9f7e4a190690 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.0.0 + 1.4.0.3 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index cd8b9bd630c8..c5801e5606a8 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.0.0 + 1.4.0.3 diff --git a/package-lock.json b/package-lock.json index fae3dbb10ed7..a4fd6324fd7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.0-0", + "version": "1.4.0-3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.0-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 b7e2f5ad8515..6b5f44d417da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.0-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/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.tsx b/src/components/Badge.tsx index 2ccd41575073..22c056dfdfc4 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -1,7 +1,7 @@ import React, {useCallback} from 'react'; import {GestureResponderEvent, PressableStateCallbackType, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; -import styles from '@styles/styles'; 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'; @@ -33,12 +33,13 @@ type BadgeProps = { }; 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], - [success, error, environment, badgeStyles], + [styles.badge, styles.ml2, success, error, environment, badgeStyles], ); return ( 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