diff --git a/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts b/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts index b9d01702e66e..da946b78a056 100644 --- a/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts +++ b/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts @@ -1,13 +1,83 @@ import * as core from '@actions/core'; import * as github from '@actions/github'; +import type {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types'; import {getJSONInput} from '@github/libs/ActionUtils'; import GithubUtils from '@github/libs/GithubUtils'; import GitUtils from '@github/libs/GitUtils'; +type WorkflowRun = RestEndpointMethodTypes['actions']['listWorkflowRuns']['response']['data']['workflow_runs'][number]; + +const BUILD_AND_DEPLOY_JOB_NAME_PREFIX = 'Build and deploy'; + +/** + * This function checks if a given release is a valid baseTag to get the PR list with `git log baseTag...endTag`. + * + * The rules are: + * - production deploys can only be compared with other production deploys + * - staging deploys can be compared with other staging deploys or production deploys. + * The reason is that the final staging release in each deploy cycle will BECOME a production release. + * For example, imagine a checklist is closed with version 9.0.20-6; that's the most recent staging deploy, but the release for 9.0.20-6 is now finalized, so it looks like a prod deploy. + * When 9.0.21-0 finishes deploying to staging, the most recent prerelease is 9.0.20-5. However, we want 9.0.20-6...9.0.21-0, + * NOT 9.0.20-5...9.0.21-0 (so that the PR CP'd in 9.0.20-6 is not included in the next checklist) + */ +async function isReleaseValidBaseForEnvironment(releaseTag: string, isProductionDeploy: boolean) { + if (!isProductionDeploy) { + return true; + } + const isPrerelease = ( + await GithubUtils.octokit.repos.getReleaseByTag({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + tag: releaseTag, + }) + ).data.prerelease; + return !isPrerelease; +} + +/** + * Was a given platformDeploy workflow run successful on at least one platform? + */ +async function wasDeploySuccessful(runID: number) { + const jobsForWorkflowRun = ( + await GithubUtils.octokit.actions.listJobsForWorkflowRun({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + // eslint-disable-next-line @typescript-eslint/naming-convention + run_id: runID, + filter: 'latest', + }) + ).data.jobs; + return jobsForWorkflowRun.some((job) => job.name.startsWith(BUILD_AND_DEPLOY_JOB_NAME_PREFIX) && job.conclusion === 'success'); +} + +/** + * This function checks if a given deploy workflow is a valid basis for comparison when listing PRs merged between two versions. + * It returns the reason a version should be skipped, or an empty string if the version should not be skipped. + */ +async function shouldSkipVersion(lastSuccessfulDeploy: WorkflowRun, inputTag: string, isProductionDeploy: boolean): Promise { + if (!lastSuccessfulDeploy?.head_branch) { + // This should never happen. Just doing this to appease TS. + return ''; + } + + // we never want to compare a tag with itself. This check is necessary because prod deploys almost always have the same version as the last staging deploy. + // In this case, the next for wrong environment fails because the release that triggered that staging deploy is now finalized, so it looks like a prod deploy. + if (lastSuccessfulDeploy?.head_branch === inputTag) { + return `Same as input tag ${inputTag}`; + } + if (!(await isReleaseValidBaseForEnvironment(lastSuccessfulDeploy?.head_branch, isProductionDeploy))) { + return 'Was a staging deploy, we only want to compare with other production deploys'; + } + if (!(await wasDeploySuccessful(lastSuccessfulDeploy.id))) { + return 'Was an unsuccessful deploy, nothing was deployed in that version'; + } + return ''; +} + async function run() { try { const inputTag = core.getInput('TAG', {required: true}); - const isProductionDeploy = getJSONInput('IS_PRODUCTION_DEPLOY', {required: false}, false); + const isProductionDeploy = !!getJSONInput('IS_PRODUCTION_DEPLOY', {required: false}, false); const deployEnv = isProductionDeploy ? 'production' : 'staging'; console.log(`Looking for PRs deployed to ${deployEnv} in ${inputTag}...`); @@ -27,33 +97,26 @@ async function run() { // Find the most recent deploy workflow targeting the correct environment, for which at least one of the build jobs finished successfully let lastSuccessfulDeploy = completedDeploys.shift(); - while ( - lastSuccessfulDeploy?.head_branch && - (( - await GithubUtils.octokit.repos.getReleaseByTag({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - tag: lastSuccessfulDeploy.head_branch, - }) - ).data.prerelease === isProductionDeploy || - !( - await GithubUtils.octokit.actions.listJobsForWorkflowRun({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - // eslint-disable-next-line @typescript-eslint/naming-convention - run_id: lastSuccessfulDeploy.id, - filter: 'latest', - }) - ).data.jobs.some((job) => job.name.startsWith('Build and deploy') && job.conclusion === 'success')) - ) { - console.log(`Deploy was not a success: ${lastSuccessfulDeploy.html_url}, looking at the next one`); - lastSuccessfulDeploy = completedDeploys.shift(); - } if (!lastSuccessfulDeploy) { throw new Error('Could not find a prior successful deploy'); } + let reason = await shouldSkipVersion(lastSuccessfulDeploy, inputTag, isProductionDeploy); + while (lastSuccessfulDeploy && reason) { + console.log( + `Deploy of tag ${lastSuccessfulDeploy.head_branch} was not valid as a base for comparison, looking at the next one. Reason: ${reason}`, + lastSuccessfulDeploy.html_url, + ); + lastSuccessfulDeploy = completedDeploys.shift(); + + if (!lastSuccessfulDeploy) { + throw new Error('Could not find a prior successful deploy'); + } + + reason = await shouldSkipVersion(lastSuccessfulDeploy, inputTag, isProductionDeploy); + } + const priorTag = lastSuccessfulDeploy.head_branch; console.log(`Looking for PRs deployed to ${deployEnv} between ${priorTag} and ${inputTag}`); const prList = await GitUtils.getPullRequestsMergedBetween(priorTag ?? '', inputTag); diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js index 05ae086fcc24..e8bd7057d40e 100644 --- a/.github/actions/javascript/getDeployPullRequestList/index.js +++ b/.github/actions/javascript/getDeployPullRequestList/index.js @@ -11502,10 +11502,68 @@ const github = __importStar(__nccwpck_require__(5438)); const ActionUtils_1 = __nccwpck_require__(6981); const GithubUtils_1 = __importDefault(__nccwpck_require__(9296)); const GitUtils_1 = __importDefault(__nccwpck_require__(1547)); +const BUILD_AND_DEPLOY_JOB_NAME_PREFIX = 'Build and deploy'; +/** + * This function checks if a given release is a valid baseTag to get the PR list with `git log baseTag...endTag`. + * + * The rules are: + * - production deploys can only be compared with other production deploys + * - staging deploys can be compared with other staging deploys or production deploys. + * The reason is that the final staging release in each deploy cycle will BECOME a production release. + * For example, imagine a checklist is closed with version 9.0.20-6; that's the most recent staging deploy, but the release for 9.0.20-6 is now finalized, so it looks like a prod deploy. + * When 9.0.21-0 finishes deploying to staging, the most recent prerelease is 9.0.20-5. However, we want 9.0.20-6...9.0.21-0, + * NOT 9.0.20-5...9.0.21-0 (so that the PR CP'd in 9.0.20-6 is not included in the next checklist) + */ +async function isReleaseValidBaseForEnvironment(releaseTag, isProductionDeploy) { + if (!isProductionDeploy) { + return true; + } + const isPrerelease = (await GithubUtils_1.default.octokit.repos.getReleaseByTag({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + tag: releaseTag, + })).data.prerelease; + return !isPrerelease; +} +/** + * Was a given platformDeploy workflow run successful on at least one platform? + */ +async function wasDeploySuccessful(runID) { + const jobsForWorkflowRun = (await GithubUtils_1.default.octokit.actions.listJobsForWorkflowRun({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + // eslint-disable-next-line @typescript-eslint/naming-convention + run_id: runID, + filter: 'latest', + })).data.jobs; + return jobsForWorkflowRun.some((job) => job.name.startsWith(BUILD_AND_DEPLOY_JOB_NAME_PREFIX) && job.conclusion === 'success'); +} +/** + * This function checks if a given deploy workflow is a valid basis for comparison when listing PRs merged between two versions. + * It returns the reason a version should be skipped, or an empty string if the version should not be skipped. + */ +async function shouldSkipVersion(lastSuccessfulDeploy, inputTag, isProductionDeploy) { + if (!lastSuccessfulDeploy?.head_branch) { + // This should never happen. Just doing this to appease TS. + return ''; + } + // we never want to compare a tag with itself. This check is necessary because prod deploys almost always have the same version as the last staging deploy. + // In this case, the next for wrong environment fails because the release that triggered that staging deploy is now finalized, so it looks like a prod deploy. + if (lastSuccessfulDeploy?.head_branch === inputTag) { + return `Same as input tag ${inputTag}`; + } + if (!(await isReleaseValidBaseForEnvironment(lastSuccessfulDeploy?.head_branch, isProductionDeploy))) { + return 'Was a staging deploy, we only want to compare with other production deploys'; + } + if (!(await wasDeploySuccessful(lastSuccessfulDeploy.id))) { + return 'Was an unsuccessful deploy, nothing was deployed in that version'; + } + return ''; +} async function run() { try { const inputTag = core.getInput('TAG', { required: true }); - const isProductionDeploy = (0, ActionUtils_1.getJSONInput)('IS_PRODUCTION_DEPLOY', { required: false }, false); + const isProductionDeploy = !!(0, ActionUtils_1.getJSONInput)('IS_PRODUCTION_DEPLOY', { required: false }, false); const deployEnv = isProductionDeploy ? 'production' : 'staging'; console.log(`Looking for PRs deployed to ${deployEnv} in ${inputTag}...`); const completedDeploys = (await GithubUtils_1.default.octokit.actions.listWorkflowRuns({ @@ -11520,25 +11578,18 @@ async function run() { .filter((workflowRun) => workflowRun.conclusion !== 'cancelled'); // Find the most recent deploy workflow targeting the correct environment, for which at least one of the build jobs finished successfully let lastSuccessfulDeploy = completedDeploys.shift(); - while (lastSuccessfulDeploy?.head_branch && - ((await GithubUtils_1.default.octokit.repos.getReleaseByTag({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - tag: lastSuccessfulDeploy.head_branch, - })).data.prerelease === isProductionDeploy || - !(await GithubUtils_1.default.octokit.actions.listJobsForWorkflowRun({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - // eslint-disable-next-line @typescript-eslint/naming-convention - run_id: lastSuccessfulDeploy.id, - filter: 'latest', - })).data.jobs.some((job) => job.name.startsWith('Build and deploy') && job.conclusion === 'success'))) { - console.log(`Deploy was not a success: ${lastSuccessfulDeploy.html_url}, looking at the next one`); - lastSuccessfulDeploy = completedDeploys.shift(); - } if (!lastSuccessfulDeploy) { throw new Error('Could not find a prior successful deploy'); } + let reason = await shouldSkipVersion(lastSuccessfulDeploy, inputTag, isProductionDeploy); + while (lastSuccessfulDeploy && reason) { + console.log(`Deploy of tag ${lastSuccessfulDeploy.head_branch} was not valid as a base for comparison, looking at the next one. Reason: ${reason}`, lastSuccessfulDeploy.html_url); + lastSuccessfulDeploy = completedDeploys.shift(); + if (!lastSuccessfulDeploy) { + throw new Error('Could not find a prior successful deploy'); + } + reason = await shouldSkipVersion(lastSuccessfulDeploy, inputTag, isProductionDeploy); + } const priorTag = lastSuccessfulDeploy.head_branch; console.log(`Looking for PRs deployed to ${deployEnv} between ${priorTag} and ${inputTag}`); const prList = await GitUtils_1.default.getPullRequestsMergedBetween(priorTag ?? '', inputTag); diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index 0004b2d3eaf8..9f81222898ad 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -38,6 +38,7 @@ jobs: secrets: inherit android: + # WARNING: getDeployPullRequestList depends on this job name. do not change job name without adjusting that action accordingly name: Build and deploy Android needs: validateActor if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} @@ -122,6 +123,7 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} desktop: + # WARNING: getDeployPullRequestList depends on this job name. do not change job name without adjusting that action accordingly name: Build and deploy Desktop needs: validateActor if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} @@ -165,6 +167,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} iOS: + # WARNING: getDeployPullRequestList depends on this job name. do not change job name without adjusting that action accordingly name: Build and deploy iOS needs: validateActor if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} @@ -276,6 +279,7 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} web: + # WARNING: getDeployPullRequestList depends on this job name. do not change job name without adjusting that action accordingly name: Build and deploy Web needs: validateActor if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} diff --git a/android/app/build.gradle b/android/app/build.gradle index a3cff286241f..92a5ab10f314 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -108,8 +108,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009002101 - versionName "9.0.21-1" + versionCode 1009002103 + versionName "9.0.21-3" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/assets/images/box.svg b/assets/images/box.svg index ba0b3c22d8a0..05f808e801ee 100644 --- a/assets/images/box.svg +++ b/assets/images/box.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/images/product-illustrations/todd-with-phones.svg b/assets/images/product-illustrations/todd-with-phones.svg index 5992a4f408d7..21ee5a015820 100644 --- a/assets/images/product-illustrations/todd-with-phones.svg +++ b/assets/images/product-illustrations/todd-with-phones.svg @@ -1,667 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/advanced-approvals-icon-square.svg b/assets/images/simple-illustrations/advanced-approvals-icon-square.svg index 00f3de51bd42..bd71512a9846 100644 --- a/assets/images/simple-illustrations/advanced-approvals-icon-square.svg +++ b/assets/images/simple-illustrations/advanced-approvals-icon-square.svg @@ -1,86 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/emptystate__big-vault.svg b/assets/images/simple-illustrations/emptystate__big-vault.svg index 02606e39fafd..a1d18da1b117 100644 --- a/assets/images/simple-illustrations/emptystate__big-vault.svg +++ b/assets/images/simple-illustrations/emptystate__big-vault.svg @@ -1,378 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/user-check.svg b/assets/images/user-check.svg index 2da67de751f4..931f4e5f6a51 100644 --- a/assets/images/user-check.svg +++ b/assets/images/user-check.svg @@ -1,9 +1 @@ - - - - + \ No newline at end of file diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index 61baec9d9f1c..eab59a65d003 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -37,6 +37,8 @@ If you've found a vulnerability, please email security@expensify.com with the su ## Payment for Contributions We hire and pay external contributors via [Upwork.com](https://www.upwork.com). If you'd like to be paid for contributing, please create an Upwork account, apply for an available job in [GitHub](https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22), and finally apply for the job in Upwork once your proposal gets selected in GitHub. Please make sure your Upwork profile is **fully verified** before applying, otherwise you run the risk of not being paid. If you think your compensation should be increased for a specific job, you can request a reevaluation by commenting in the Github issue where the Upwork job was posted. +Please add your Upwork profile link in your GitHub Bio to help ensure prompt payment. If you're using Slack or Expensify for discussions, please add your Upwork profile link **and** your GitHub username in your Slack Title and Expensify Status. + Payment for your contributions will be made no less than 7 days after the pull request is deployed to production to allow for [regression](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#regressions) testing. If you have not received payment after 8 days of the PR being deployed to production, and there are no [regressions](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#regressions), please add a comment to the issue mentioning the BugZero team member (Look for the melvin-bot "Triggered auto assignment to... (`Bug`)" to see who this is). New contributors are limited to working on one job at a time, **do not submit proposals for new jobs until your first PR has been merged**. Experienced contributors may work on numerous jobs simultaneously. diff --git a/docs/assets/images/Duty-of-care.png b/docs/assets/images/Duty-of-care.png index f09f8254b478..8774c0983f3f 100644 Binary files a/docs/assets/images/Duty-of-care.png and b/docs/assets/images/Duty-of-care.png differ diff --git a/docs/assets/images/ExpensifyHelp-FreeTrial-1.png b/docs/assets/images/ExpensifyHelp-FreeTrial-1.png new file mode 100644 index 000000000000..28bfcaf15847 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-FreeTrial-1.png differ diff --git a/docs/assets/images/ExpensifyHelp_CreateWorkspace_1.png b/docs/assets/images/ExpensifyHelp_CreateWorkspace_1.png index 3dcf92d028ab..e221b508a799 100644 Binary files a/docs/assets/images/ExpensifyHelp_CreateWorkspace_1.png and b/docs/assets/images/ExpensifyHelp_CreateWorkspace_1.png differ diff --git a/docs/assets/images/ExpensifyHelp_CreateWorkspace_2.png b/docs/assets/images/ExpensifyHelp_CreateWorkspace_2.png index cafb106e897e..9953bfcbd281 100644 Binary files a/docs/assets/images/ExpensifyHelp_CreateWorkspace_2.png and b/docs/assets/images/ExpensifyHelp_CreateWorkspace_2.png differ diff --git a/docs/assets/images/ExpensifyHelp_CreateWorkspace_3.png b/docs/assets/images/ExpensifyHelp_CreateWorkspace_3.png index 08b553857110..f30ec7f9c1e3 100644 Binary files a/docs/assets/images/ExpensifyHelp_CreateWorkspace_3.png and b/docs/assets/images/ExpensifyHelp_CreateWorkspace_3.png differ diff --git a/docs/assets/images/ExpensifyHelp_InviteMembers_1.png b/docs/assets/images/ExpensifyHelp_InviteMembers_1.png index cba73c2ce150..12b000fbd07c 100644 Binary files a/docs/assets/images/ExpensifyHelp_InviteMembers_1.png and b/docs/assets/images/ExpensifyHelp_InviteMembers_1.png differ diff --git a/docs/assets/images/ExpensifyHelp_InviteMembers_2.png b/docs/assets/images/ExpensifyHelp_InviteMembers_2.png index e09b8ac5b2b0..169013014ab5 100644 Binary files a/docs/assets/images/ExpensifyHelp_InviteMembers_2.png and b/docs/assets/images/ExpensifyHelp_InviteMembers_2.png differ diff --git a/docs/assets/images/ExpensifyHelp_InviteMembers_3.png b/docs/assets/images/ExpensifyHelp_InviteMembers_3.png index 999e6785ae5f..c8603b7a6b3c 100644 Binary files a/docs/assets/images/ExpensifyHelp_InviteMembers_3.png and b/docs/assets/images/ExpensifyHelp_InviteMembers_3.png differ diff --git a/docs/assets/images/Export-Expenses.png b/docs/assets/images/Export-Expenses.png index 37cabda7922e..8e73577bdf62 100644 Binary files a/docs/assets/images/Export-Expenses.png and b/docs/assets/images/Export-Expenses.png differ diff --git a/docs/assets/images/QBO_classic_edit_exports.png b/docs/assets/images/QBO_classic_edit_exports.png index 037d8ac4aafa..adff19e3e60a 100644 Binary files a/docs/assets/images/QBO_classic_edit_exports.png and b/docs/assets/images/QBO_classic_edit_exports.png differ diff --git a/docs/assets/images/QBO_classic_icon.png b/docs/assets/images/QBO_classic_icon.png index fccca3bf2bce..a557b2affdf5 100644 Binary files a/docs/assets/images/QBO_classic_icon.png and b/docs/assets/images/QBO_classic_icon.png differ diff --git a/docs/assets/images/QBO_classic_report_history.png b/docs/assets/images/QBO_classic_report_history.png index 47ef39845b47..9a732b058c72 100644 Binary files a/docs/assets/images/QBO_classic_report_history.png and b/docs/assets/images/QBO_classic_report_history.png differ diff --git a/docs/assets/images/QBO_classic_troubleshooting_billable.png b/docs/assets/images/QBO_classic_troubleshooting_billable.png index 82b91e904902..876fe33dbe66 100644 Binary files a/docs/assets/images/QBO_classic_troubleshooting_billable.png and b/docs/assets/images/QBO_classic_troubleshooting_billable.png differ diff --git a/docs/assets/images/QBO_classic_troubleshooting_billable_2.png b/docs/assets/images/QBO_classic_troubleshooting_billable_2.png index cda5424cd279..5a88c48a3e5c 100644 Binary files a/docs/assets/images/QBO_classic_troubleshooting_billable_2.png and b/docs/assets/images/QBO_classic_troubleshooting_billable_2.png differ diff --git a/docs/assets/images/Travel-Analytics.png b/docs/assets/images/Travel-Analytics.png index 27f696e28bcd..c4f969b6d51c 100644 Binary files a/docs/assets/images/Travel-Analytics.png and b/docs/assets/images/Travel-Analytics.png differ diff --git a/docs/assets/images/Xero_classic_Edit_exports.png b/docs/assets/images/Xero_classic_Edit_exports.png index 4bfc8dcf70cc..25bda4a9454a 100644 Binary files a/docs/assets/images/Xero_classic_Edit_exports.png and b/docs/assets/images/Xero_classic_Edit_exports.png differ diff --git a/docs/assets/images/Xero_classic_bank_transaction.png b/docs/assets/images/Xero_classic_bank_transaction.png index ff4080a3756a..b730e65569a6 100644 Binary files a/docs/assets/images/Xero_classic_bank_transaction.png and b/docs/assets/images/Xero_classic_bank_transaction.png differ diff --git a/docs/assets/images/Xero_classic_category_icon.png b/docs/assets/images/Xero_classic_category_icon.png index ecc1b51e2548..50360ecdae54 100644 Binary files a/docs/assets/images/Xero_classic_category_icon.png and b/docs/assets/images/Xero_classic_category_icon.png differ diff --git a/docs/assets/images/Xero_classic_copy.png b/docs/assets/images/Xero_classic_copy.png index 5734b57e216d..ff67fecad252 100644 Binary files a/docs/assets/images/Xero_classic_copy.png and b/docs/assets/images/Xero_classic_copy.png differ diff --git a/docs/assets/images/Xero_classic_new_connection.png b/docs/assets/images/Xero_classic_new_connection.png index a798572a1302..bb7183262e19 100644 Binary files a/docs/assets/images/Xero_classic_new_connection.png and b/docs/assets/images/Xero_classic_new_connection.png differ diff --git a/docs/assets/images/Xero_classic_troubleshoot_category.png b/docs/assets/images/Xero_classic_troubleshoot_category.png index fb1b25070521..af6b8c2ffa0a 100644 Binary files a/docs/assets/images/Xero_classic_troubleshoot_category.png and b/docs/assets/images/Xero_classic_troubleshoot_category.png differ diff --git a/docs/assets/images/Xero_classic_troubleshoot_payment.png b/docs/assets/images/Xero_classic_troubleshoot_payment.png index 7ce1bf1ef86f..441324ebf1b1 100644 Binary files a/docs/assets/images/Xero_classic_troubleshoot_payment.png and b/docs/assets/images/Xero_classic_troubleshoot_payment.png differ diff --git a/docs/assets/images/Xero_classic_troubleshoot_remove_redo.png b/docs/assets/images/Xero_classic_troubleshoot_remove_redo.png index c8a5dac029f5..2028457c9555 100644 Binary files a/docs/assets/images/Xero_classic_troubleshoot_remove_redo.png and b/docs/assets/images/Xero_classic_troubleshoot_remove_redo.png differ diff --git a/docs/assets/images/Xero_help_01.png b/docs/assets/images/Xero_help_01.png index ce05ea83c925..ce60204866f7 100644 Binary files a/docs/assets/images/Xero_help_01.png and b/docs/assets/images/Xero_help_01.png differ diff --git a/docs/assets/images/Xero_help_02.png b/docs/assets/images/Xero_help_02.png index c2d556c7aed0..8ed2f256cf0f 100644 Binary files a/docs/assets/images/Xero_help_02.png and b/docs/assets/images/Xero_help_02.png differ diff --git a/docs/assets/images/Xero_help_03.png b/docs/assets/images/Xero_help_03.png index 30616ffd3d64..318bb274b115 100644 Binary files a/docs/assets/images/Xero_help_03.png and b/docs/assets/images/Xero_help_03.png differ diff --git a/docs/assets/images/Xero_help_04.png b/docs/assets/images/Xero_help_04.png index d0e950d3968a..b0d29bc9fde6 100644 Binary files a/docs/assets/images/Xero_help_04.png and b/docs/assets/images/Xero_help_04.png differ diff --git a/docs/assets/images/Xero_help_05.png b/docs/assets/images/Xero_help_05.png index be65e9c62960..e8265e82b652 100644 Binary files a/docs/assets/images/Xero_help_05.png and b/docs/assets/images/Xero_help_05.png differ diff --git a/docs/redirects.csv b/docs/redirects.csv index 97d05fdc3248..f662533358cc 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -1,4 +1,5 @@ sourceURL,targetURL +https://community.expensify.com,https://help.expensify.com https://community.expensify.com/discussion/5634/deep-dive-how-long-will-it-take-for-me-to-receive-my-reimbursement,https://help.expensify.com/articles/expensify-classic/expenses/reports/Reimbursements https://community.expensify.com/discussion/4925/how-to-dispute-an-expensify-card-transaction,https://help.expensify.com/articles/expensify-classic/expensify-card/Dispute-A-Transaction https://community.expensify.com/discussion/5184/faq-how-am-i-protected-from-fraud-using-the-expensify-card,https://help.expensify.com/articles/expensify-classic/expensify-card/Dispute-A-Transaction diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 29a93b364afb..1e399278b7c6 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.21.1 + 9.0.21.3 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 01f3b9f2c22c..d617a49380cd 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.21.1 + 9.0.21.3 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 929c1e66e8d0..955b0991eca0 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.21 CFBundleVersion - 9.0.21.1 + 9.0.21.3 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 297269156d06..4c1ede673a79 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1303,7 +1303,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-keyboard-controller (1.12.2): + - react-native-keyboard-controller (1.13.0): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2565,7 +2565,7 @@ SPEC CHECKSUMS: react-native-geolocation: 580c86eb531c0aaf7a14bc76fd2983ce47ca58aa react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: 28ccfa09520e7d7e30739480dea4df003493bfe8 - react-native-keyboard-controller: 47c01b0741ae5fc84e53cf282e61cfa5c2edb19b + react-native-keyboard-controller: a8cbf848d0bc0e1976a07948f1c53b8432c1246c react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: 02d31de0e08ab043d48f2a1a8baade109d7b6ca5 react-native-pager-view: ccd4bbf9fc7effaf8f91f8dae43389844d9ef9fa diff --git a/package-lock.json b/package-lock.json index 94b4f565347e..a85076a976b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.21-1", + "version": "9.0.21-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.21-1", + "version": "9.0.21-3", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -97,7 +97,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#93399c6410de32966eb57085936ef6951398c2c3", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.12.2", + "react-native-keyboard-controller": "1.13.0", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", @@ -37470,13 +37470,13 @@ "license": "MIT" }, "node_modules/react-native-keyboard-controller": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.12.2.tgz", - "integrity": "sha512-10Sy0+neSHGJxOmOxrUJR8TQznnrQ+jTFQtM1PP6YnblNQeAw1eOa+lO6YLGenRr5WuNSMZbks/3Ay0e2yMKLw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.13.0.tgz", + "integrity": "sha512-bMRx94Mt+HOkh6WRkShYgbszUcF+S9OWYHUT+DhFK9/nm1AxmDoMQKorDdb4XUXJgxrDe2k46fx4eAA6BZm/wA==", "peerDependencies": { "react": "*", "react-native": "*", - "react-native-reanimated": ">=2.3.0" + "react-native-reanimated": ">=3.0.0" } }, "node_modules/react-native-launch-arguments": { diff --git a/package.json b/package.json index d525567d7fd4..4d0974d8fe70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.21-1", + "version": "9.0.21-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.", @@ -153,7 +153,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#93399c6410de32966eb57085936ef6951398c2c3", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.12.2", + "react-native-keyboard-controller": "1.13.0", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", diff --git a/patches/react-native-keyboard-controller+1.12.2.patch b/patches/react-native-keyboard-controller+1.13.0.patch similarity index 57% rename from patches/react-native-keyboard-controller+1.12.2.patch rename to patches/react-native-keyboard-controller+1.13.0.patch index 3c8034354481..b376c9a5c022 100644 --- a/patches/react-native-keyboard-controller+1.12.2.patch +++ b/patches/react-native-keyboard-controller+1.13.0.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -index 83884d8..5d9e989 100644 +index 6e566fc..07ccb3c 100644 --- a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +++ b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -@@ -99,12 +99,12 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R +@@ -117,12 +117,12 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R } private fun goToEdgeToEdge(edgeToEdge: Boolean) { @@ -12,22 +12,24 @@ index 83884d8..5d9e989 100644 - !edgeToEdge, - ) - } -+ // reactContext.currentActivity?.let { -+ // WindowCompat.setDecorFitsSystemWindows( -+ // it.window, -+ // !edgeToEdge, -+ // ) -+ // } ++ // reactContext.currentActivity?.let { ++ // WindowCompat.setDecorFitsSystemWindows( ++ // it.window, ++ // !edgeToEdge, ++ // ) ++ // } } private fun setupKeyboardCallbacks() { -@@ -158,13 +158,13 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R +@@ -169,16 +169,16 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R // region State managers private fun enable() { this.goToEdgeToEdge(true) - this.setupWindowInsets() + // this.setupWindowInsets() this.setupKeyboardCallbacks() +- modalAttachedWatcher.enable() ++ // modalAttachedWatcher.enable() } private fun disable() { @@ -35,5 +37,17 @@ index 83884d8..5d9e989 100644 - this.setupWindowInsets() + // this.setupWindowInsets() this.removeKeyboardCallbacks() +- modalAttachedWatcher.disable() ++ // modalAttachedWatcher.disable() + } + // endregion + +@@ -206,7 +206,7 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R + fun forceStatusBarTranslucent(isStatusBarTranslucent: Boolean) { + if (active && this.isStatusBarTranslucent != isStatusBarTranslucent) { + this.isStatusBarTranslucent = isStatusBarTranslucent +- this.setupWindowInsets() ++ // this.setupWindowInsets() + this.requestApplyInsetsWhenAttached() + } } - // endregion \ No newline at end of file diff --git a/src/CONST.ts b/src/CONST.ts index f1e4a3bb46d5..a9160e606bd9 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -911,6 +911,7 @@ const CONST = { WRITE: 'write', SHARE: 'share', OWN: 'own', + AUDITOR: 'auditor', }, INVOICE_RECEIVER_TYPE: { INDIVIDUAL: 'individual', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 23b56d921c4d..b7b6cf53a176 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -571,6 +571,10 @@ const ONYXKEYS = { REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft', PERSONAL_BANK_ACCOUNT_FORM: 'personalBankAccount', PERSONAL_BANK_ACCOUNT_FORM_DRAFT: 'personalBankAccountDraft', + DISABLE_AUTO_RENEW_SURVEY_FORM: 'disableAutoRenewSurveyForm', + DISABLE_AUTO_RENEW_SURVEY_FORM_DRAFT: 'disableAutoRenewSurveyFormDraft', + REQUEST_EARLY_CANCELLATION_FORM: 'requestEarlyCancellationForm', + REQUEST_EARLY_CANCELLATION_FORM_DRAFT: 'requestEarlyCancellationFormDraft', EXIT_SURVEY_REASON_FORM: 'exitSurveyReasonForm', EXIT_SURVEY_REASON_FORM_DRAFT: 'exitSurveyReasonFormDraft', EXIT_SURVEY_RESPONSE_FORM: 'exitSurveyResponseForm', @@ -647,6 +651,8 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.ROOM_SETTINGS_FORM]: FormTypes.RoomSettingsForm; [ONYXKEYS.FORMS.NEW_TASK_FORM]: FormTypes.NewTaskForm; [ONYXKEYS.FORMS.EDIT_TASK_FORM]: FormTypes.EditTaskForm; + [ONYXKEYS.FORMS.DISABLE_AUTO_RENEW_SURVEY_FORM]: FormTypes.FeedbackSurveyForm; + [ONYXKEYS.FORMS.REQUEST_EARLY_CANCELLATION_FORM]: FormTypes.FeedbackSurveyForm; [ONYXKEYS.FORMS.EXIT_SURVEY_REASON_FORM]: FormTypes.ExitSurveyReasonForm; [ONYXKEYS.FORMS.EXIT_SURVEY_RESPONSE_FORM]: FormTypes.ExitSurveyResponseForm; [ONYXKEYS.FORMS.MONEY_REQUEST_DESCRIPTION_FORM]: FormTypes.MoneyRequestDescriptionForm; diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index 9bae189ac183..a5ac2c84eb2b 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -39,7 +39,7 @@ function EmptySelectionListContent({contentType}: EmptySelectionListContentProps ); return ( - + = {borderColor: theme.border}; - const [reason, setReason] = useState