diff --git a/.eslintignore b/.eslintignore index 5338df11c520..aa8b769dfede 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,3 +9,4 @@ android/**/build/** docs/vendor/** docs/assets/** web/gtm.js +**/.expo/** diff --git a/.eslintrc.js b/.eslintrc.js index 9f839e45ce75..d8f8c15eb878 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,5 @@ +const path = require('path'); + const restrictedImportPaths = [ { name: 'react-native', @@ -96,7 +98,7 @@ module.exports = { plugins: ['@typescript-eslint', 'jsdoc', 'you-dont-need-lodash-underscore', 'react-native-a11y', 'react', 'testing-library'], parser: '@typescript-eslint/parser', parserOptions: { - project: './tsconfig.json', + project: path.resolve(__dirname, './tsconfig.json'), }, env: { jest: true, @@ -105,10 +107,9 @@ module.exports = { __DEV__: 'readonly', }, rules: { + // TypeScript specific rules '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', - - // TypeScript specific rules '@typescript-eslint/prefer-enum-initializers': 'error', '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-non-null-assertion': 'error', @@ -215,6 +216,8 @@ module.exports = { // Other rules curly: 'error', 'you-dont-need-lodash-underscore/throttle': 'off', + // The suggested alternative (structuredClone) is not supported in Hermes:https://github.com/facebook/hermes/issues/684 + 'you-dont-need-lodash-underscore/clone-deep': 'off', 'prefer-regex-literals': 'off', 'valid-jsdoc': 'off', 'jsdoc/no-types': 'error', @@ -257,6 +260,7 @@ module.exports = { // Remove once no JS files are left { files: ['*.js', '*.jsx'], + extends: ['plugin:@typescript-eslint/disable-type-checked'], rules: { '@typescript-eslint/prefer-nullish-coalescing': 'off', '@typescript-eslint/no-unsafe-return': 'off', diff --git a/.github/actions/javascript/authorChecklist/index.js b/.github/actions/javascript/authorChecklist/index.js index 1dace19d0a01..84624b6bfc6f 100644 --- a/.github/actions/javascript/authorChecklist/index.js +++ b/.github/actions/javascript/authorChecklist/index.js @@ -11710,7 +11710,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -17557,27 +17557,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/awaitStagingDeploys/index.js b/.github/actions/javascript/awaitStagingDeploys/index.js index c5ba4d9ca936..6ec9bbf06aeb 100644 --- a/.github/actions/javascript/awaitStagingDeploys/index.js +++ b/.github/actions/javascript/awaitStagingDeploys/index.js @@ -7373,7 +7373,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -12822,27 +12822,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/bumpVersion/bumpVersion.ts b/.github/actions/javascript/bumpVersion/bumpVersion.ts index eba79c7c9edb..ff43ab9ee5c5 100644 --- a/.github/actions/javascript/bumpVersion/bumpVersion.ts +++ b/.github/actions/javascript/bumpVersion/bumpVersion.ts @@ -5,6 +5,7 @@ import type {PackageJson} from 'type-fest'; import {promisify} from 'util'; import {generateAndroidVersionCode, updateAndroidVersion, updateiOSVersion} from '@github/libs/nativeVersionUpdater'; import * as versionUpdater from '@github/libs/versionUpdater'; +import type {SemverLevel} from '@github/libs/versionUpdater'; const exec = promisify(originalExec); @@ -43,7 +44,7 @@ function updateNativeVersions(version: string) { } let semanticVersionLevel = core.getInput('SEMVER_LEVEL', {required: true}); -if (!semanticVersionLevel || !Object.keys(versionUpdater.SEMANTIC_VERSION_LEVELS).includes(semanticVersionLevel)) { +if (!semanticVersionLevel || !versionUpdater.isValidSemverLevel(semanticVersionLevel)) { semanticVersionLevel = versionUpdater.SEMANTIC_VERSION_LEVELS.BUILD; console.log(`Invalid input for 'SEMVER_LEVEL': ${semanticVersionLevel}`, `Defaulting to: ${semanticVersionLevel}`); } @@ -53,7 +54,7 @@ if (!previousVersion) { core.setFailed('Error: Could not read package.json'); } -const newVersion = versionUpdater.incrementVersion(previousVersion ?? '', semanticVersionLevel); +const newVersion = versionUpdater.incrementVersion(previousVersion ?? '', semanticVersionLevel as SemverLevel); console.log(`Previous version: ${previousVersion}`, `New version: ${newVersion}`); updateNativeVersions(newVersion); diff --git a/.github/actions/javascript/bumpVersion/index.js b/.github/actions/javascript/bumpVersion/index.js index e1a5cf13a8d9..93ea47bed2ae 100644 --- a/.github/actions/javascript/bumpVersion/index.js +++ b/.github/actions/javascript/bumpVersion/index.js @@ -3473,7 +3473,7 @@ function updateNativeVersions(version) { } } let semanticVersionLevel = core.getInput('SEMVER_LEVEL', { required: true }); -if (!semanticVersionLevel || !Object.keys(versionUpdater.SEMANTIC_VERSION_LEVELS).includes(semanticVersionLevel)) { +if (!semanticVersionLevel || !versionUpdater.isValidSemverLevel(semanticVersionLevel)) { semanticVersionLevel = versionUpdater.SEMANTIC_VERSION_LEVELS.BUILD; console.log(`Invalid input for 'SEMVER_LEVEL': ${semanticVersionLevel}`, `Defaulting to: ${semanticVersionLevel}`); } @@ -3589,7 +3589,7 @@ exports.updateiOSVersion = updateiOSVersion; "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getPreviousVersion = exports.incrementPatch = exports.incrementMinor = exports.SEMANTIC_VERSION_LEVELS = exports.MAX_INCREMENTS = exports.incrementVersion = exports.getVersionStringFromNumber = exports.getVersionNumberFromString = void 0; +exports.getPreviousVersion = exports.incrementPatch = exports.incrementMinor = exports.SEMANTIC_VERSION_LEVELS = exports.MAX_INCREMENTS = exports.incrementVersion = exports.getVersionStringFromNumber = exports.getVersionNumberFromString = exports.isValidSemverLevel = void 0; const SEMANTIC_VERSION_LEVELS = { MAJOR: 'MAJOR', MINOR: 'MINOR', @@ -3599,6 +3599,10 @@ const SEMANTIC_VERSION_LEVELS = { exports.SEMANTIC_VERSION_LEVELS = SEMANTIC_VERSION_LEVELS; const MAX_INCREMENTS = 99; exports.MAX_INCREMENTS = MAX_INCREMENTS; +function isValidSemverLevel(str) { + return Object.keys(SEMANTIC_VERSION_LEVELS).includes(str); +} +exports.isValidSemverLevel = isValidSemverLevel; /** * Transforms a versions string into a number */ diff --git a/.github/actions/javascript/checkDeployBlockers/index.js b/.github/actions/javascript/checkDeployBlockers/index.js index 0817a8130adc..c6518f4e61c9 100644 --- a/.github/actions/javascript/checkDeployBlockers/index.js +++ b/.github/actions/javascript/checkDeployBlockers/index.js @@ -6679,7 +6679,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -12050,27 +12050,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js index 12b3165c1ead..260dd0847c8d 100644 --- a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js @@ -9434,7 +9434,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -14401,7 +14401,66 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); const child_process_1 = __nccwpck_require__(2081); const CONST_1 = __importDefault(__nccwpck_require__(9873)); const sanitizeStringForJSONParse_1 = __importDefault(__nccwpck_require__(3902)); -const VERSION_UPDATER = __importStar(__nccwpck_require__(8982)); +const VersionUpdater = __importStar(__nccwpck_require__(8982)); +/** + * Check if a tag exists locally or in the remote. + */ +function tagExists(tag) { + try { + // Check if the tag exists locally + (0, child_process_1.execSync)(`git show-ref --tags ${tag}`, { stdio: 'ignore' }); + return true; // Tag exists locally + } + catch (error) { + // Tag does not exist locally, check in remote + let shouldRetry = true; + let needsRepack = false; + let doesTagExist = 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 + (0, child_process_1.execSync)('git repack -d', { stdio: 'inherit' }); + } + (0, child_process_1.execSync)(`git ls-remote --exit-code --tags origin ${tag}`, { stdio: 'ignore' }); + doesTagExist = true; + shouldRetry = false; + } + catch (e) { + if (!needsRepack) { + console.log('Attempting to repack and retry...'); + needsRepack = true; + } + else { + console.error("Repack didn't help, giving up..."); + shouldRetry = false; + } + } + } + return doesTagExist; + } +} +/** + * This essentially just calls getPreviousVersion in a loop, until it finds a version for which a tag exists. + * It's useful if we manually perform a version bump, because in that case a tag may not exist for the previous version. + * + * @param tag the current tag + * @param level the Semver level to step backward by + */ +function getPreviousExistingTag(tag, level) { + let previousVersion = VersionUpdater.getPreviousVersion(tag, level); + let tagExistsForPreviousVersion = false; + while (!tagExistsForPreviousVersion) { + if (tagExists(previousVersion)) { + tagExistsForPreviousVersion = true; + break; + } + console.log(`Tag for previous version ${previousVersion} does not exist. Checking for an older version...`); + previousVersion = VersionUpdater.getPreviousVersion(previousVersion, level); + } + return previousVersion; +} /** * @param [shallowExcludeTag] When fetching the given tag, exclude all history reachable by the shallowExcludeTag (used to make fetch much faster) */ @@ -14444,8 +14503,8 @@ function fetchTag(tag, shallowExcludeTag = '') { * Get merge logs between two tags (inclusive) as a JavaScript object. */ function getCommitHistoryAsJSON(fromTag, toTag) { - // Fetch tags, exclude commits reachable from the previous patch version (i.e: previous checklist), so that we don't have to fetch the full history - const previousPatchVersion = VERSION_UPDATER.getPreviousVersion(fromTag, VERSION_UPDATER.SEMANTIC_VERSION_LEVELS.PATCH); + // Fetch tags, excluding commits reachable from the previous patch version (i.e: previous checklist), so that we don't have to fetch the full history + const previousPatchVersion = getPreviousExistingTag(fromTag, VersionUpdater.SEMANTIC_VERSION_LEVELS.PATCH); fetchTag(fromTag, previousPatchVersion); fetchTag(toTag, previousPatchVersion); console.log('Getting pull requests merged between the following tags:', fromTag, toTag); @@ -14517,6 +14576,7 @@ async function getPullRequestsMergedBetween(fromTag, toTag) { return pullRequestNumbers; } exports["default"] = { + getPreviousExistingTag, getValidMergedPRs, getPullRequestsMergedBetween, }; @@ -15026,7 +15086,7 @@ exports["default"] = sanitizeStringForJSONParse; "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getPreviousVersion = exports.incrementPatch = exports.incrementMinor = exports.SEMANTIC_VERSION_LEVELS = exports.MAX_INCREMENTS = exports.incrementVersion = exports.getVersionStringFromNumber = exports.getVersionNumberFromString = void 0; +exports.getPreviousVersion = exports.incrementPatch = exports.incrementMinor = exports.SEMANTIC_VERSION_LEVELS = exports.MAX_INCREMENTS = exports.incrementVersion = exports.getVersionStringFromNumber = exports.getVersionNumberFromString = exports.isValidSemverLevel = void 0; const SEMANTIC_VERSION_LEVELS = { MAJOR: 'MAJOR', MINOR: 'MINOR', @@ -15036,6 +15096,10 @@ const SEMANTIC_VERSION_LEVELS = { exports.SEMANTIC_VERSION_LEVELS = SEMANTIC_VERSION_LEVELS; const MAX_INCREMENTS = 99; exports.MAX_INCREMENTS = MAX_INCREMENTS; +function isValidSemverLevel(str) { + return Object.keys(SEMANTIC_VERSION_LEVELS).includes(str); +} +exports.isValidSemverLevel = isValidSemverLevel; /** * Transforms a versions string into a number */ @@ -15152,14 +15216,6 @@ function arrayDifference(array1, array2) { exports["default"] = arrayDifference; -/***/ }), - -/***/ 2877: -/***/ ((module) => { - -module.exports = eval("require")("encoding"); - - /***/ }), /***/ 9491: @@ -15186,6 +15242,14 @@ module.exports = require("crypto"); /***/ }), +/***/ 3975: +/***/ ((module) => { + +"use strict"; +module.exports = require("encoding"); + +/***/ }), + /***/ 2361: /***/ ((module) => { diff --git a/.github/actions/javascript/getArtifactInfo/index.js b/.github/actions/javascript/getArtifactInfo/index.js index 9a7a0ae62e53..cde9cf8748db 100644 --- a/.github/actions/javascript/getArtifactInfo/index.js +++ b/.github/actions/javascript/getArtifactInfo/index.js @@ -6679,7 +6679,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -12011,27 +12011,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js index f63cd2a14f5b..6b956f17be25 100644 --- a/.github/actions/javascript/getDeployPullRequestList/index.js +++ b/.github/actions/javascript/getDeployPullRequestList/index.js @@ -6723,7 +6723,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -11665,7 +11665,66 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); const child_process_1 = __nccwpck_require__(2081); const CONST_1 = __importDefault(__nccwpck_require__(9873)); const sanitizeStringForJSONParse_1 = __importDefault(__nccwpck_require__(3902)); -const VERSION_UPDATER = __importStar(__nccwpck_require__(8982)); +const VersionUpdater = __importStar(__nccwpck_require__(8982)); +/** + * Check if a tag exists locally or in the remote. + */ +function tagExists(tag) { + try { + // Check if the tag exists locally + (0, child_process_1.execSync)(`git show-ref --tags ${tag}`, { stdio: 'ignore' }); + return true; // Tag exists locally + } + catch (error) { + // Tag does not exist locally, check in remote + let shouldRetry = true; + let needsRepack = false; + let doesTagExist = 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 + (0, child_process_1.execSync)('git repack -d', { stdio: 'inherit' }); + } + (0, child_process_1.execSync)(`git ls-remote --exit-code --tags origin ${tag}`, { stdio: 'ignore' }); + doesTagExist = true; + shouldRetry = false; + } + catch (e) { + if (!needsRepack) { + console.log('Attempting to repack and retry...'); + needsRepack = true; + } + else { + console.error("Repack didn't help, giving up..."); + shouldRetry = false; + } + } + } + return doesTagExist; + } +} +/** + * This essentially just calls getPreviousVersion in a loop, until it finds a version for which a tag exists. + * It's useful if we manually perform a version bump, because in that case a tag may not exist for the previous version. + * + * @param tag the current tag + * @param level the Semver level to step backward by + */ +function getPreviousExistingTag(tag, level) { + let previousVersion = VersionUpdater.getPreviousVersion(tag, level); + let tagExistsForPreviousVersion = false; + while (!tagExistsForPreviousVersion) { + if (tagExists(previousVersion)) { + tagExistsForPreviousVersion = true; + break; + } + console.log(`Tag for previous version ${previousVersion} does not exist. Checking for an older version...`); + previousVersion = VersionUpdater.getPreviousVersion(previousVersion, level); + } + return previousVersion; +} /** * @param [shallowExcludeTag] When fetching the given tag, exclude all history reachable by the shallowExcludeTag (used to make fetch much faster) */ @@ -11708,8 +11767,8 @@ function fetchTag(tag, shallowExcludeTag = '') { * Get merge logs between two tags (inclusive) as a JavaScript object. */ function getCommitHistoryAsJSON(fromTag, toTag) { - // Fetch tags, exclude commits reachable from the previous patch version (i.e: previous checklist), so that we don't have to fetch the full history - const previousPatchVersion = VERSION_UPDATER.getPreviousVersion(fromTag, VERSION_UPDATER.SEMANTIC_VERSION_LEVELS.PATCH); + // Fetch tags, excluding commits reachable from the previous patch version (i.e: previous checklist), so that we don't have to fetch the full history + const previousPatchVersion = getPreviousExistingTag(fromTag, VersionUpdater.SEMANTIC_VERSION_LEVELS.PATCH); fetchTag(fromTag, previousPatchVersion); fetchTag(toTag, previousPatchVersion); console.log('Getting pull requests merged between the following tags:', fromTag, toTag); @@ -11781,6 +11840,7 @@ async function getPullRequestsMergedBetween(fromTag, toTag) { return pullRequestNumbers; } exports["default"] = { + getPreviousExistingTag, getValidMergedPRs, getPullRequestsMergedBetween, }; @@ -12290,7 +12350,7 @@ exports["default"] = sanitizeStringForJSONParse; "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getPreviousVersion = exports.incrementPatch = exports.incrementMinor = exports.SEMANTIC_VERSION_LEVELS = exports.MAX_INCREMENTS = exports.incrementVersion = exports.getVersionStringFromNumber = exports.getVersionNumberFromString = void 0; +exports.getPreviousVersion = exports.incrementPatch = exports.incrementMinor = exports.SEMANTIC_VERSION_LEVELS = exports.MAX_INCREMENTS = exports.incrementVersion = exports.getVersionStringFromNumber = exports.getVersionNumberFromString = exports.isValidSemverLevel = void 0; const SEMANTIC_VERSION_LEVELS = { MAJOR: 'MAJOR', MINOR: 'MINOR', @@ -12300,6 +12360,10 @@ const SEMANTIC_VERSION_LEVELS = { exports.SEMANTIC_VERSION_LEVELS = SEMANTIC_VERSION_LEVELS; const MAX_INCREMENTS = 99; exports.MAX_INCREMENTS = MAX_INCREMENTS; +function isValidSemverLevel(str) { + return Object.keys(SEMANTIC_VERSION_LEVELS).includes(str); +} +exports.isValidSemverLevel = isValidSemverLevel; /** * Transforms a versions string into a number */ @@ -12416,14 +12480,6 @@ function arrayDifference(array1, array2) { exports["default"] = arrayDifference; -/***/ }), - -/***/ 2877: -/***/ ((module) => { - -module.exports = eval("require")("encoding"); - - /***/ }), /***/ 9491: @@ -12450,6 +12506,14 @@ module.exports = require("crypto"); /***/ }), +/***/ 3975: +/***/ ((module) => { + +"use strict"; +module.exports = require("encoding"); + +/***/ }), + /***/ 2361: /***/ ((module) => { diff --git a/.github/actions/javascript/getPreviousVersion/getPreviousVersion.ts b/.github/actions/javascript/getPreviousVersion/getPreviousVersion.ts index 262b603124fa..a178d4073cbb 100644 --- a/.github/actions/javascript/getPreviousVersion/getPreviousVersion.ts +++ b/.github/actions/javascript/getPreviousVersion/getPreviousVersion.ts @@ -1,17 +1,29 @@ import * as core from '@actions/core'; import {readFileSync} from 'fs'; import type {PackageJson} from 'type-fest'; +import GitUtils from '@github/libs/GitUtils'; import * as versionUpdater from '@github/libs/versionUpdater'; +import type {SemverLevel} from '@github/libs/versionUpdater'; -const semverLevel = core.getInput('SEMVER_LEVEL', {required: true}); -if (!semverLevel || !Object.values(versionUpdater.SEMANTIC_VERSION_LEVELS).includes(semverLevel)) { - core.setFailed(`'Error: Invalid input for 'SEMVER_LEVEL': ${semverLevel}`); +function run() { + const semverLevel = core.getInput('SEMVER_LEVEL', {required: true}); + if (!semverLevel || !versionUpdater.isValidSemverLevel(semverLevel)) { + core.setFailed(`'Error: Invalid input for 'SEMVER_LEVEL': ${semverLevel}`); + } + + const {version: currentVersion}: PackageJson = JSON.parse(readFileSync('./package.json', 'utf8')); + if (!currentVersion) { + core.setFailed('Error: Could not read package.json'); + } + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const previousVersion = GitUtils.getPreviousExistingTag(currentVersion!, semverLevel as SemverLevel); + core.setOutput('PREVIOUS_VERSION', previousVersion); + return previousVersion; } -const {version: currentVersion}: PackageJson = JSON.parse(readFileSync('./package.json', 'utf8')); -if (!currentVersion) { - core.setFailed('Error: Could not read package.json'); +if (require.main === module) { + run(); } -const previousVersion = versionUpdater.getPreviousVersion(currentVersion ?? '', semverLevel); -core.setOutput('PREVIOUS_VERSION', previousVersion); +export default run; diff --git a/.github/actions/javascript/getPreviousVersion/index.js b/.github/actions/javascript/getPreviousVersion/index.js index 8eac2f62f03e..29d02cc4dbac 100644 --- a/.github/actions/javascript/getPreviousVersion/index.js +++ b/.github/actions/javascript/getPreviousVersion/index.js @@ -2719,20 +2719,316 @@ var __importStar = (this && this.__importStar) || function (mod) { __setModuleDefault(result, mod); return result; }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(186)); const fs_1 = __nccwpck_require__(147); +const GitUtils_1 = __importDefault(__nccwpck_require__(547)); const versionUpdater = __importStar(__nccwpck_require__(982)); -const semverLevel = core.getInput('SEMVER_LEVEL', { required: true }); -if (!semverLevel || !Object.values(versionUpdater.SEMANTIC_VERSION_LEVELS).includes(semverLevel)) { - core.setFailed(`'Error: Invalid input for 'SEMVER_LEVEL': ${semverLevel}`); +function run() { + const semverLevel = core.getInput('SEMVER_LEVEL', { required: true }); + if (!semverLevel || !versionUpdater.isValidSemverLevel(semverLevel)) { + core.setFailed(`'Error: Invalid input for 'SEMVER_LEVEL': ${semverLevel}`); + } + const { version: currentVersion } = JSON.parse((0, fs_1.readFileSync)('./package.json', 'utf8')); + if (!currentVersion) { + core.setFailed('Error: Could not read package.json'); + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const previousVersion = GitUtils_1.default.getPreviousExistingTag(currentVersion, semverLevel); + core.setOutput('PREVIOUS_VERSION', previousVersion); + return previousVersion; +} +if (require.main === require.cache[eval('__filename')]) { + run(); +} +exports["default"] = run; + + +/***/ }), + +/***/ 873: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); +const GIT_CONST = { + GITHUB_OWNER: 'Expensify', + APP_REPO: 'App', +}; +const CONST = { + ...GIT_CONST, + APPLAUSE_BOT: 'applausebot', + OS_BOTIFY: 'OSBotify', + LABELS: { + STAGING_DEPLOY: 'StagingDeployCash', + DEPLOY_BLOCKER: 'DeployBlockerCash', + INTERNAL_QA: 'InternalQA', + }, + DATE_FORMAT_STRING: 'yyyy-MM-dd', + PULL_REQUEST_REGEX: new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`), + ISSUE_REGEX: new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`), + ISSUE_OR_PULL_REQUEST_REGEX: new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`), + POLL_RATE: 10000, + APP_REPO_URL: `https://github.com/${GIT_CONST.GITHUB_OWNER}/${GIT_CONST.APP_REPO}`, + APP_REPO_GIT_URL: `git@github.com:${GIT_CONST.GITHUB_OWNER}/${GIT_CONST.APP_REPO}.git`, +}; +exports["default"] = CONST; + + +/***/ }), + +/***/ 547: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +const child_process_1 = __nccwpck_require__(81); +const CONST_1 = __importDefault(__nccwpck_require__(873)); +const sanitizeStringForJSONParse_1 = __importDefault(__nccwpck_require__(902)); +const VersionUpdater = __importStar(__nccwpck_require__(982)); +/** + * Check if a tag exists locally or in the remote. + */ +function tagExists(tag) { + try { + // Check if the tag exists locally + (0, child_process_1.execSync)(`git show-ref --tags ${tag}`, { stdio: 'ignore' }); + return true; // Tag exists locally + } + catch (error) { + // Tag does not exist locally, check in remote + let shouldRetry = true; + let needsRepack = false; + let doesTagExist = 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 + (0, child_process_1.execSync)('git repack -d', { stdio: 'inherit' }); + } + (0, child_process_1.execSync)(`git ls-remote --exit-code --tags origin ${tag}`, { stdio: 'ignore' }); + doesTagExist = true; + shouldRetry = false; + } + catch (e) { + if (!needsRepack) { + console.log('Attempting to repack and retry...'); + needsRepack = true; + } + else { + console.error("Repack didn't help, giving up..."); + shouldRetry = false; + } + } + } + return doesTagExist; + } +} +/** + * This essentially just calls getPreviousVersion in a loop, until it finds a version for which a tag exists. + * It's useful if we manually perform a version bump, because in that case a tag may not exist for the previous version. + * + * @param tag the current tag + * @param level the Semver level to step backward by + */ +function getPreviousExistingTag(tag, level) { + let previousVersion = VersionUpdater.getPreviousVersion(tag, level); + let tagExistsForPreviousVersion = false; + while (!tagExistsForPreviousVersion) { + if (tagExists(previousVersion)) { + tagExistsForPreviousVersion = true; + break; + } + console.log(`Tag for previous version ${previousVersion} does not exist. Checking for an older version...`); + previousVersion = VersionUpdater.getPreviousVersion(previousVersion, level); + } + return previousVersion; } -const { version: currentVersion } = JSON.parse((0, fs_1.readFileSync)('./package.json', 'utf8')); -if (!currentVersion) { - core.setFailed('Error: Could not read package.json'); +/** + * @param [shallowExcludeTag] When fetching the given tag, exclude all history reachable by the shallowExcludeTag (used to make fetch much faster) + */ +function fetchTag(tag, shallowExcludeTag = '') { + let shouldRetry = true; + let needsRepack = false; + while (shouldRetry) { + try { + let command = ''; + if (needsRepack) { + // We have seen some scenarios where this fixes the git fetch. + // Why? Who knows... https://github.com/Expensify/App/pull/31459 + command = 'git repack -d'; + console.log(`Running command: ${command}`); + (0, child_process_1.execSync)(command); + } + command = `git fetch origin tag ${tag} --no-tags`; + // Note that this condition is only ever NOT true in the 1.0.0-0 edge case + if (shallowExcludeTag && shallowExcludeTag !== tag) { + command += ` --shallow-exclude=${shallowExcludeTag}`; + } + console.log(`Running command: ${command}`); + (0, child_process_1.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; + } + } + } +} +/** + * Get merge logs between two tags (inclusive) as a JavaScript object. + */ +function getCommitHistoryAsJSON(fromTag, toTag) { + // Fetch tags, excluding commits reachable from the previous patch version (i.e: previous checklist), so that we don't have to fetch the full history + const previousPatchVersion = getPreviousExistingTag(fromTag, VersionUpdater.SEMANTIC_VERSION_LEVELS.PATCH); + fetchTag(fromTag, previousPatchVersion); + fetchTag(toTag, previousPatchVersion); + console.log('Getting pull requests merged between the following tags:', fromTag, toTag); + return new Promise((resolve, reject) => { + let stdout = ''; + let stderr = ''; + const args = ['log', '--format={"commit": "%H", "authorName": "%an", "subject": "%s"},', `${fromTag}...${toTag}`]; + console.log(`Running command: git ${args.join(' ')}`); + const spawnedProcess = (0, child_process_1.spawn)('git', args); + spawnedProcess.on('message', console.log); + spawnedProcess.stdout.on('data', (chunk) => { + console.log(chunk.toString()); + stdout += chunk.toString(); + }); + spawnedProcess.stderr.on('data', (chunk) => { + console.error(chunk.toString()); + stderr += chunk.toString(); + }); + spawnedProcess.on('close', (code) => { + if (code !== 0) { + return reject(new Error(`${stderr}`)); + } + resolve(stdout); + }); + spawnedProcess.on('error', (err) => reject(err)); + }).then((stdout) => { + // Sanitize just the text within commit subjects as that's the only potentially un-parseable text. + const sanitizedOutput = stdout.replace(/(?<="subject": ").*?(?="})/g, (subject) => (0, sanitizeStringForJSONParse_1.default)(subject)); + // Then remove newlines, format as JSON and convert to a proper JS object + const json = `[${sanitizedOutput}]`.replace(/(\r\n|\n|\r)/gm, '').replace('},]', '}]'); + return JSON.parse(json); + }); +} +/** + * Parse merged PRs, excluding those from irrelevant branches. + */ +function getValidMergedPRs(commits) { + const mergedPRs = new Set(); + commits.forEach((commit) => { + const author = commit.authorName; + if (author === CONST_1.default.OS_BOTIFY) { + return; + } + const match = commit.subject.match(/Merge pull request #(\d+) from (?!Expensify\/.*-cherry-pick-staging)/); + if (!Array.isArray(match) || match.length < 2) { + return; + } + const pr = Number.parseInt(match[1], 10); + if (mergedPRs.has(pr)) { + // If a PR shows up in the log twice, that means that the PR was deployed in the previous checklist. + // That also means that we don't want to include it in the current checklist, so we remove it now. + mergedPRs.delete(pr); + return; + } + mergedPRs.add(pr); + }); + return Array.from(mergedPRs); } -const previousVersion = versionUpdater.getPreviousVersion(currentVersion ?? '', semverLevel); -core.setOutput('PREVIOUS_VERSION', previousVersion); +/** + * Takes in two git tags and returns a list of PR numbers of all PRs merged between those two tags + */ +async function getPullRequestsMergedBetween(fromTag, toTag) { + console.log(`Looking for commits made between ${fromTag} and ${toTag}...`); + const commitList = await getCommitHistoryAsJSON(fromTag, toTag); + console.log(`Commits made between ${fromTag} and ${toTag}:`, commitList); + // Find which commit messages correspond to merged PR's + const pullRequestNumbers = getValidMergedPRs(commitList).sort((a, b) => a - b); + console.log(`List of pull requests merged between ${fromTag} and ${toTag}`, pullRequestNumbers); + return pullRequestNumbers; +} +exports["default"] = { + getPreviousExistingTag, + getValidMergedPRs, + getPullRequestsMergedBetween, +}; + + +/***/ }), + +/***/ 902: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +/* eslint-disable @typescript-eslint/naming-convention */ +Object.defineProperty(exports, "__esModule", ({ value: true })); +const replacer = (str) => ({ + '\\': '\\\\', + '\t': '\\t', + '\n': '\\n', + '\r': '\\r', + '\f': '\\f', + '"': '\\"', +}[str] ?? ''); +/** + * Replace any characters in the string that will break JSON.parse for our Git Log output + * + * Solution partly taken from SO user Gabriel Rodríguez Flores 🙇 + * https://stackoverflow.com/questions/52789718/how-to-remove-special-characters-before-json-parse-while-file-reading + */ +const sanitizeStringForJSONParse = (inputString) => { + if (typeof inputString !== 'string') { + throw new TypeError('Input must me of type String'); + } + // Replace any newlines and escape backslashes + return inputString.replace(/\\|\t|\n|\r|\f|"/g, replacer); +}; +exports["default"] = sanitizeStringForJSONParse; /***/ }), @@ -2743,7 +3039,7 @@ core.setOutput('PREVIOUS_VERSION', previousVersion); "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getPreviousVersion = exports.incrementPatch = exports.incrementMinor = exports.SEMANTIC_VERSION_LEVELS = exports.MAX_INCREMENTS = exports.incrementVersion = exports.getVersionStringFromNumber = exports.getVersionNumberFromString = void 0; +exports.getPreviousVersion = exports.incrementPatch = exports.incrementMinor = exports.SEMANTIC_VERSION_LEVELS = exports.MAX_INCREMENTS = exports.incrementVersion = exports.getVersionStringFromNumber = exports.getVersionNumberFromString = exports.isValidSemverLevel = void 0; const SEMANTIC_VERSION_LEVELS = { MAJOR: 'MAJOR', MINOR: 'MINOR', @@ -2753,6 +3049,10 @@ const SEMANTIC_VERSION_LEVELS = { exports.SEMANTIC_VERSION_LEVELS = SEMANTIC_VERSION_LEVELS; const MAX_INCREMENTS = 99; exports.MAX_INCREMENTS = MAX_INCREMENTS; +function isValidSemverLevel(str) { + return Object.keys(SEMANTIC_VERSION_LEVELS).includes(str); +} +exports.isValidSemverLevel = isValidSemverLevel; /** * Transforms a versions string into a number */ @@ -2846,6 +3146,14 @@ module.exports = require("assert"); /***/ }), +/***/ 81: +/***/ ((module) => { + +"use strict"; +module.exports = require("child_process"); + +/***/ }), + /***/ 113: /***/ ((module) => { diff --git a/.github/actions/javascript/getPullRequestDetails/index.js b/.github/actions/javascript/getPullRequestDetails/index.js index 7cb768941711..8fa081de17fa 100644 --- a/.github/actions/javascript/getPullRequestDetails/index.js +++ b/.github/actions/javascript/getPullRequestDetails/index.js @@ -6679,7 +6679,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -12113,27 +12113,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/getReleaseBody/index.js b/.github/actions/javascript/getReleaseBody/index.js index f6ba78030927..81b73876db95 100644 --- a/.github/actions/javascript/getReleaseBody/index.js +++ b/.github/actions/javascript/getReleaseBody/index.js @@ -6679,7 +6679,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -12057,27 +12057,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/isStagingDeployLocked/index.js b/.github/actions/javascript/isStagingDeployLocked/index.js index 29c010f086c3..feccd44beb1a 100644 --- a/.github/actions/javascript/isStagingDeployLocked/index.js +++ b/.github/actions/javascript/isStagingDeployLocked/index.js @@ -6679,7 +6679,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -12011,27 +12011,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/index.js b/.github/actions/javascript/markPullRequestsAsDeployed/index.js index 1256b6425640..ba474583a2fe 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/index.js +++ b/.github/actions/javascript/markPullRequestsAsDeployed/index.js @@ -6723,7 +6723,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -11598,15 +11598,25 @@ async function run() { * 1. For regular staging deploys, the person who merged the PR. * 2. For CPs, the person who committed the cherry-picked commit (not necessarily the author of the commit). */ - const { data: pr } = await GithubUtils_1.default.octokit.pulls.get({ - owner: CONST_1.default.GITHUB_OWNER, - repo: CONST_1.default.APP_REPO, - pull_number: prNumber, - }); - const deployer = isCP ? commit.committer.name : pr.merged_by?.login; - const title = pr.title; - const deployMessage = deployer ? getDeployMessage(deployer, isCP ? 'Cherry-picked' : 'Deployed', title) : ''; - await commentPR(prNumber, deployMessage); + try { + const { data: pr } = await GithubUtils_1.default.octokit.pulls.get({ + owner: CONST_1.default.GITHUB_OWNER, + repo: CONST_1.default.APP_REPO, + pull_number: prNumber, + }); + const deployer = isCP ? commit.committer.name : pr.merged_by?.login; + const title = pr.title; + const deployMessage = deployer ? getDeployMessage(deployer, isCP ? 'Cherry-picked' : 'Deployed', title) : ''; + await commentPR(prNumber, deployMessage); + } + catch (error) { + if (error.status === 404) { + console.log(`Unable to comment on PR #${prNumber}. GitHub responded with 404.`); + } + else { + throw error; + } + } } } if (require.main === require.cache[eval('__filename')]) { @@ -12208,27 +12218,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts b/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts index a312bae7e7df..53018cbb035e 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts +++ b/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ import * as core from '@actions/core'; import {context} from '@actions/github'; +import type {RequestError} from '@octokit/types'; import * as ActionUtils from '@github/libs/ActionUtils'; import CONST from '@github/libs/CONST'; import GithubUtils from '@github/libs/GithubUtils'; @@ -113,16 +114,24 @@ async function run() { * 1. For regular staging deploys, the person who merged the PR. * 2. For CPs, the person who committed the cherry-picked commit (not necessarily the author of the commit). */ - const {data: pr} = await GithubUtils.octokit.pulls.get({ - owner: CONST.GITHUB_OWNER, - repo: CONST.APP_REPO, - pull_number: prNumber, - }); - const deployer = isCP ? commit.committer.name : pr.merged_by?.login; - - const title = pr.title; - const deployMessage = deployer ? getDeployMessage(deployer, isCP ? 'Cherry-picked' : 'Deployed', title) : ''; - await commentPR(prNumber, deployMessage); + try { + const {data: pr} = await GithubUtils.octokit.pulls.get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: prNumber, + }); + const deployer = isCP ? commit.committer.name : pr.merged_by?.login; + + const title = pr.title; + const deployMessage = deployer ? getDeployMessage(deployer, isCP ? 'Cherry-picked' : 'Deployed', title) : ''; + await commentPR(prNumber, deployMessage); + } catch (error) { + if ((error as RequestError).status === 404) { + console.log(`Unable to comment on PR #${prNumber}. GitHub responded with 404.`); + } else { + throw error; + } + } } } diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js index fef09bc25c56..68f3b11f45f1 100644 --- a/.github/actions/javascript/postTestBuildComment/index.js +++ b/.github/actions/javascript/postTestBuildComment/index.js @@ -6723,7 +6723,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -12110,27 +12110,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/reopenIssueWithComment/index.js b/.github/actions/javascript/reopenIssueWithComment/index.js index b211f301eb67..461f576e4691 100644 --- a/.github/actions/javascript/reopenIssueWithComment/index.js +++ b/.github/actions/javascript/reopenIssueWithComment/index.js @@ -6679,7 +6679,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -12021,27 +12021,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/reviewerChecklist/index.js b/.github/actions/javascript/reviewerChecklist/index.js index b53863ef0c26..5be97363a21c 100644 --- a/.github/actions/javascript/reviewerChecklist/index.js +++ b/.github/actions/javascript/reviewerChecklist/index.js @@ -6723,7 +6723,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -12113,27 +12113,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/actions/javascript/verifySignedCommits/index.js b/.github/actions/javascript/verifySignedCommits/index.js index e3507f897933..c932e0c4c618 100644 --- a/.github/actions/javascript/verifySignedCommits/index.js +++ b/.github/actions/javascript/verifySignedCommits/index.js @@ -6723,7 +6723,7 @@ FetchError.prototype.name = 'FetchError'; let convert; try { - convert = (__nccwpck_require__(2877).convert); + convert = (__nccwpck_require__(3975).convert); } catch (e) {} const INTERNALS = Symbol('Body internals'); @@ -12053,27 +12053,27 @@ exports["default"] = arrayDifference; /***/ }), -/***/ 2877: +/***/ 9491: /***/ ((module) => { -module.exports = eval("require")("encoding"); - +"use strict"; +module.exports = require("assert"); /***/ }), -/***/ 9491: +/***/ 6113: /***/ ((module) => { "use strict"; -module.exports = require("assert"); +module.exports = require("crypto"); /***/ }), -/***/ 6113: +/***/ 3975: /***/ ((module) => { "use strict"; -module.exports = require("crypto"); +module.exports = require("encoding"); /***/ }), diff --git a/.github/libs/GitUtils.ts b/.github/libs/GitUtils.ts index dc8ae037be28..ab4a81f96adf 100644 --- a/.github/libs/GitUtils.ts +++ b/.github/libs/GitUtils.ts @@ -1,7 +1,8 @@ import {execSync, spawn} from 'child_process'; import CONST from './CONST'; import sanitizeStringForJSONParse from './sanitizeStringForJSONParse'; -import * as VERSION_UPDATER from './versionUpdater'; +import * as VersionUpdater from './versionUpdater'; +import type {SemverLevel} from './versionUpdater'; type CommitType = { commit: string; @@ -9,6 +10,64 @@ type CommitType = { authorName: string; }; +/** + * Check if a tag exists locally or in the remote. + */ +function tagExists(tag: string) { + try { + // Check if the tag exists locally + execSync(`git show-ref --tags ${tag}`, {stdio: 'ignore'}); + return true; // Tag exists locally + } catch (error) { + // Tag does not exist locally, check in remote + let shouldRetry = true; + let needsRepack = false; + let doesTagExist = 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', {stdio: 'inherit'}); + } + execSync(`git ls-remote --exit-code --tags origin ${tag}`, {stdio: 'ignore'}); + doesTagExist = true; + shouldRetry = false; + } catch (e) { + if (!needsRepack) { + console.log('Attempting to repack and retry...'); + needsRepack = true; + } else { + console.error("Repack didn't help, giving up..."); + shouldRetry = false; + } + } + } + return doesTagExist; + } +} + +/** + * This essentially just calls getPreviousVersion in a loop, until it finds a version for which a tag exists. + * It's useful if we manually perform a version bump, because in that case a tag may not exist for the previous version. + * + * @param tag the current tag + * @param level the Semver level to step backward by + */ +function getPreviousExistingTag(tag: string, level: SemverLevel) { + let previousVersion = VersionUpdater.getPreviousVersion(tag, level); + let tagExistsForPreviousVersion = false; + while (!tagExistsForPreviousVersion) { + if (tagExists(previousVersion)) { + tagExistsForPreviousVersion = true; + break; + } + console.log(`Tag for previous version ${previousVersion} does not exist. Checking for an older version...`); + previousVersion = VersionUpdater.getPreviousVersion(previousVersion, level); + } + return previousVersion; +} + /** * @param [shallowExcludeTag] When fetching the given tag, exclude all history reachable by the shallowExcludeTag (used to make fetch much faster) */ @@ -53,8 +112,8 @@ function fetchTag(tag: string, shallowExcludeTag = '') { * Get merge logs between two tags (inclusive) as a JavaScript object. */ function getCommitHistoryAsJSON(fromTag: string, toTag: string): Promise { - // Fetch tags, exclude commits reachable from the previous patch version (i.e: previous checklist), so that we don't have to fetch the full history - const previousPatchVersion = VERSION_UPDATER.getPreviousVersion(fromTag, VERSION_UPDATER.SEMANTIC_VERSION_LEVELS.PATCH); + // Fetch tags, excluding commits reachable from the previous patch version (i.e: previous checklist), so that we don't have to fetch the full history + const previousPatchVersion = getPreviousExistingTag(fromTag, VersionUpdater.SEMANTIC_VERSION_LEVELS.PATCH); fetchTag(fromTag, previousPatchVersion); fetchTag(toTag, previousPatchVersion); @@ -138,6 +197,7 @@ async function getPullRequestsMergedBetween(fromTag: string, toTag: string) { } export default { + getPreviousExistingTag, getValidMergedPRs, getPullRequestsMergedBetween, }; diff --git a/.github/libs/versionUpdater.ts b/.github/libs/versionUpdater.ts index 9b60fb82bd43..d9b24f532d8e 100644 --- a/.github/libs/versionUpdater.ts +++ b/.github/libs/versionUpdater.ts @@ -1,11 +1,18 @@ +import type {ValueOf} from 'type-fest'; + const SEMANTIC_VERSION_LEVELS = { MAJOR: 'MAJOR', MINOR: 'MINOR', PATCH: 'PATCH', BUILD: 'BUILD', } as const; +type SemverLevel = ValueOf; + +const MAX_INCREMENTS = 99; -const MAX_INCREMENTS = 99 as const; +function isValidSemverLevel(str: string): str is SemverLevel { + return Object.keys(SEMANTIC_VERSION_LEVELS).includes(str); +} /** * Transforms a versions string into a number @@ -46,7 +53,7 @@ const incrementPatch = (major: number, minor: number, patch: number): string => /** * Increments a build version */ -const incrementVersion = (version: string, level: string): string => { +const incrementVersion = (version: string, level: SemverLevel): string => { const [major, minor, patch, build] = getVersionNumberFromString(version); // Majors will always be incremented @@ -99,7 +106,9 @@ function getPreviousVersion(currentVersion: string, level: string): string { return getVersionStringFromNumber(major, minor, patch, build - 1); } +export type {SemverLevel}; export { + isValidSemverLevel, getVersionNumberFromString, getVersionStringFromNumber, incrementVersion, diff --git a/.github/scripts/buildActions.sh b/.github/scripts/buildActions.sh index 30c284a776be..27d65ca99c9b 100755 --- a/.github/scripts/buildActions.sh +++ b/.github/scripts/buildActions.sh @@ -43,7 +43,7 @@ for ((i=0; i < ${#GITHUB_ACTIONS[@]}; i++)); do ACTION_DIR=$(dirname "$ACTION") # Build the action in the background - ncc build -t "$ACTION" -o "$ACTION_DIR" & + npx ncc build --transpile-only --external encoding "$ACTION" -o "$ACTION_DIR" & ASYNC_BUILDS[i]=$! done diff --git a/.github/scripts/verifyActions.sh b/.github/scripts/verifyActions.sh index ddc8fa0a3226..17316e1aac70 100755 --- a/.github/scripts/verifyActions.sh +++ b/.github/scripts/verifyActions.sh @@ -22,7 +22,7 @@ if [[ EXIT_CODE -eq 0 ]]; then echo -e "${GREEN}Github Actions are up to date!${NC}" exit 0 else - echo -e "${RED}Error: Diff found when Github Actions were rebuilt. Did you forget to run \`npm run gh-actions-build\` after a clean install (\`rm -rf node_modules && npm i\`)? Do you need to merge main? Did you try running \`git config --global core.autocrlf false\` then \`npm run gh-actions-build\` again?${NC}" + echo -e "${RED}Error: Diff found when Github Actions were rebuilt. Did you forget to run \`npm run gh-actions-build\` after a clean install (\`rm -rf node_modules && npm i\`)? Do you need to merge main? Did you try running \`git config --global core.autocrlf false\` then \`npm run gh-actions-build\` again? Did you try running \`npx ncc cache clean\`?${NC}" echo "$DIFF_OUTPUT" | "$LIB_PATH/diff-so-fancy" | less --tabs=4 -RFX exit 1 fi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d6b346cb3995..36d4248fcc3c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ on: pull_request: types: [opened, synchronize] branches-ignore: [staging, production] - paths: ['**.js', '**.ts', '**.tsx', '**.sh', 'package.json', 'package-lock.json'] + paths: ['**.js', '**.ts', '**.tsx', 'package.json', 'package-lock.json'] concurrency: group: ${{ github.ref == 'refs/heads/main' && format('{0}-{1}', github.ref, github.sha) || github.ref }}-jest @@ -54,20 +54,3 @@ jobs: - name: Storybook run run: npm run storybook -- --smoke-test --ci - - shellTests: - if: ${{ github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' || github.event_name == 'workflow_call' }} - runs-on: ubuntu-latest - name: Shell tests - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: ./.github/actions/composite/setupNode - - - name: Install ts-node - run: npm i -g ts-node - - - name: Test CI git logic - run: tests/unit/CIGitLogicTest.sh diff --git a/android/app/build.gradle b/android/app/build.gradle index d005d82471a9..dd19b75b1e49 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009000004 - versionName "9.0.0-4" + versionCode 1009000114 + versionName "9.0.1-14" // 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/simple-illustrations/simple-illustration__lockclosed_orange.svg b/assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg new file mode 100644 index 000000000000..91af173f0357 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__treasurechest.svg b/assets/images/simple-illustrations/simple-illustration__treasurechest.svg index 51718aa5112a..622c611a9dc5 100644 --- a/assets/images/simple-illustrations/simple-illustration__treasurechest.svg +++ b/assets/images/simple-illustrations/simple-illustration__treasurechest.svg @@ -1,59 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index aec527edabe0..d6ab46c809ef 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -48,7 +48,7 @@ We hire and pay external contributors via [Upwork.com](https://www.upwork.com). 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, however experienced contributors may work on numerous jobs simultaneously. +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. Please be aware that compensation for any support in solving an issue is provided **entirely at Expensify’s discretion**. Personal time or resources applied towards investigating a proposal **will not guarantee compensation**. Compensation is only guaranteed to those who **[propose a solution and get hired for that job](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#propose-a-solution-for-the-job)**. We understand there may be cases where a selected proposal may take inspiration from a previous proposal. Unfortunately, it’s not possible for us to evaluate every individual case and we have no process that can efficiently do so. Issues with higher rewards come with higher risk factors so try to keep things civil and make the best proposal you can. Once again, **any information provided may not necessarily lead to you getting hired for that issue or compensated in any way.** diff --git a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md index bc9801060223..0fde76c8fa92 100644 --- a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md +++ b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md @@ -80,7 +80,7 @@ When you link your credit cards to Expensify, the transactions will appear in ea After a card is connected via direct connection or via Approved! banks, Expensify will import 30-90 days' worth of historical transactions to your account (the timeframe is based on your bank's discretion). Any historical expenses beyond that date range can be imported using the CSV spreadsheet import method. ## Using eReceipts -Expensify eReceipts serve as digital substitutes for paper receipts in your purchase transactions, eliminating the necessity to retain physical receipts or utilize SmartScanning receipts. In the case of Expensify Card transactions, eReceipts are automatically generated for all amounts. For other card programs, eReceipts are specifically generated for purchases amounting to $75 or less, provided the transactions are in USD. +Expensify eReceipts serve as digital substitutes for paper receipts in your purchase transactions, eliminating the necessity to retain physical receipts or utilize SmartScanning receipts. In the case of Expensify Card transactions, eReceipts are automatically generated for all amounts in the following categories: Airlines, Commuter expenses, Gas, Groceries, Mail, Meals, Car rental, Taxis, and Utilities. For other card programs, eReceipts are specifically generated for purchases amounting to $75 or less, provided the transactions are in USD. To ensure seamless automatic importation, it's essential to maintain your transactions in US Dollars. Additionally, eReceipts can be directly imported from your bank account. Please be aware that CSV/OFX imported files of bank transactions do not support eReceipts. It's important to note that eReceipts are not generated for lodging expenses. Moreover, due to incomplete or inaccurate category information from certain banks, there may be instances of invalid eReceipts being generated for hotel purchases. If you choose to re-categorize expenses, a similar situation may arise. It's crucial to remember that our Expensify eReceipt Guarantee excludes coverage for hotel and motel expenses. diff --git a/docs/articles/new-expensify/expenses/Connect-a-Business-Bank-Account.md b/docs/articles/new-expensify/expenses/Connect-a-Business-Bank-Account.md index 64ae3997c4f9..516497c9dce7 100644 --- a/docs/articles/new-expensify/expenses/Connect-a-Business-Bank-Account.md +++ b/docs/articles/new-expensify/expenses/Connect-a-Business-Bank-Account.md @@ -20,7 +20,7 @@ To connect a bank account in New Expensify, you must first enable the Make or Tr 2. Select either Connect online with Plaid (preferred) or Connect manually 3. Enter bank details -# Step 4: Upload ID +# Step 3: Upload ID After entering your personal details, you’ll be prompted to click a link or scan a QR code so that you can do the following: 1. Upload a photo of the front and back of your ID (this cannot be a photo of an existing image) 2. Use your device to take a selfie and record a short video of yourself @@ -29,14 +29,14 @@ After entering your personal details, you’ll be prompted to click a link or sc - Issued in the US - Current (ie: the expiration date must be in the future) -# Step 5: Enter company information +# Step 4: Enter company information This is where you’ll add the legal business name as well as several other company details. - **Company address:** The company address must be located in the US and a physical location (If you input a maildrop address, PO box, or UPS Store, the address will be flagged for review, and adding the bank account to Expensify will be delayed) - **Tax Identification Number:** This is the identification number that was assigned to the business by the IRS - **Company website:** A company website is required to use most of Expensify’s payment features. - **Industry Classification Code:** You can locate a list of Industry Classification Codes [here](https://www.census.gov/naics/?input=software&year=2022). -# Step 6: Additional Information +# Step 5: Additional Information Check the appropriate box under Additional Information, accept the agreement terms, and verify that all of the information is true and accurate: - A Beneficial Owner refers to an individual who owns 25% or more of the business. - If you or another individual owns 25% or more of the business, please check the appropriate box diff --git a/docs/articles/new-expensify/expenses/Set-up-your-wallet.md b/docs/articles/new-expensify/expenses/Set-up-your-wallet.md index de1ee61066b0..7f7b6196707d 100644 --- a/docs/articles/new-expensify/expenses/Set-up-your-wallet.md +++ b/docs/articles/new-expensify/expenses/Set-up-your-wallet.md @@ -5,6 +5,8 @@ description: Send and receive payments by adding your payment account
To send and receive money using Expensify, you’ll first need to set up your Expensify Wallet by adding your payment account. +![The Wallet Tab where you can add a personal bank account]({{site.url}}/assets/images/ExpensifyHelp_R5_Wallet_1.png){:width="100%"} + {% include selector.html values="desktop, mobile" %} {% include option.html value="desktop" %} diff --git a/docs/articles/new-expensify/getting-started/Create-a-company-workspace.md b/docs/articles/new-expensify/getting-started/Create-a-company-workspace.md index cbe21c9db20a..e25ae580e87a 100644 --- a/docs/articles/new-expensify/getting-started/Create-a-company-workspace.md +++ b/docs/articles/new-expensify/getting-started/Create-a-company-workspace.md @@ -30,6 +30,12 @@ You can get support any time by locating your chat with Concierge in your chat i
  • Click Default Currency to set the currency for all expenses submitted under the workspace. Expensify automatically converts all other currencies to your default currency.
  • +![Click your profile image or icon]({{site.url}}/assets/images/ExpensifyHelp_R1_CreateWorkspace_1.png){:width="100%"} + +![Click Workspaces in the left menu and New Worksapce]({{site.url}}/assets/images/ExpensifyHelp_R1_CreateWorkspace_2.png){:width="100%"} + +![Options to make changes like a custom workspace name]({{site.url}}/assets/images/ExpensifyHelp_R1_CreateWorkspace_3.png){:width="100%"} + # 3. Invite members
      @@ -46,6 +52,12 @@ Once the invite is accepted, the new members will appear in your members list. You can also invite members on the workspace’s Profile page by clicking **Share** to share the workspace’s URL or QR code. {% include end-info.html %} +![Click Members on the left and click Invite member]({{site.url}}/assets/images/ExpensifyHelp_R1_InviteMembers_1.png){:width="100%"} + +![Use the search field to find the individual by name, email, or phone number]({{site.url}}/assets/images/ExpensifyHelp_R1_InviteMembers_2.png){:width="100%"} + +![Enter a custom message into the Message field]({{site.url}}/assets/images/ExpensifyHelp_R1_InviteMembers_3.png){:width="100%"} + # 4. Set admins Admins are members of your workspace that have permissions to manage the workspace. The table below shows the difference between member and admin permissions: diff --git a/docs/articles/new-expensify/getting-started/Join-your-company's-workspace.md b/docs/articles/new-expensify/getting-started/Join-your-company's-workspace.md index 9c5aea0c61ae..a4747c2d95e5 100644 --- a/docs/articles/new-expensify/getting-started/Join-your-company's-workspace.md +++ b/docs/articles/new-expensify/getting-started/Join-your-company's-workspace.md @@ -51,6 +51,8 @@ Upload your expenses and check your reports right from your phone by downloading {% include end-option.html %} {% include end-selector.html %} + +![The profile page to change options like display name]({{site.url}}/assets/images/ExpensifyHelp_R2_Profile_1.png){:width="100%"} # 3. Meet Concierge diff --git a/docs/articles/new-expensify/workspaces/Create-expense-categories.md b/docs/articles/new-expensify/workspaces/Create-expense-categories.md index 7b8d29d09d1c..6f810d8f96d0 100644 --- a/docs/articles/new-expensify/workspaces/Create-expense-categories.md +++ b/docs/articles/new-expensify/workspaces/Create-expense-categories.md @@ -8,6 +8,8 @@ In Expensify, categories refer to the **chart of accounts, GL accounts, expense An admin can manually create categories for a workspace, or they will be automatically imported if your workspace is connected to another platform such as QuickBooks Online, QuickBooks Desktop, Intacct, Xero, or NetSuite. These imported categories can be enabled or disabled to use as categories for expenses added to Expensify. Additionally, Expensify will learn how you apply categories to specific merchants over time and apply them automatically. +![The Categories tab]({{site.url}}/assets/images/ExpensifyHelp_R3_Categories_1.png){:width="100%"} + # Manually add or delete categories {% include selector.html values="desktop, mobile" %} diff --git a/docs/articles/new-expensify/workspaces/Create-expense-tags.md b/docs/articles/new-expensify/workspaces/Create-expense-tags.md index cf7bdd6fc6a7..e048ad9ca768 100644 --- a/docs/articles/new-expensify/workspaces/Create-expense-tags.md +++ b/docs/articles/new-expensify/workspaces/Create-expense-tags.md @@ -8,6 +8,8 @@ In Expensify, tags refer to **classes, projects, cost centers, locations, custom An admin can manually create tags for a workspace, or they will be automatically imported if your workspace is connected to an accounting system, like QuickBooks Online or Xero. These imported tags can be enabled or disabled to use as tags for expenses added to Expensify. Additionally, Expensify will learn how you apply tags to specific merchants over time and apply them automatically. +![The Tags tab]({{site.url}}/assets/images/ExpensifyHelp_R4_Tags_2.png){:width="100%"} + # Manually add or delete tags {% include selector.html values="desktop, mobile" %} @@ -24,6 +26,8 @@ To manually add a tag, 7. Click **Add Tag** in the top right. 8. Enter a name for the tag and click **Save**. +![The toggle to enable Tags]({{site.url}}/assets/images/ExpensifyHelp_R4_Tags_1.png){:width="100%"} + To delete a tag, 1. Click the tag on the Tags page. diff --git a/docs/assets/images/ExpensifyHelp-QBO-1.png b/docs/assets/images/ExpensifyHelp-QBO-1.png new file mode 100644 index 000000000000..c612cb760d58 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-QBO-1.png differ diff --git a/docs/assets/images/ExpensifyHelp-QBO-2.png b/docs/assets/images/ExpensifyHelp-QBO-2.png new file mode 100644 index 000000000000..7fbc99503f2e Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-QBO-2.png differ diff --git a/docs/assets/images/ExpensifyHelp-QBO-3.png b/docs/assets/images/ExpensifyHelp-QBO-3.png new file mode 100644 index 000000000000..600a5903c05f Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-QBO-3.png differ diff --git a/docs/assets/images/ExpensifyHelp-Xero-1.png b/docs/assets/images/ExpensifyHelp-Xero-1.png new file mode 100644 index 000000000000..c612cb760d58 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-Xero-1.png differ diff --git a/docs/assets/images/ExpensifyHelp-Xero-2.png b/docs/assets/images/ExpensifyHelp-Xero-2.png new file mode 100644 index 000000000000..7fbc99503f2e Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-Xero-2.png differ diff --git a/docs/assets/images/ExpensifyHelp-Xero-3.png b/docs/assets/images/ExpensifyHelp-Xero-3.png new file mode 100644 index 000000000000..e340a302bd89 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-Xero-3.png differ diff --git a/docs/assets/images/ExpensifyHelp_ApproveExpense_1.png b/docs/assets/images/ExpensifyHelp_ApproveExpense_1.png new file mode 100644 index 000000000000..8a721744860f Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_ApproveExpense_1.png differ diff --git a/docs/assets/images/ExpensifyHelp_ApproveExpense_2.png b/docs/assets/images/ExpensifyHelp_ApproveExpense_2.png new file mode 100644 index 000000000000..25e11f0e7624 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_ApproveExpense_2.png differ diff --git a/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_1.png b/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_1.png index baa3f8734e8e..4487e10d9719 100644 Binary files a/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_1.png and b/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_1.png differ diff --git a/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_2.png b/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_2.png index 83eaadff3bdc..600e5a7788ed 100644 Binary files a/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_2.png and b/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_2.png differ diff --git a/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_3.png b/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_3.png index 6b43c41c39cf..dd0996a7eecd 100644 Binary files a/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_3.png and b/docs/assets/images/ExpensifyHelp_R1_CreateWorkspace_3.png differ diff --git a/docs/assets/images/ExpensifyHelp_R1_InviteMembers_1.png b/docs/assets/images/ExpensifyHelp_R1_InviteMembers_1.png index 0f1883bd8f69..874fb307f067 100644 Binary files a/docs/assets/images/ExpensifyHelp_R1_InviteMembers_1.png and b/docs/assets/images/ExpensifyHelp_R1_InviteMembers_1.png differ diff --git a/docs/assets/images/ExpensifyHelp_R1_InviteMembers_2.png b/docs/assets/images/ExpensifyHelp_R1_InviteMembers_2.png index c3b8c035c9ed..f5dae55fdcd4 100644 Binary files a/docs/assets/images/ExpensifyHelp_R1_InviteMembers_2.png and b/docs/assets/images/ExpensifyHelp_R1_InviteMembers_2.png differ diff --git a/docs/assets/images/ExpensifyHelp_R1_InviteMembers_3.png b/docs/assets/images/ExpensifyHelp_R1_InviteMembers_3.png index 515a111847f8..1d646d3d5001 100644 Binary files a/docs/assets/images/ExpensifyHelp_R1_InviteMembers_3.png and b/docs/assets/images/ExpensifyHelp_R1_InviteMembers_3.png differ diff --git a/docs/assets/images/ExpensifyHelp_R2_Profile_1.png b/docs/assets/images/ExpensifyHelp_R2_Profile_1.png index 0419c66e7563..7e3e6ac181be 100644 Binary files a/docs/assets/images/ExpensifyHelp_R2_Profile_1.png and b/docs/assets/images/ExpensifyHelp_R2_Profile_1.png differ diff --git a/docs/assets/images/ExpensifyHelp_R3_Categories_1.png b/docs/assets/images/ExpensifyHelp_R3_Categories_1.png index d82757866d3d..47997df920de 100644 Binary files a/docs/assets/images/ExpensifyHelp_R3_Categories_1.png and b/docs/assets/images/ExpensifyHelp_R3_Categories_1.png differ diff --git a/docs/assets/images/ExpensifyHelp_R4_Tags_1.png b/docs/assets/images/ExpensifyHelp_R4_Tags_1.png index a14ea8343578..c8540d93423f 100644 Binary files a/docs/assets/images/ExpensifyHelp_R4_Tags_1.png and b/docs/assets/images/ExpensifyHelp_R4_Tags_1.png differ diff --git a/docs/assets/images/ExpensifyHelp_R4_Tags_2.png b/docs/assets/images/ExpensifyHelp_R4_Tags_2.png index 889d845e931a..a83d748ea12b 100644 Binary files a/docs/assets/images/ExpensifyHelp_R4_Tags_2.png and b/docs/assets/images/ExpensifyHelp_R4_Tags_2.png differ diff --git a/docs/assets/images/ExpensifyHelp_R5_Wallet_1.png b/docs/assets/images/ExpensifyHelp_R5_Wallet_1.png index 64ff3730e695..695dceb52581 100644 Binary files a/docs/assets/images/ExpensifyHelp_R5_Wallet_1.png and b/docs/assets/images/ExpensifyHelp_R5_Wallet_1.png differ diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 3360f1adfad7..eb251dc53b1a 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.0 + 9.0.1 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.0.4 + 9.0.1.14 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 796437f37280..efc72698918f 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.0 + 9.0.1 CFBundleSignature ???? CFBundleVersion - 9.0.0.4 + 9.0.1.14 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 5a4f6310d225..6a414d579761 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.0 + 9.0.1 CFBundleVersion - 9.0.0.4 + 9.0.1.14 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ddab159714fc..35dccc2de393 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1865,7 +1865,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.85): + - RNLiveMarkdown (0.1.88): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1883,9 +1883,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.85) + - RNLiveMarkdown/common (= 0.1.88) - Yoga - - RNLiveMarkdown/common (0.1.85): + - RNLiveMarkdown/common (0.1.88): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2606,7 +2606,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 74b7b3d06d667ba0bbf41da7718f2607ae0dfe8f RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: fff70dc755ed8199a449f61e76cbadec7cd20440 + RNLiveMarkdown: e33d2c97863d5480f8f4b45f8b25f801cc43c7f5 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: df8fe93dbd251f25022f4023d31bc04160d4d65c RNPermissions: 0b61d30d21acbeafe25baaa47d9bae40a0c65216 diff --git a/package-lock.json b/package-lock.json index 2ef901f63d43..db6fa70d9598 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "new.expensify", - "version": "9.0.0-4", + "version": "9.0.1-14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.0-4", + "version": "9.0.1-14", "hasInstallScript": true, "license": "MIT", "dependencies": { "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.85", + "@expensify/react-native-live-markdown": "0.1.88", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -60,11 +60,12 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "2.0.17", + "expensify-common": "2.0.19", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", "expo-image-manipulator": "11.8.0", + "fast-equals": "^4.0.3", "focus-trap-react": "^10.2.3", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -105,7 +106,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.52", + "react-native-onyx": "2.0.53", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -135,8 +136,7 @@ "react-web-config": "^1.0.0", "react-webcam": "^7.1.1", "react-window": "^1.8.9", - "semver": "^7.5.2", - "shim-keyboard-event-key": "^1.0.3" + "semver": "^7.5.2" }, "devDependencies": { "@actions/core": "1.10.0", @@ -152,14 +152,14 @@ "@babel/runtime": "^7.20.0", "@babel/traverse": "^7.22.20", "@babel/types": "^7.22.19", - "@dword-design/eslint-plugin-import-alias": "^4.0.8", + "@dword-design/eslint-plugin-import-alias": "^5.0.0", "@electron/notarize": "^2.1.0", "@jest/globals": "^29.5.0", "@ngneat/falso": "^7.1.1", "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", - "@react-native-community/eslint-config": "3.0.0", + "@react-native-community/eslint-config": "3.2.0", "@react-native/babel-preset": "^0.73.21", "@react-native/metro-config": "^0.73.5", "@react-navigation/devtools": "^6.0.10", @@ -187,12 +187,11 @@ "@types/setimmediate": "^1.0.2", "@types/webpack": "^5.28.5", "@types/webpack-bundle-analyzer": "^4.7.0", - "@typescript-eslint/eslint-plugin": "^6.13.2", - "@typescript-eslint/parser": "^6.13.2", + "@typescript-eslint/eslint-plugin": "^7.13.1", + "@typescript-eslint/parser": "^7.13.1", "@vercel/ncc": "0.38.1", "@welldone-software/why-did-you-render": "7.0.1", "ajv-cli": "^5.0.0", - "babel-eslint": "^10.1.0", "babel-jest": "29.4.1", "babel-loader": "^9.1.3", "babel-plugin-module-resolver": "^5.0.0", @@ -208,18 +207,16 @@ "dotenv": "^16.0.3", "electron": "^29.4.1", "electron-builder": "24.13.2", - "eslint": "^7.6.0", - "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-expensify": "^2.0.51", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jest": "^24.1.0", + "eslint": "^8.57.0", + "eslint-config-airbnb-typescript": "^18.0.0", + "eslint-config-expensify": "^2.0.52", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jest": "^28.6.0", "eslint-plugin-jsdoc": "^46.2.6", - "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-react-native-a11y": "^3.3.0", "eslint-plugin-storybook": "^0.8.0", "eslint-plugin-testing-library": "^6.2.2", - "eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0", + "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", "html-webpack-plugin": "^5.5.0", "jest": "29.4.1", "jest-circus": "29.4.1", @@ -466,10 +463,10 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.23.10", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.7.tgz", + "integrity": "sha512-SO5E3bVxDuxyNxM5agFv480YA2HO6ohZbGxbazZdIk3KQOPOGVNw6q78I9/lbviIf95eq6tPozeYnJLbjnC8IA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", @@ -480,14 +477,13 @@ }, "peerDependencies": { "@babel/core": "^7.11.0", - "eslint": "^7.5.0 || ^8.0.0" + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/@babel/eslint-parser/node_modules/semver": { "version": "6.3.1", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -2866,16 +2862,18 @@ }, "node_modules/@dword-design/dedent": { "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@dword-design/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-OFmAmzKiDUh9m7WRMYcoEOPI7b5tS5hdqQmtKDwF+ZssVJv8a+GHo9VOtFsmlw3h8Roh/9QzFWIsjSFZyQUMdg==", "dev": true, - "license": "MIT", "dependencies": { "babel-plugin-add-module-exports": "^1.0.2" } }, "node_modules/@dword-design/endent": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@dword-design/endent/-/endent-1.4.1.tgz", + "integrity": "sha512-e2sCTzth5kyRdM0o+yEb5wBVzUdJL8Y6HblRGRV0Bif0knf1ZjRLhUjdCrqM+Muirb68X/xJzgdRDJVmLqgXGA==", "dev": true, - "license": "MIT", "dependencies": { "@dword-design/dedent": "^0.7.0", "fast-json-parse": "^1.0.3", @@ -2883,35 +2881,37 @@ } }, "node_modules/@dword-design/eslint-plugin-import-alias": { - "version": "4.0.8", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@dword-design/eslint-plugin-import-alias/-/eslint-plugin-import-alias-5.0.0.tgz", + "integrity": "sha512-QbY2hA+YvhKiPJnAd9fOwT7gNV8OvaGLHdUsC6uVtyoUVjzx55WbUlzlEZzurlwDamXDlIB81IxbHgHT32Lx0w==", "dev": true, - "license": "MIT", "dependencies": { "@babel/core": "^7.10.2", - "@dword-design/functions": "^5.0.22", + "@dword-design/functions": "^6.0.0", "babel-plugin-module-resolver": "^5.0.0", "deepmerge": "^4.3.1", "jiti": "^1.18.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/dword-design" } }, "node_modules/@dword-design/functions": { - "version": "5.0.26", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@dword-design/functions/-/functions-6.0.0.tgz", + "integrity": "sha512-22X9eviXAbaz8xdYrp5Tj6KjDPiT+m3fppAP+wEqA3gecAlCyExABesA1bEZ57aXrXrbhNk88M8PvdAO/PLg3A==", "dev": true, - "license": "MIT", "dependencies": { "@dword-design/endent": "^1.0.0", - "delay": "^5.0.0", + "delay": "^6.0.0", "lodash": "^4.17.15", "tinycolor2": "^1.4.1" }, "engines": { - "node": ">=14" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/dword-design" @@ -3474,36 +3474,42 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.6.2", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", "dev": true, - "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "0.4.3", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3515,10 +3521,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.21.0", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -3529,23 +3542,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -3563,9 +3582,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.85", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.85.tgz", - "integrity": "sha512-jeP4JBzN34pGSpjHKM7Zj3d0cqcKbID3//WrqC+SI7SK/1iJT4SdhZptVCxUg+Dcxq5XwzYIhdnhTNimeya0Fg==", + "version": "0.1.88", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.88.tgz", + "integrity": "sha512-78X5ACV+OL+aL6pfJAXyHkNuMGUc4Rheo4qLkIwLpmUIAiAxmY0B2lch5XHSNGf1a5ofvVbdQ6kl84+4E6DwlQ==", "workspaces": [ "parser", "example", @@ -5673,13 +5692,15 @@ "license": "Apache-2.0" }, "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" @@ -5699,9 +5720,11 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "dev": true, - "license": "BSD-3-Clause" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true }, "node_modules/@invertase/react-native-apple-authentication": { "version": "2.2.2", @@ -7387,7 +7410,6 @@ "version": "5.1.1-v1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "eslint-scope": "5.1.1" } @@ -8692,52 +8714,57 @@ } }, "node_modules/@react-native-community/eslint-config": { - "version": "3.0.0", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/eslint-config/-/eslint-config-3.2.0.tgz", + "integrity": "sha512-ZjGvoeiBtCbd506hQqwjKmkWPgynGUoJspG8/MuV/EfKnkjCtBmeJvq2n+sWbWEvL9LWXDp2GJmPzmvU5RSvKQ==", "dev": true, - "license": "MIT", "dependencies": { + "@babel/core": "^7.14.0", + "@babel/eslint-parser": "^7.18.2", "@react-native-community/eslint-plugin": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^4.22.1", - "@typescript-eslint/parser": "^4.22.1", - "babel-eslint": "^10.1.0", - "eslint-config-prettier": "^6.10.1", - "eslint-plugin-eslint-comments": "^3.1.2", - "eslint-plugin-flowtype": "2.50.3", - "eslint-plugin-jest": "22.4.1", - "eslint-plugin-prettier": "3.1.2", - "eslint-plugin-react": "^7.20.0", - "eslint-plugin-react-hooks": "^4.0.7", - "eslint-plugin-react-native": "^3.10.0", - "prettier": "^2.0.2" + "@typescript-eslint/eslint-plugin": "^5.30.5", + "@typescript-eslint/parser": "^5.30.5", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-ft-flow": "^2.0.1", + "eslint-plugin-jest": "^26.5.3", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-native": "^4.0.0" }, "peerDependencies": { - "eslint": ">=7" + "eslint": ">=8", + "prettier": ">=2" } }, "node_modules/@react-native-community/eslint-config/node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.33.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^4.0.0", - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -8746,24 +8773,25 @@ } }, "node_modules/@react-native-community/eslint-config/node_modules/@typescript-eslint/parser": { - "version": "4.33.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -8771,29 +8799,153 @@ } } }, - "node_modules/@react-native-community/eslint-config/node_modules/eslint-config-prettier": { - "version": "6.15.0", + "node_modules/@react-native-community/eslint-config/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@react-native-community/eslint-config/node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@react-native-community/eslint-config/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@react-native-community/eslint-config/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@react-native-community/eslint-config/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, - "license": "MIT", "dependencies": { - "get-stdin": "^6.0.0" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@react-native-community/eslint-config/node_modules/eslint-config-prettier": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "dev": true, "bin": { - "eslint-config-prettier-check": "bin/cli.js" + "eslint-config-prettier": "bin/cli.js" }, "peerDependencies": { - "eslint": ">=3.14.1" + "eslint": ">=7.0.0" } }, "node_modules/@react-native-community/eslint-config/node_modules/eslint-plugin-jest": { - "version": "22.4.1", + "version": "26.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.9.0.tgz", + "integrity": "sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==", "dev": true, - "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "peerDependencies": { - "eslint": ">=5" + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/@react-native-community/eslint-config/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@react-native-community/eslint-plugin": { @@ -12778,86 +12930,31 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.2", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.1.tgz", + "integrity": "sha512-kZqi+WZQaZfPKnsflLJQCz6Ze9FFSMfXrrIOcyargekQxG37ES7DJNpJUE9Q/X5n3yTIP/WPutVNzgknQ7biLg==", "dev": true, - "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.2", - "@typescript-eslint/type-utils": "6.13.2", - "@typescript-eslint/utils": "6.13.2", - "@typescript-eslint/visitor-keys": "6.13.2", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/type-utils": "7.13.1", + "@typescript-eslint/utils": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.13.2", - "@typescript-eslint/visitor-keys": "6.13.2" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "6.13.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "6.13.2", - "@typescript-eslint/visitor-keys": "6.13.2", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -12866,153 +12963,48 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "6.13.2", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.1.tgz", + "integrity": "sha512-h5MzFBD5a/Gh/fvNdp9pTfqJAbuQC4sCN2WzuXme71lqFJsZtLbjxfSk4r3p02WIArOF9N94pdsLiGutpDbrXQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.2", - "@typescript-eslint/types": "6.13.2", - "@typescript-eslint/typescript-estree": "6.13.2", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.13.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/typescript-estree": "7.13.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "6.13.2", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.13.1.tgz", + "integrity": "sha512-1ELDPlnLvDQ5ybTSrMhRTFDfOQEOXNM+eP+3HT/Yq7ruWpciQw+Avi73pdEbA4SooCawEWo3dtYbF68gN7Ed1A==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.13.2", - "@typescript-eslint/types": "6.13.2", - "@typescript-eslint/typescript-estree": "6.13.2", - "@typescript-eslint/visitor-keys": "6.13.2", + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/typescript-estree": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.13.2", - "@typescript-eslint/visitor-keys": "6.13.2" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "6.13.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "6.13.2", - "@typescript-eslint/visitor-keys": "6.13.2", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -13020,43 +13012,17 @@ } } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.13.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@typescript-eslint/scope-manager": { - "version": "4.33.0", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.1.tgz", + "integrity": "sha512-adbXNVEs6GmbzaCpymHQ0MB6E4TqoiVbC0iqG3uijR8ZYfpAXMGttouQzF4Oat3P2GxDVIrg7bMI/P65LiQZdg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0" + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1" }, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -13064,24 +13030,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.13.2", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.13.1.tgz", + "integrity": "sha512-aWDbLu1s9bmgPGXSzNCxELu+0+HQOapV/y+60gPXafR8e2g1Bifxzevaa+4L2ytCWm+CHqpELq4CSoN9ELiwCg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.2", - "@typescript-eslint/utils": "6.13.2", + "@typescript-eslint/typescript-estree": "7.13.1", + "@typescript-eslint/utils": "7.13.1", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -13089,49 +13056,58 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.2", + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.1.tgz", + "integrity": "sha512-h5MzFBD5a/Gh/fvNdp9pTfqJAbuQC4sCN2WzuXme71lqFJsZtLbjxfSk4r3p02WIArOF9N94pdsLiGutpDbrXQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.13.2", - "@typescript-eslint/visitor-keys": "6.13.2" + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/typescript-estree": "7.13.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "6.13.2", + "node_modules/@typescript-eslint/types": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.13.1.tgz", + "integrity": "sha512-7K7HMcSQIAND6RBL4kDl24sG/xKM13cA85dc7JnmQXw2cBDngg7c19B++JzvJHRG3zG36n9j1i451GBzRuHchw==", "dev": true, - "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.2", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.1.tgz", + "integrity": "sha512-uxNr51CMV7npU1BxZzYjoVz9iyjckBduFBP0S5sLlh1tXYzHzgZ3BR9SVsNed+LmwKrmnqN3Kdl5t7eZ5TS1Yw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.13.2", - "@typescript-eslint/visitor-keys": "6.13.2", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -13143,93 +13119,28 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "6.13.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.2", - "@typescript-eslint/types": "6.13.2", - "@typescript-eslint/typescript-estree": "6.13.2", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.2", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.13.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "4.33.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "balanced-match": "^1.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.33.0", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16 || 14 >=14.17" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/utils": { @@ -13345,21 +13256,34 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.33.0", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.1.tgz", + "integrity": "sha512-k/Bfne7lrP7hcb7m9zSsgcBmo+8eicqqfNAJ7uUY+jkTFpKeH2FSkWpFRtimBxgkyvqfu9jTPRbYOvud6isdXA==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "4.33.0", - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "7.13.1", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ua/react-native-airship": { "version": "17.2.1", "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-17.2.1.tgz", @@ -13940,14 +13864,6 @@ "version": "1.4.10", "license": "MIT" }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "license": "MIT", @@ -14537,6 +14453,7 @@ "version": "2.0.0", "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=8" } @@ -14989,8 +14906,9 @@ }, "node_modules/babel-plugin-add-module-exports": { "version": "1.0.4", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-1.0.4.tgz", + "integrity": "sha512-g+8yxHUZ60RcyaUpfNzy56OtWW+x9cyEe9j+CranqLiqbju2yf/Cy6ZtYK40EZxtrdHllzlVZgLmcOUCTlJ7Jg==", + "dev": true }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", @@ -18122,11 +18040,12 @@ } }, "node_modules/delay": { - "version": "5.0.0", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz", + "integrity": "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==", "dev": true, - "license": "MIT", "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -18888,17 +18807,6 @@ "node": ">=10.13.0" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/entities": { "version": "2.2.0", "license": "BSD-2-Clause", @@ -19214,420 +19122,6 @@ } }, "node_modules/eslint": { - "version": "7.32.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-airbnb": { - "version": "19.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-config-airbnb-base": "^15.0.0", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5" - }, - "engines": { - "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0" - } - }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-config-airbnb-typescript": { - "version": "17.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-config-airbnb-base": "^15.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", - "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3" - } - }, - "node_modules/eslint-config-expensify": { - "version": "2.0.51", - "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.51.tgz", - "integrity": "sha512-qEUPCI9vsAi5c5E6zM4QEYal13hIRHFvonf4U/x0JI4ceMdAejOoq/Zvt9r1ZwKT1RmA8eRoGWWIQ/4O/9hJPg==", - "dev": true, - "dependencies": { - "@lwc/eslint-plugin-lwc": "^1.7.2", - "@typescript-eslint/parser": "^7.12.0", - "@typescript-eslint/utils": "^7.12.0", - "babel-eslint": "^10.1.0", - "eslint": "^8.56.0", - "eslint-config-airbnb": "19.0.4", - "eslint-config-airbnb-base": "15.0.0", - "eslint-plugin-es": "^4.1.0", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-react": "^7.18.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-rulesdir": "^0.2.2", - "lodash": "^4.17.21", - "underscore": "^1.13.6" - } - }, - "node_modules/eslint-config-expensify/node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-expensify/node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/eslint-config-expensify/node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true - }, - "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/parser": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz", - "integrity": "sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/typescript-estree": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/scope-manager": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz", - "integrity": "sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz", - "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==", - "dev": true, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz", - "integrity": "sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/utils": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.12.0.tgz", - "integrity": "sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/typescript-estree": "7.12.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz", - "integrity": "sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.12.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-config-expensify/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/eslint-config-expensify/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint-config-expensify/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint-config-expensify/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/eslint-config-expensify/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/eslint-config-expensify/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint-config-expensify/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint-config-expensify/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint-config-expensify/node_modules/eslint": { "version": "8.57.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", @@ -19682,142 +19176,227 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-expensify/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/eslint-config-airbnb": { + "version": "19.0.4", "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" } }, - "node_modules/eslint-config-expensify/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", "dev": true, + "license": "MIT", + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^10.12.0 || >=12.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" } }, - "node_modules/eslint-config-expensify/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-18.0.0.tgz", + "integrity": "sha512-oc+Lxzgzsu8FQyFVa4QFaVKiitTYiiW3frB9KYW5OWdPrqFc7FzxgB20hP4cHMlr+MBzGcLl3jnCOVOydL9mIg==", "dev": true, "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "eslint-config-airbnb-base": "^15.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" } }, - "node_modules/eslint-config-expensify/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/eslint-config-expensify": { + "version": "2.0.52", + "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.52.tgz", + "integrity": "sha512-TUhtgsb+EUsfqhEGhSbUVgIypEhZjloYC8PEPxKKniaaG14SW/z1G3C5E4NJQ05xVdRwJ4H+shF7ZzOYbVcraQ==", "dev": true, "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" + "@lwc/eslint-plugin-lwc": "^1.7.2", + "@typescript-eslint/parser": "^7.12.0", + "@typescript-eslint/utils": "^7.12.0", + "babel-eslint": "^10.1.0", + "eslint": "^8.56.0", + "eslint-config-airbnb": "19.0.4", + "eslint-config-airbnb-base": "15.0.0", + "eslint-plugin-es": "^4.1.0", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-react": "^7.18.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-rulesdir": "^0.2.2", + "lodash": "^4.17.21", + "underscore": "^1.13.6" } }, - "node_modules/eslint-config-expensify/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/scope-manager": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz", + "integrity": "sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/eslint-config-expensify/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz", + "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==", "dev": true, "engines": { - "node": ">=8" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/eslint-config-expensify/node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz", + "integrity": "sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==", "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, "engines": { - "node": ">=8" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/eslint-config-expensify/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { - "argparse": "^2.0.1" + "brace-expansion": "^2.0.1" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint-config-expensify/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/utils": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.12.0.tgz", + "integrity": "sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.12.0", + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/typescript-estree": "7.12.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } }, - "node_modules/eslint-config-expensify/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/eslint-config-expensify/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz", + "integrity": "sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@typescript-eslint/types": "7.12.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/eslint-config-expensify/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/eslint-config-expensify/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-config-expensify/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-config-prettier": { - "version": "8.10.0", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, - "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -19933,18 +19512,21 @@ "node": ">=0.8.0" } }, - "node_modules/eslint-plugin-flowtype": { - "version": "2.50.3", + "node_modules/eslint-plugin-ft-flow": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz", + "integrity": "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "lodash": "^4.17.10" + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" }, "engines": { - "node": ">=4" + "node": ">=12.22.0" }, "peerDependencies": { - "eslint": ">=2.0.0" + "@babel/eslint-parser": "^7.12.0", + "eslint": "^8.1.0" } }, "node_modules/eslint-plugin-import": { @@ -20038,25 +19620,52 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "24.7.0", + "version": "28.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.6.0.tgz", + "integrity": "sha512-YG28E1/MIKwnz+e2H7VwYPzHUYU4aMa19w0yGcwXnnmJH6EfgHahTJ2un3IyraUxNfnz/KUhJAFXNNwWPo12tg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/experimental-utils": "^4.0.1" + "@typescript-eslint/utils": "^6.0.0 || ^7.0.0" }, "engines": { - "node": ">=10" + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": ">= 4", - "eslint": ">=5" + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0", + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", + "jest": "*" }, "peerDependenciesMeta": { "@typescript-eslint/eslint-plugin": { "optional": true + }, + "jest": { + "optional": true } } }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.1.tgz", + "integrity": "sha512-h5MzFBD5a/Gh/fvNdp9pTfqJAbuQC4sCN2WzuXme71lqFJsZtLbjxfSk4r3p02WIArOF9N94pdsLiGutpDbrXQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/typescript-estree": "7.13.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, "node_modules/eslint-plugin-jsdoc": { "version": "46.2.6", "dev": true, @@ -20130,18 +19739,24 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "3.1.2", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", "dev": true, - "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=12.0.0" }, "peerDependencies": { - "eslint": ">= 5.0.0", - "prettier": ">= 1.13.0" + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } } }, "node_modules/eslint-plugin-react": { @@ -20185,15 +19800,15 @@ } }, "node_modules/eslint-plugin-react-native": { - "version": "3.11.0", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-native/-/eslint-plugin-react-native-4.1.0.tgz", + "integrity": "sha512-QLo7rzTBOl43FvVqDdq5Ql9IoElIuTdjrz9SKAXCvULvBoRZ44JGSkx9z4999ZusCsb4rK3gjS8gOGyeYqZv2Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.7.4", "eslint-plugin-react-native-globals": "^0.1.1" }, "peerDependencies": { - "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7" + "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8" } }, "node_modules/eslint-plugin-react-native-a11y": { @@ -20214,8 +19829,9 @@ }, "node_modules/eslint-plugin-react-native-globals": { "version": "0.1.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz", + "integrity": "sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==", + "dev": true }, "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", @@ -20303,9 +19919,9 @@ } }, "node_modules/eslint-plugin-you-dont-need-lodash-underscore": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-you-dont-need-lodash-underscore/-/eslint-plugin-you-dont-need-lodash-underscore-6.12.0.tgz", - "integrity": "sha512-WF4mNp+k2532iswT6iUd1BX6qjd3AV4cFy/09VC82GY9SsRtvkxhUIx7JNGSe0/bLyd57oTr4inPFiIaENXhGw==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-you-dont-need-lodash-underscore/-/eslint-plugin-you-dont-need-lodash-underscore-6.14.0.tgz", + "integrity": "sha512-3zkkU/O1agczP7szJGHmisZJS/AknfVl6mb0Zqoc95dvFsdmfK+cbhrn+Ffy0UWB1pgDJwQr7kIO3rPstWs3Dw==", "dev": true, "dependencies": { "kebab-case": "^1.0.0" @@ -20332,23 +19948,6 @@ "node": ">=4.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, "node_modules/eslint-visitor-keys": { "version": "2.1.0", "dev": true, @@ -20357,14 +19956,6 @@ "node": ">=10" } }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, "node_modules/eslint/node_modules/ajv": { "version": "6.12.6", "dev": true, @@ -20394,6 +19985,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -20425,26 +20022,44 @@ "dev": true, "license": "MIT" }, - "node_modules/eslint/node_modules/eslint-utils": { - "version": "2.1.0", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "Apache-2.0", "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, "node_modules/eslint/node_modules/globals": { @@ -20469,12 +20084,25 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", + "node_modules/eslint/node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/eslint/node_modules/json-schema-traverse": { @@ -20505,24 +20133,44 @@ } }, "node_modules/espree": { - "version": "7.3.1", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "Apache-2.0", "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -20782,9 +20430,9 @@ } }, "node_modules/expensify-common": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.17.tgz", - "integrity": "sha512-W7xO10/bYF/p0/cUOtzejXJDiLCB/U6JTXVltzOE70xjGgzTSyRotPkEtEItHTvXOS2Wz8jJ262nrGfFMpfisA==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.19.tgz", + "integrity": "sha512-GdWlYiHOAapy/jxjcvL9NKGOofhoEuKIwvJNGNVHbDXcA+0NxVCNYrHt1yrLnVcE4KtK6PGT6fQ2Lp8NTCoA+g==", "dependencies": { "awesome-phonenumber": "^5.4.0", "classnames": "2.5.0", @@ -22062,11 +21710,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, "node_modules/functions-have-names": { "version": "1.2.3", "dev": true, @@ -23352,8 +22995,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.2.4", - "license": "MIT", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "engines": { "node": ">= 4" } @@ -27702,11 +27346,6 @@ "version": "4.1.1", "license": "MIT" }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.union": { "version": "4.6.0", "dev": true, @@ -29179,6 +28818,12 @@ "version": "1.4.0", "license": "MIT" }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/ncp": { "version": "2.0.0", "license": "MIT", @@ -30833,8 +30478,9 @@ }, "node_modules/prettier-linter-helpers": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, - "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -31961,9 +31607,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.52", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.52.tgz", - "integrity": "sha512-uXlNhQg1UStx1W/U+9GYtIhLvx3vTIpN1WwE1gsiVxvimnUzKpQX/JBkgpR9b48ZoxsdiZXOT5kKLlqGCa6O1g==", + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.53.tgz", + "integrity": "sha512-ObNk5MhLOAVkLgE0NCI04CEO3qaP5ZG+NY1Kn3UnxcHlhyLlDQb10EOiDWSLwNR2s4K3kK+ge7Xmo6N0VdMyyA==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -34273,10 +33919,6 @@ "shellcheck": "shellcheck-stable/shellcheck" } }, - "node_modules/shim-keyboard-event-key": { - "version": "1.0.3", - "license": "MIT" - }, "node_modules/side-channel": { "version": "1.0.4", "license": "MIT", @@ -35153,6 +34795,12 @@ "node": ">=10" } }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "dev": true + }, "node_modules/string-width": { "version": "4.2.3", "license": "MIT", @@ -35594,67 +35242,6 @@ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, - "node_modules/table": { - "version": "6.8.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/table/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/table/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/table/node_modules/slice-ansi": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -36098,8 +35685,9 @@ }, "node_modules/tinycolor2": { "version": "1.6.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "dev": true }, "node_modules/tinyqueue": { "version": "2.0.3", @@ -37030,11 +36618,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "dev": true, - "license": "MIT" - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "devOptional": true, diff --git a/package.json b/package.json index 590972f64299..f5261521cb7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.0-4", + "version": "9.0.1-14", "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.", @@ -66,7 +66,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.85", + "@expensify/react-native-live-markdown": "0.1.88", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -113,11 +113,12 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "2.0.17", + "expensify-common": "2.0.19", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", "expo-image-manipulator": "11.8.0", + "fast-equals": "^4.0.3", "focus-trap-react": "^10.2.3", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -158,7 +159,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.52", + "react-native-onyx": "2.0.53", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -188,8 +189,7 @@ "react-web-config": "^1.0.0", "react-webcam": "^7.1.1", "react-window": "^1.8.9", - "semver": "^7.5.2", - "shim-keyboard-event-key": "^1.0.3" + "semver": "^7.5.2" }, "devDependencies": { "@actions/core": "1.10.0", @@ -205,14 +205,14 @@ "@babel/runtime": "^7.20.0", "@babel/traverse": "^7.22.20", "@babel/types": "^7.22.19", - "@dword-design/eslint-plugin-import-alias": "^4.0.8", + "@dword-design/eslint-plugin-import-alias": "^5.0.0", "@electron/notarize": "^2.1.0", "@jest/globals": "^29.5.0", "@ngneat/falso": "^7.1.1", "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", - "@react-native-community/eslint-config": "3.0.0", + "@react-native-community/eslint-config": "3.2.0", "@react-native/babel-preset": "^0.73.21", "@react-native/metro-config": "^0.73.5", "@react-navigation/devtools": "^6.0.10", @@ -240,12 +240,11 @@ "@types/setimmediate": "^1.0.2", "@types/webpack": "^5.28.5", "@types/webpack-bundle-analyzer": "^4.7.0", - "@typescript-eslint/eslint-plugin": "^6.13.2", - "@typescript-eslint/parser": "^6.13.2", + "@typescript-eslint/eslint-plugin": "^7.13.1", + "@typescript-eslint/parser": "^7.13.1", "@vercel/ncc": "0.38.1", "@welldone-software/why-did-you-render": "7.0.1", "ajv-cli": "^5.0.0", - "babel-eslint": "^10.1.0", "babel-jest": "29.4.1", "babel-loader": "^9.1.3", "babel-plugin-module-resolver": "^5.0.0", @@ -261,18 +260,16 @@ "dotenv": "^16.0.3", "electron": "^29.4.1", "electron-builder": "24.13.2", - "eslint": "^7.6.0", - "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-expensify": "^2.0.51", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jest": "^24.1.0", + "eslint": "^8.57.0", + "eslint-config-airbnb-typescript": "^18.0.0", + "eslint-config-expensify": "^2.0.52", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jest": "^28.6.0", "eslint-plugin-jsdoc": "^46.2.6", - "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-react-native-a11y": "^3.3.0", "eslint-plugin-storybook": "^0.8.0", "eslint-plugin-testing-library": "^6.2.2", - "eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0", + "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", "html-webpack-plugin": "^5.5.0", "jest": "29.4.1", "jest-circus": "29.4.1", diff --git a/patches/@expensify+react-native-live-markdown+0.1.85.patch b/patches/@expensify+react-native-live-markdown+0.1.85.patch new file mode 100644 index 000000000000..f745786a088e --- /dev/null +++ b/patches/@expensify+react-native-live-markdown+0.1.85.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/@expensify/react-native-live-markdown/lib/module/web/cursorUtils.js b/node_modules/@expensify/react-native-live-markdown/lib/module/web/cursorUtils.js +index e975fb2..6a4b510 100644 +--- a/node_modules/@expensify/react-native-live-markdown/lib/module/web/cursorUtils.js ++++ b/node_modules/@expensify/react-native-live-markdown/lib/module/web/cursorUtils.js +@@ -53,7 +53,7 @@ function setCursorPosition(target, start, end = null) { + // 3. Caret at the end of whole input, when pressing enter + // 4. All other placements + if (prevChar === '\n' && prevTextLength !== undefined && prevTextLength < textCharacters.length) { +- if (nextChar !== '\n') { ++ if (nextChar !== '\n' && i !== n - 1 && nextChar) { + range.setStart(textNodes[i + 1], 0); + } else if (i !== textNodes.length - 1) { + range.setStart(textNodes[i], 1); diff --git a/src/CONST.ts b/src/CONST.ts index 71ef5e26f7ae..f8ce1a574d49 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -361,10 +361,9 @@ const CONST = { P2P_DISTANCE_REQUESTS: 'p2pDistanceRequests', WORKFLOWS_DELAYED_SUBMISSION: 'workflowsDelayedSubmission', SPOTNANA_TRAVEL: 'spotnanaTravel', - ACCOUNTING_ON_NEW_EXPENSIFY: 'accountingOnNewExpensify', - XERO_ON_NEW_EXPENSIFY: 'xeroOnNewExpensify', NETSUITE_ON_NEW_EXPENSIFY: 'netsuiteOnNewExpensify', REPORT_FIELDS_FEATURE: 'reportFieldsFeature', + WORKSPACE_FEEDS: 'workspaceFeeds', }, BUTTON_STATES: { DEFAULT: 'default', @@ -943,20 +942,6 @@ const CONST = { RESIZE_DEBOUNCE_TIME: 100, UNREAD_UPDATE_DEBOUNCE_TIME: 300, }, - SEARCH_TABLE_COLUMNS: { - RECEIPT: 'receipt', - DATE: 'date', - MERCHANT: 'merchant', - DESCRIPTION: 'description', - FROM: 'from', - TO: 'to', - CATEGORY: 'category', - TAG: 'tag', - TOTAL_AMOUNT: 'amount', - TYPE: 'type', - ACTION: 'action', - TAX_AMOUNT: 'taxAmount', - }, PRIORITY_MODE: { GSD: 'gsd', DEFAULT: 'default', @@ -1266,6 +1251,7 @@ const CONST = { }, CENTRAL_PANE_ANIMATION_HEIGHT: 200, LHN_SKELETON_VIEW_ITEM_HEIGHT: 64, + SEARCH_SKELETON_VIEW_ITEM_HEIGHT: 108, EXPENSIFY_PARTNER_NAME: 'expensify.com', EMAIL: { ACCOUNTING: 'accounting@expensify.com', @@ -1555,6 +1541,7 @@ const CONST = { }, IOU: { + MAX_RECENT_REPORTS_TO_SHOW: 5, // This is the transactionID used when going through the create expense flow so that it mimics a real transaction (like the edit flow) OPTIMISTIC_TRANSACTION_ID: '1', // Note: These payment types are used when building IOU reportAction message values in the server and should @@ -1731,7 +1718,7 @@ const CONST = { ARE_TAGS_ENABLED: 'areTagsEnabled', ARE_DISTANCE_RATES_ENABLED: 'areDistanceRatesEnabled', ARE_WORKFLOWS_ENABLED: 'areWorkflowsEnabled', - ARE_REPORTFIELDS_ENABLED: 'areReportFieldsEnabled', + ARE_REPORT_FIELDS_ENABLED: 'areReportFieldsEnabled', ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled', ARE_TAXES_ENABLED: 'tax', }, @@ -1833,6 +1820,21 @@ const CONST = { XERO_SYNC_IMPORT_TAX_RATES: 'xeroSyncImportTaxRates', XERO_CHECK_CONNECTION: 'xeroCheckConnection', XERO_SYNC_TITLE: 'xeroSyncTitle', + NETSUITE_SYNC_CONNECTION: 'netSuiteSyncConnection', + NETSUITE_SYNC_CUSTOMERS: 'netSuiteSyncCustomers', + NETSUITE_SYNC_INIT_DATA: 'netSuiteSyncInitData', + NETSUITE_SYNC_IMPORT_TAXES: 'netSuiteSyncImportTaxes', + NETSUITE_SYNC_IMPORT_ITEMS: 'netSuiteSyncImportItems', + NETSUITE_SYNC_DATA: 'netSuiteSyncData', + NETSUITE_SYNC_ACCOUNTS: 'netSuiteSyncAccounts', + NETSUITE_SYNC_CURRENCIES: 'netSuiteSyncCurrencies', + NETSUITE_SYNC_CATEGORIES: 'netSuiteSyncCategories', + NETSUITE_SYNC_IMPORT_EMPLOYEES: 'netSuiteSyncImportEmployees', + NETSUITE_SYNC_REPORT_FIELDS: 'netSuiteSyncReportFields', + NETSUITE_SYNC_TAGS: 'netSuiteSyncTags', + NETSUITE_SYNC_UPDATE_DATA: 'netSuiteSyncUpdateConnectionData', + NETSUITE_SYNC_NETSUITE_REIMBURSED_REPORTS: 'netSuiteSyncNetSuiteReimbursedReports', + NETSUITE_SYNC_EXPENSIFY_REIMBURSED_REPORTS: 'netSuiteSyncExpensifyReimbursedReports', }, SYNC_STAGE_TIMEOUT_MINUTES: 20, }, @@ -2126,6 +2128,8 @@ const CONST = { SETTINGS: 'settings', LEAVE_ROOM: 'leaveRoom', PRIVATE_NOTES: 'privateNotes', + DELETE: 'delete', + MARK_AS_INCOMPLETE: 'markAsIncomplete', }, EDIT_REQUEST_FIELD: { AMOUNT: 'amount', @@ -3524,12 +3528,7 @@ const CONST = { SCAN: 'scan', DISTANCE: 'distance', }, - TAB_SEARCH: { - ALL: 'all', - SHARED: 'shared', - DRAFTS: 'drafts', - FINISHED: 'finished', - }, + STATUS_TEXT_MAX_LENGTH: 100, DROPDOWN_BUTTON_SIZE: { @@ -3735,6 +3734,14 @@ const CONST = { REPORT: 'REPORT', }, + PROMOTED_ACTIONS: { + PIN: 'pin', + SHARE: 'share', + JOIN: 'join', + MESSAGE: 'message', + HOLD: 'hold', + }, + THUMBNAIL_IMAGE: { SMALL_SCREEN: { SIZE: 250, @@ -4822,28 +4829,52 @@ const CONST = { ADHOC: ' AdHoc', }, - SEARCH_TRANSACTION_TYPE: { - CASH: 'cash', - CARD: 'card', - DISTANCE: 'distance', - }, - - SEARCH_RESULTS_PAGE_SIZE: 50, - - SEARCH_DATA_TYPES: { - TRANSACTION: 'transaction', - REPORT: 'report', + SEARCH: { + RESULTS_PAGE_SIZE: 50, + DATA_TYPES: { + TRANSACTION: 'transaction', + REPORT: 'report', + }, + ACTION_TYPES: { + DONE: 'done', + PAID: 'paid', + VIEW: 'view', + }, + TRANSACTION_TYPE: { + CASH: 'cash', + CARD: 'card', + DISTANCE: 'distance', + }, + SORT_ORDER: { + ASC: 'asc', + DESC: 'desc', + }, + TAB: { + ALL: 'all', + SHARED: 'shared', + DRAFTS: 'drafts', + FINISHED: 'finished', + }, + TABLE_COLUMNS: { + RECEIPT: 'receipt', + DATE: 'date', + MERCHANT: 'merchant', + DESCRIPTION: 'description', + FROM: 'from', + TO: 'to', + CATEGORY: 'category', + TAG: 'tag', + TOTAL_AMOUNT: 'amount', + TYPE: 'type', + ACTION: 'action', + TAX_AMOUNT: 'taxAmount', + }, }, REFERRER: { NOTIFICATION: 'notification', }, - SORT_ORDER: { - ASC: 'asc', - DESC: 'desc', - }, - SUBSCRIPTION_SIZE_LIMIT: 20000, PAYMENT_CARD_CURRENCY: { diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8a20032b4f91..b5eea4228042 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -339,6 +339,9 @@ const ONYXKEYS = { /** Holds the checks used while transferring the ownership of the workspace */ POLICY_OWNERSHIP_CHANGE_CHECKS: 'policyOwnershipChangeChecks', + /** Stores info during review duplicates flow */ + REVIEW_DUPLICATES: 'reviewDuplicates', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -719,6 +722,7 @@ type OnyxValuesMapping = { [ONYXKEYS.CACHED_PDF_PATHS]: Record; [ONYXKEYS.POLICY_OWNERSHIP_CHANGE_CHECKS]: Record; [ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE]: OnyxTypes.QuickAction; + [ONYXKEYS.REVIEW_DUPLICATES]: OnyxTypes.ReviewDuplicates; [ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: string; [ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL]: string; [ONYXKEYS.NVP_BILLING_FUND_ID]: number; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c1fdd68951fa..d354ee1c47fd 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -776,6 +776,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/tax/:taxID/value', getRoute: (policyID: string, taxID: string) => `settings/workspaces/${policyID}/tax/${encodeURIComponent(taxID)}/value` as const, }, + WORKSPACE_REPORT_FIELDS: { + route: 'settings/workspaces/:policyID/reportFields', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields` as const, + }, WORKSPACE_DISTANCE_RATES: { route: 'settings/workspaces/:policyID/distance-rates', getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates` as const, @@ -823,6 +827,11 @@ const ROUTES = { route: 'r/:reportID/transaction/:transactionID/receipt', getRoute: (reportID: string, transactionID: string) => `r/${reportID}/transaction/${transactionID}/receipt` as const, }, + TRANSACTION_DUPLICATE_REVIEW_PAGE: { + route: 'r/:threadReportID/duplicates/review', + getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review` as const, + }, + POLICY_ACCOUNTING_XERO_IMPORT: { route: 'settings/workspaces/:policyID/accounting/xero/import', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import` as const, @@ -908,6 +917,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/taxes', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/taxes` as const, }, + RESTRICTED_ACTION: { + route: 'restricted-action/workspace/:policyID', + getRoute: (policyID: string) => `restricted-action/workspace/${policyID}` as const, + }, } as const; /** diff --git a/src/SCREENS.ts b/src/SCREENS.ts index f884cca94ef5..d8a2d166099e 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -138,9 +138,11 @@ const SCREENS = { ROOM_INVITE: 'RoomInvite', REFERRAL: 'Referral', PROCESS_MONEY_REQUEST_HOLD: 'ProcessMoneyRequestHold', + TRANSACTION_DUPLICATE: 'TransactionDuplicate', TRAVEL: 'Travel', SEARCH_REPORT: 'SearchReport', SETTINGS_CATEGORIES: 'SettingsCategories', + RESTRICTED_ACTION: 'RestrictedAction', }, ONBOARDING_MODAL: { ONBOARDING: 'Onboarding', @@ -179,6 +181,10 @@ const SCREENS = { STATE_SELECTOR: 'Money_Request_State_Selector', }, + TRANSACTION_DUPLICATE: { + REVIEW: 'Transaction_Duplicate_Review', + }, + IOU_SEND: { ADD_BANK_ACCOUNT: 'IOU_Send_Add_Bank_Account', ADD_DEBIT_CARD: 'IOU_Send_Add_Debit_Card', @@ -283,6 +289,7 @@ const SCREENS = { TAGS_EDIT: 'Tags_Edit', TAG_EDIT: 'Tag_Edit', TAXES: 'Workspace_Taxes', + REPORT_FIELDS: 'Workspace_ReportFields', TAX_EDIT: 'Workspace_Tax_Edit', TAX_NAME: 'Workspace_Tax_Name', TAX_VALUE: 'Workspace_Tax_Value', @@ -378,6 +385,7 @@ const SCREENS = { KEYBOARD_SHORTCUTS: 'KeyboardShortcuts', TRANSACTION_RECEIPT: 'TransactionReceipt', FEATURE_TRAINING_ROOT: 'FeatureTraining_Root', + RESTRICTED_ACTION_ROOT: 'RestrictedAction_Root', } as const; type Screen = DeepValueOf; diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx index 17a2f6212447..9bd6142b5604 100644 --- a/src/components/AddressSearch/index.tsx +++ b/src/components/AddressSearch/index.tsx @@ -14,6 +14,7 @@ import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as UserLocation from '@libs/actions/UserLocation'; import * as ApiUtils from '@libs/ApiUtils'; import getCurrentPosition from '@libs/getCurrentPosition'; import type {GeolocationErrorCodeType} from '@libs/getCurrentPosition/getCurrentPosition.types'; @@ -249,12 +250,17 @@ function AddressSearch( setIsFetchingCurrentLocation(false); setLocationErrorCode(null); + const {latitude, longitude} = successData.coords; + const location = { - lat: successData.coords.latitude, - lng: successData.coords.longitude, + lat: latitude, + lng: longitude, address: CONST.YOUR_LOCATION_TEXT, name: CONST.YOUR_LOCATION_TEXT, }; + + // Update the current user location + UserLocation.setUserLocation({longitude, latitude}); onPress?.(location); }, (errorData) => { diff --git a/src/components/ConfirmedRoute.tsx b/src/components/ConfirmedRoute.tsx index b6d6bb13213c..315afd2dbddc 100644 --- a/src/components/ConfirmedRoute.tsx +++ b/src/components/ConfirmedRoute.tsx @@ -118,6 +118,7 @@ function ConfirmedRoute({mapboxAccessToken, transaction, isSmallerIcon, shouldHa style={[styles.mapView, shouldHaveBorderRadius && styles.br4]} waypoints={waypointMarkers} styleURL={CONST.MAPBOX.STYLE_URL} + requireRouteToDisplayMap={requireRouteToDisplayMap} /> ) : ( Navigation.goBack()} /> diff --git a/src/components/CustomStylesForChildrenProvider.tsx b/src/components/CustomStylesForChildrenProvider.tsx new file mode 100644 index 000000000000..6fc7efa81ed8 --- /dev/null +++ b/src/components/CustomStylesForChildrenProvider.tsx @@ -0,0 +1,21 @@ +import React, {useMemo} from 'react'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; + +type CustomStylesForChildrenContextType = StyleProp | null; + +const CustomStylesForChildrenContext = React.createContext(null); + +type CustomStylesForChildrenProviderProps = React.PropsWithChildren & { + style: StyleProp | null; +}; + +function CustomStylesForChildrenProvider({children, style}: CustomStylesForChildrenProviderProps) { + const value = useMemo(() => style, [style]); + + return {children}; +} + +CustomStylesForChildrenProvider.displayName = 'CustomStylesForChildrenProvider'; + +export default CustomStylesForChildrenProvider; +export {CustomStylesForChildrenContext}; diff --git a/src/components/DistanceMapView/index.android.tsx b/src/components/DistanceMapView/index.android.tsx index 629b05d7bccf..930b08cad2d5 100644 --- a/src/components/DistanceMapView/index.android.tsx +++ b/src/components/DistanceMapView/index.android.tsx @@ -3,18 +3,21 @@ import {View} from 'react-native'; import BlockingView from '@components/BlockingViews/BlockingView'; import * as Expensicons from '@components/Icon/Expensicons'; import MapView from '@components/MapView'; +import PendingMapView from '@components/MapView/PendingMapView'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type DistanceMapViewProps from './types'; -function DistanceMapView({overlayStyle, ...rest}: DistanceMapViewProps) { +function DistanceMapView({overlayStyle, requireRouteToDisplayMap, ...rest}: DistanceMapViewProps) { const styles = useThemeStyles(); const [isMapReady, setIsMapReady] = useState(false); const {isOffline} = useNetwork(); const {translate} = useLocalize(); const theme = useTheme(); + const StyleUtils = useStyleUtils(); return ( <> @@ -29,14 +32,22 @@ function DistanceMapView({overlayStyle, ...rest}: DistanceMapViewProps) { }} /> {!isMapReady && ( - - + + {/* The "map pending" text should only be shown in the IOU create flow. In the created IOU preview, only the icon should be shown. */} + {!requireRouteToDisplayMap ? ( + + ) : ( + + )} )} diff --git a/src/components/DistanceMapView/types.ts b/src/components/DistanceMapView/types.ts index 18213235445f..1621870d70ed 100644 --- a/src/components/DistanceMapView/types.ts +++ b/src/components/DistanceMapView/types.ts @@ -3,6 +3,10 @@ import type {MapViewProps} from '@components/MapView/MapViewTypes'; type DistanceMapViewProps = MapViewProps & { overlayStyle?: StyleProp; + + /** Whether it should display the Mapbox map only when the route/coordinates exist otherwise + * it will display pending map icon */ + requireRouteToDisplayMap?: boolean; }; export default DistanceMapViewProps; diff --git a/src/components/FeedbackSurvey.tsx b/src/components/FeedbackSurvey.tsx index 925448046076..a7b0732be1fb 100644 --- a/src/components/FeedbackSurvey.tsx +++ b/src/components/FeedbackSurvey.tsx @@ -92,7 +92,7 @@ function FeedbackSurvey({title, description, onSubmit, optionRowStyles}: Feedbac diff --git a/src/components/FlatList/index.tsx b/src/components/FlatList/index.tsx index 9f42e9597c79..f54eddcbeb79 100644 --- a/src/components/FlatList/index.tsx +++ b/src/components/FlatList/index.tsx @@ -44,6 +44,8 @@ function MVCPFlatList({maintainVisibleContentPosition, horizontal = false const mutationObserverRef = useRef(null); const lastScrollOffsetRef = useRef(0); const isListRenderedRef = useRef(false); + const mvcpAutoscrollToTopThresholdRef = useRef(mvcpAutoscrollToTopThreshold); + mvcpAutoscrollToTopThresholdRef.current = mvcpAutoscrollToTopThreshold; const getScrollOffset = useCallback((): number => { if (!scrollRef.current) { @@ -105,11 +107,11 @@ function MVCPFlatList({maintainVisibleContentPosition, horizontal = false const scrollOffset = getScrollOffset(); prevFirstVisibleOffsetRef.current = firstVisibleViewOffset; scrollToOffset(scrollOffset + delta, false); - if (mvcpAutoscrollToTopThreshold != null && scrollOffset <= mvcpAutoscrollToTopThreshold) { + if (mvcpAutoscrollToTopThresholdRef.current != null && scrollOffset <= mvcpAutoscrollToTopThresholdRef.current) { scrollToOffset(0, true); } } - }, [getScrollOffset, scrollToOffset, mvcpMinIndexForVisible, mvcpAutoscrollToTopThreshold, horizontal]); + }, [getScrollOffset, scrollToOffset, mvcpMinIndexForVisible, horizontal]); const setupMutationObserver = useCallback(() => { const contentView = getContentView(); diff --git a/src/components/FocusTrap/FocusTrapForModal/index.web.tsx b/src/components/FocusTrap/FocusTrapForModal/index.web.tsx index 1ea425f0f9fd..be5da8c49a78 100644 --- a/src/components/FocusTrap/FocusTrapForModal/index.web.tsx +++ b/src/components/FocusTrap/FocusTrapForModal/index.web.tsx @@ -9,7 +9,7 @@ function FocusTrapForModal({children, active}: FocusTrapForModalProps) { active={active} focusTrapOptions={{ trapStack: sharedTrapStack, - allowOutsideClick: true, + clickOutsideDeactivates: true, initialFocus: false, fallbackFocus: document.body, }} diff --git a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts index 5f10a7293457..be772c6ae10c 100644 --- a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts +++ b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts @@ -30,8 +30,11 @@ const WIDE_LAYOUT_INACTIVE_SCREENS: string[] = [ SCREENS.WORKSPACE.MORE_FEATURES, SCREENS.WORKSPACE.TAGS, SCREENS.WORKSPACE.TAXES, + SCREENS.WORKSPACE.REPORT_FIELDS, SCREENS.WORKSPACE.DISTANCE_RATES, SCREENS.SEARCH.CENTRAL_PANE, + SCREENS.SETTINGS.TROUBLESHOOT, + SCREENS.SETTINGS.SAVE_THE_WORLD, ]; export default WIDE_LAYOUT_INACTIVE_SCREENS; diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index bed4cea31829..9df94e4c6114 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -350,7 +350,7 @@ function FormProvider( }); if (inputProps.shouldSaveDraft && !formID.includes('Draft')) { - FormActions.setDraftValues(formID as OnyxFormKey, {[inputKey]: value}); + FormActions.setDraftValues(formID, {[inputKey]: value}); } inputProps.onValueChange?.(value, inputKey); }, diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx index 27c7f229a247..49850d73e2d7 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx @@ -33,7 +33,7 @@ const getMentionDetails = (htmlAttributeReportID: string, currentReport: OnyxEnt // Get mention details based on reportID from tag attribute if (!isEmpty(htmlAttributeReportID)) { - const report = reports?.[htmlAttributeReportID]; + const report = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${htmlAttributeReportID}`]; reportID = report?.reportID ?? htmlAttributeReportID; mentionDisplayText = removeLeadingLTRAndHash(report?.reportName ?? report?.displayName ?? htmlAttributeReportID); // Get mention details from name inside tnode diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 3fe36239d631..e699badc43ec 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -61,6 +61,7 @@ import House from '@assets/images/simple-illustrations/simple-illustration__hous import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg'; import Lightbulb from '@assets/images/simple-illustrations/simple-illustration__lightbulb.svg'; import LockClosed from '@assets/images/simple-illustrations/simple-illustration__lockclosed.svg'; +import LockClosedOrange from '@assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg'; import LockOpen from '@assets/images/simple-illustrations/simple-illustration__lockopen.svg'; import Luggage from '@assets/images/simple-illustrations/simple-illustration__luggage.svg'; import Mailbox from '@assets/images/simple-illustrations/simple-illustration__mailbox.svg'; @@ -192,4 +193,5 @@ export { SendMoney, CheckmarkCircle, CreditCardEyes, + LockClosedOrange, }; diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index c6be99297182..283f7c396edb 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -25,7 +25,7 @@ import type {ComponentProps, MapViewOnyxProps} from './types'; import utils from './utils'; const MapView = forwardRef( - ({accessToken, style, mapPadding, userLocation: cachedUserLocation, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, onMapReady, interactive = true}, ref) => { + ({accessToken, style, mapPadding, userLocation, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, onMapReady, interactive = true}, ref) => { const navigation = useNavigation(); const {isOffline} = useNetwork(); const {translate} = useLocalize(); @@ -34,7 +34,7 @@ const MapView = forwardRef( const cameraRef = useRef(null); const [isIdle, setIsIdle] = useState(false); const initialLocation = useMemo(() => initialState && {longitude: initialState.location[0], latitude: initialState.location[1]}, [initialState]); - const [currentPosition, setCurrentPosition] = useState(cachedUserLocation ?? initialLocation); + const currentPosition = userLocation ?? initialLocation; const [userInteractedWithMap, setUserInteractedWithMap] = useState(false); const shouldInitializeCurrentPosition = useRef(true); @@ -50,7 +50,6 @@ const MapView = forwardRef( return; } UserLocation.clearUserLocation(); - setCurrentPosition(initialLocation); }, [initialLocation], ); @@ -74,7 +73,6 @@ const MapView = forwardRef( getCurrentPosition((params) => { const currentCoords = {longitude: params.coords.longitude, latitude: params.coords.latitude}; - setCurrentPosition(currentCoords); UserLocation.setUserLocation(currentCoords); }, setCurrentPositionToInitialState); }, [isOffline, shouldPanMapToCurrentPosition, setCurrentPositionToInitialState]), diff --git a/src/components/MapView/MapView.website.tsx b/src/components/MapView/MapView.website.tsx index c573550e49fb..01d7134a96da 100644 --- a/src/components/MapView/MapView.website.tsx +++ b/src/components/MapView/MapView.website.tsx @@ -12,6 +12,7 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; +import usePrevious from '@hooks/usePrevious'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -39,7 +40,7 @@ const MapView = forwardRef( waypoints, mapPadding, accessToken, - userLocation: cachedUserLocation, + userLocation, directionCoordinates, initialState = {location: CONST.MAPBOX.DEFAULT_COORDINATE, zoom: CONST.MAPBOX.DEFAULT_ZOOM}, interactive = true, @@ -55,7 +56,8 @@ const MapView = forwardRef( const [mapRef, setMapRef] = useState(null); const initialLocation = useMemo(() => ({longitude: initialState.location[0], latitude: initialState.location[1]}), [initialState]); - const [currentPosition, setCurrentPosition] = useState(cachedUserLocation ?? initialLocation); + const currentPosition = userLocation ?? initialLocation; + const prevUserPosition = usePrevious(currentPosition); const [userInteractedWithMap, setUserInteractedWithMap] = useState(false); const [shouldResetBoundaries, setShouldResetBoundaries] = useState(false); const setRef = useCallback((newRef: MapRef | null) => setMapRef(newRef), []); @@ -73,7 +75,6 @@ const MapView = forwardRef( return; } UserLocation.clearUserLocation(); - setCurrentPosition(initialLocation); }, [initialLocation], ); @@ -97,7 +98,6 @@ const MapView = forwardRef( getCurrentPosition((params) => { const currentCoords = {longitude: params.coords.longitude, latitude: params.coords.latitude}; - setCurrentPosition(currentCoords); UserLocation.setUserLocation(currentCoords); }, setCurrentPositionToInitialState); }, [isOffline, shouldPanMapToCurrentPosition, setCurrentPositionToInitialState]), @@ -112,11 +112,15 @@ const MapView = forwardRef( return; } + // Avoid animating the naviagtion to the same location + const shouldAnimate = prevUserPosition.longitude !== currentPosition.longitude || prevUserPosition.latitude !== currentPosition.latitude; + mapRef.flyTo({ center: [currentPosition.longitude, currentPosition.latitude], zoom: CONST.MAPBOX.DEFAULT_ZOOM, + animate: shouldAnimate, }); - }, [currentPosition, userInteractedWithMap, mapRef, shouldPanMapToCurrentPosition]); + }, [currentPosition, mapRef, prevUserPosition, shouldPanMapToCurrentPosition]); const resetBoundaries = useCallback(() => { if (!waypoints || waypoints.length === 0) { diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index fa58b5cd5f5f..8d5fe98bb99d 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -514,7 +514,7 @@ function MenuItem( ...(Array.isArray(wrapperStyle) ? wrapperStyle : [wrapperStyle]), !focused && (isHovered || pressed) && hoverAndPressStyle, shouldGreyOutWhenDisabled && disabled && styles.buttonOpacityDisabled, - isHovered && !pressed && styles.hoveredComponentBG, + isHovered && interactive && !pressed && styles.hoveredComponentBG, ] as StyleProp } disabledStyle={shouldUseDefaultCursorWhenDisabled && [styles.cursorDefault]} diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 8db2901a6116..8bfcbbeb779e 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -1,20 +1,17 @@ import {useIsFocused} from '@react-navigation/native'; -import {format} from 'date-fns'; -import {Str} from 'expensify-common'; -import React, {useCallback, useEffect, useMemo, useReducer, useState} from 'react'; +import lodashIsEqual from 'lodash/isEqual'; +import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; import type {TextStyle} from 'react-native'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import {MouseProvider} from '@hooks/useMouseContext'; -import useNetwork from '@hooks/useNetwork'; import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; @@ -26,13 +23,9 @@ import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import {getCustomUnitRate, isTaxTrackingEnabled} from '@libs/PolicyUtils'; -import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; -import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; -import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import * as IOU from '@userActions/IOU'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; @@ -46,22 +39,14 @@ import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type {SplitShares} from '@src/types/onyx/Transaction'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import type {DropdownOption} from './ButtonWithDropdownMenu/types'; -import ConfirmedRoute from './ConfirmedRoute'; -import ConfirmModal from './ConfirmModal'; import FormHelpMessage from './FormHelpMessage'; -import MenuItem from './MenuItem'; -import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import MoneyRequestAmountInput from './MoneyRequestAmountInput'; -import PDFThumbnail from './PDFThumbnail'; +import MoneyRequestConfirmationListFooter from './MoneyRequestConfirmationListFooter'; import {PressableWithFeedback} from './Pressable'; -import PressableWithoutFocus from './Pressable/PressableWithoutFocus'; -import ReceiptEmptyState from './ReceiptEmptyState'; -import ReceiptImage from './ReceiptImage'; import SelectionList from './SelectionList'; import type {SectionListDataType} from './SelectionList/types'; import UserListItem from './SelectionList/UserListItem'; import SettlementButton from './SettlementButton'; -import ShowMoreButton from './ShowMoreButton'; import Text from './Text'; type MoneyRequestConfirmationListOnyxProps = { @@ -89,9 +74,6 @@ type MoneyRequestConfirmationListOnyxProps = { /** Last selected distance rates */ lastSelectedDistanceRates: OnyxEntry>; - /** The list of all policies */ - allPolicies: OnyxCollection; - /** List of currencies */ currencyList: OnyxEntry; }; @@ -219,20 +201,18 @@ function MoneyRequestConfirmationList({ reportActionID, defaultMileageRate, lastSelectedDistanceRates, - allPolicies, action = CONST.IOU.ACTION.CREATE, currencyList, shouldDisplayReceipt = false, }: MoneyRequestConfirmationListProps) { const policy = policyReal ?? policyDraft; const policyCategories = policyCategoriesReal ?? policyCategoriesDraft; - const theme = useTheme(); + const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate, toLocaleDigit} = useLocalize(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const {canUseP2PDistanceRequests, canUseViolations} = usePermissions(iouType); - const {isOffline} = useNetwork(); + const {canUseP2PDistanceRequests} = usePermissions(iouType); const isTypeRequest = iouType === CONST.IOU.TYPE.SUBMIT; const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; @@ -267,44 +247,15 @@ function MoneyRequestConfirmationList({ const currency = (mileageRate as MileageRate)?.currency ?? policyCurrency; - const taxRates = policy?.taxRates ?? null; - // A flag for showing the categories field const shouldShowCategories = isPolicyExpenseChat && (!!iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); - // A flag and a toggler for showing the rest of the form fields - const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); - - // Do not hide fields in case of paying someone - const shouldShowAllFields = !!isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || !!isEditingSplitBill; - - // In Send Money and Split Bill with Scan flow, we don't allow the Merchant or Date to be edited. For distance requests, don't show the merchant as there's already another "Distance" menu item - const shouldShowDate = (shouldShowSmartScanFields || isDistanceRequest) && !isTypeSend; const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest && !isTypeSend; const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); - const senderWorkspace = useMemo(() => { - const senderWorkspaceParticipant = selectedParticipantsProp.find((participant) => participant.isSender); - return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${senderWorkspaceParticipant?.policyID}`]; - }, [allPolicies, selectedParticipantsProp]); - - const canUpdateSenderWorkspace = useMemo(() => { - const isInvoiceRoomParticipant = selectedParticipantsProp.some((participant) => participant.isInvoiceRoom); - - return PolicyUtils.canSendInvoice(allPolicies) && !!transaction?.isFromGlobalCreate && !isInvoiceRoomParticipant; - }, [allPolicies, selectedParticipantsProp, transaction?.isFromGlobalCreate]); - - const canModifyTaxFields = !isReadOnly && !isDistanceRequest; - - // A flag for showing the tags field - // TODO: remove the !isTypeInvoice from this condition after BE supports tags for invoices: https://github.com/Expensify/App/issues/41281 - const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists) && !isTypeInvoice, [isPolicyExpenseChat, policyTagLists, isTypeInvoice]); - const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest) && !isTypeInvoice; - // A flag for showing the billable field - const shouldShowBillable = policy?.disabledFields?.defaultBillable === false; const isMovingTransactionFromTrackExpense = IOUUtils.isMovingTransactionFromTrackExpense(action); const hasRoute = TransactionUtils.hasRoute(transaction, isDistanceRequest); const isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate) && !isMovingTransactionFromTrackExpense; @@ -314,8 +265,6 @@ function MoneyRequestConfirmationList({ shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0) : iouAmount, isDistanceRequest ? currency : iouCurrencyCode, ); - const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode); - const taxRateTitle = TransactionUtils.getTaxName(policy, transaction); const previousTransactionAmount = usePrevious(transaction?.amount); const previousTransactionCurrency = usePrevious(transaction?.currency); @@ -326,14 +275,6 @@ function MoneyRequestConfirmationList({ const [didConfirm, setDidConfirm] = useState(false); const [didConfirmSplit, setDidConfirmSplit] = useState(false); - const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); - const [invalidAttachmentPromt, setInvalidAttachmentPromt] = useState(translate('attachmentPicker.protectedPDFNotSupported')); - - const navigateBack = useCallback( - () => Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)), - [iouType, reportID, transactionID], - ); - const shouldDisplayFieldError: boolean = useMemo(() => { if (!isEditingSplitBill) { return false; @@ -344,7 +285,6 @@ function MoneyRequestConfirmationList({ const isMerchantEmpty = useMemo(() => !iouMerchant || TransactionUtils.isMerchantMissing(transaction), [transaction, iouMerchant]); const isMerchantRequired = (isPolicyExpenseChat || isTypeInvoice) && (!isScanRequest || isEditingSplitBill) && shouldShowMerchant; - const shouldDisplayMerchantError = isMerchantRequired && (shouldDisplayFieldError || formError === 'iou.error.invalidMerchant') && isMerchantEmpty; const isCategoryRequired = !!policy?.requiresCategory; @@ -846,437 +786,51 @@ function MoneyRequestConfirmationList({ translate, ]); - // An intermediate structure that helps us classify the fields as "primary" and "supplementary". - // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. - const classifiedFields = [ - { - item: ( - { - if (isDistanceRequest) { - return; - } - - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); - }} - style={[styles.moneyRequestMenuItem, styles.mt2]} - titleStyle={styles.moneyRequestConfirmationAmount} - disabled={didConfirm} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} - errorText={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? translate('common.error.enterAmount') : ''} - /> - ), - shouldShow: shouldShowSmartScanFields, - isSupplementary: false, - }, - { - item: ( - { - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID), - ); - }} - style={[styles.moneyRequestMenuItem]} - titleStyle={styles.flex1} - disabled={didConfirm} - interactive={!isReadOnly} - numberOfLinesTitle={2} - /> - ), - shouldShow: true, - isSupplementary: false, - }, - { - item: ( - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} - disabled={didConfirm} - // todo: handle edit for transaction while moving from track expense - interactive={!isReadOnly && !isMovingTransactionFromTrackExpense} - /> - ), - shouldShow: isDistanceRequest && !canUseP2PDistanceRequests, - isSupplementary: false, - }, - { - item: ( - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} - disabled={didConfirm} - interactive={!isReadOnly} - /> - ), - shouldShow: isDistanceRequest && canUseP2PDistanceRequests, - isSupplementary: false, - }, - { - item: ( - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} - disabled={didConfirm} - interactive={!!rate && !isReadOnly && isPolicyExpenseChat} - /> - ), - shouldShow: isDistanceRequest && canUseP2PDistanceRequests, - isSupplementary: false, - }, - { - item: ( - { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); - }} - disabled={didConfirm} - interactive={!isReadOnly} - brickRoadIndicator={shouldDisplayMerchantError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} - errorText={shouldDisplayMerchantError ? translate('common.error.fieldRequired') : ''} - rightLabel={isMerchantRequired && !shouldDisplayMerchantError ? translate('common.required') : ''} - numberOfLinesTitle={2} - /> - ), - shouldShow: shouldShowMerchant, - isSupplementary: !isMerchantRequired, - }, - { - item: ( - { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID)); - }} - disabled={didConfirm} - interactive={!isReadOnly} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} - errorText={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? translate('common.error.enterDate') : ''} - /> - ), - shouldShow: shouldShowDate, - isSupplementary: true, - }, - { - item: ( - - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID)) - } - style={[styles.moneyRequestMenuItem]} - titleStyle={styles.flex1} - disabled={didConfirm} - interactive={!isReadOnly} - rightLabel={isCategoryRequired && canUseViolations ? translate('common.required') : ''} - /> - ), - shouldShow: shouldShowCategories, - isSupplementary: action === CONST.IOU.ACTION.CATEGORIZE ? false : !isCategoryRequired, - }, - ...policyTagLists.map(({name, required}, index) => { - const isTagRequired = required ?? false; - return { - item: ( - - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(action, iouType, index, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID), - ) - } - style={[styles.moneyRequestMenuItem]} - disabled={didConfirm} - interactive={!isReadOnly} - rightLabel={isTagRequired && canUseViolations ? translate('common.required') : ''} - /> - ), - shouldShow: shouldShowTags, - isSupplementary: !isTagRequired, - }; - }), - { - item: ( - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} - disabled={didConfirm} - interactive={canModifyTaxFields} - /> - ), - shouldShow: shouldShowTax, - isSupplementary: true, - }, - { - item: ( - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} - disabled={didConfirm} - interactive={canModifyTaxFields} - /> - ), - shouldShow: shouldShowTax, - isSupplementary: true, - }, - { - item: ( - - onToggleBillable?.(isOn)} - isActive={iouIsBillable} - disabled={isReadOnly} - titleStyle={!iouIsBillable && {color: theme.textSupporting}} - wrapperStyle={styles.flex1} - /> - - ), - shouldShow: shouldShowBillable, - isSupplementary: true, - }, - ]; - - const primaryFields = classifiedFields.filter((classifiedField) => classifiedField.shouldShow && !classifiedField.isSupplementary).map((primaryField) => primaryField.item); - - const supplementaryFields = classifiedFields - .filter((classifiedField) => classifiedField.shouldShow && classifiedField.isSupplementary) - .map((supplementaryField) => supplementaryField.item); - - const { - image: receiptImage, - thumbnail: receiptThumbnail, - isThumbnail, - fileExtension, - isLocalFile, - } = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); - - const resolvedThumbnail = isLocalFile ? receiptThumbnail : tryResolveUrlFromApiRoot(receiptThumbnail ?? ''); - const resolvedReceiptImage = isLocalFile ? receiptImage : tryResolveUrlFromApiRoot(receiptImage ?? ''); - - const receiptThumbnailContent = useMemo( - () => ( - - {isLocalFile && Str.isPDF(receiptFilename) ? ( - Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(reportID ?? '', transactionID ?? ''))} - accessibilityRole={CONST.ROLE.BUTTON} - accessibilityLabel={translate('accessibilityHints.viewAttachment')} - disabled={!shouldDisplayReceipt} - disabledStyle={styles.cursorDefault} - > - { - setIsAttachmentInvalid(true); - setInvalidAttachmentPromt(translate('attachmentPicker.protectedPDFNotSupported')); - }} - onLoadError={() => { - setInvalidAttachmentPromt(translate('attachmentPicker.errorWhileSelectingCorruptedAttachment')); - setIsAttachmentInvalid(true); - }} - /> - - ) : ( - Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(reportID ?? '', transactionID ?? ''))} - disabled={!shouldDisplayReceipt || isThumbnail} - accessibilityRole={CONST.ROLE.BUTTON} - accessibilityLabel={translate('accessibilityHints.viewAttachment')} - disabledStyle={styles.cursorDefault} - > - - - )} - - ), - [ - isLocalFile, - receiptFilename, - resolvedThumbnail, - styles.moneyRequestImage, - isAttachmentInvalid, - isThumbnail, - resolvedReceiptImage, - receiptThumbnail, - fileExtension, - isDistanceRequest, - reportID, - transactionID, - translate, - styles.cursorDefault, - shouldDisplayReceipt, - ], - ); - - const listFooterContent = useMemo( - () => ( - <> - {isTypeInvoice && ( - { - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_SEND_FROM.getRoute(iouType, transaction?.transactionID ?? '-1', reportID, Navigation.getActiveRouteWithoutParams()), - ); - }} - style={styles.moneyRequestMenuItem} - labelStyle={styles.mt2} - titleStyle={styles.flex1} - disabled={didConfirm} - /> - )} - {isDistanceRequest && ( - - - - )} - {!isDistanceRequest && - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - (receiptImage || receiptThumbnail - ? receiptThumbnailContent - : // The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate") - PolicyUtils.isPaidGroupPolicy(policy) && - !isDistanceRequest && - iouType === CONST.IOU.TYPE.SUBMIT && ( - - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()), - ) - } - /> - ))} - {primaryFields} - {!shouldShowAllFields && ( - - )} - {shouldShowAllFields && supplementaryFields} - - - ), - [ - canUpdateSenderWorkspace, - didConfirm, - iouType, - isAttachmentInvalid, - isDistanceRequest, - isReadOnly, - isTypeInvoice, - navigateBack, - policy, - primaryFields, - receiptImage, - receiptThumbnail, - receiptThumbnailContent, - reportID, - senderWorkspace?.avatarURL, - senderWorkspace?.name, - senderWorkspace?.id, - shouldShowAllFields, - styles.confirmationListMapItem, - styles.flex1, - styles.mb2, - styles.mb5, - styles.moneyRequestMenuItem, - styles.mt1, - styles.mt2, - supplementaryFields, - transaction, - transactionID, - translate, - invalidAttachmentPromt, - ], + const listFooterContent = ( + ); return ( @@ -1326,10 +880,43 @@ export default withOnyx + lodashIsEqual(prevProps.transaction, nextProps.transaction) && + prevProps.onSendMoney === nextProps.onSendMoney && + prevProps.onConfirm === nextProps.onConfirm && + prevProps.iouType === nextProps.iouType && + prevProps.iouAmount === nextProps.iouAmount && + prevProps.isDistanceRequest === nextProps.isDistanceRequest && + prevProps.isPolicyExpenseChat === nextProps.isPolicyExpenseChat && + prevProps.iouCategory === nextProps.iouCategory && + prevProps.shouldShowSmartScanFields === nextProps.shouldShowSmartScanFields && + prevProps.isEditingSplitBill === nextProps.isEditingSplitBill && + prevProps.iouCurrencyCode === nextProps.iouCurrencyCode && + prevProps.iouMerchant === nextProps.iouMerchant && + lodashIsEqual(prevProps.selectedParticipants, nextProps.selectedParticipants) && + lodashIsEqual(prevProps.payeePersonalDetails, nextProps.payeePersonalDetails) && + prevProps.isReadOnly === nextProps.isReadOnly && + prevProps.bankAccountRoute === nextProps.bankAccountRoute && + prevProps.policyID === nextProps.policyID && + prevProps.reportID === nextProps.reportID && + prevProps.receiptPath === nextProps.receiptPath && + prevProps.iouComment === nextProps.iouComment && + prevProps.receiptFilename === nextProps.receiptFilename && + prevProps.iouCreated === nextProps.iouCreated && + prevProps.iouIsBillable === nextProps.iouIsBillable && + prevProps.onToggleBillable === nextProps.onToggleBillable && + prevProps.hasSmartScanFailed === nextProps.hasSmartScanFailed && + prevProps.reportActionID === nextProps.reportActionID && + lodashIsEqual(prevProps.defaultMileageRate, nextProps.defaultMileageRate) && + lodashIsEqual(prevProps.lastSelectedDistanceRates, nextProps.lastSelectedDistanceRates) && + lodashIsEqual(prevProps.action, nextProps.action) && + lodashIsEqual(prevProps.currencyList, nextProps.currencyList) && + prevProps.shouldDisplayReceipt === nextProps.shouldDisplayReceipt, + ), +); diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx new file mode 100644 index 000000000000..cda43938a18f --- /dev/null +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -0,0 +1,711 @@ +import {format} from 'date-fns'; +import {Str} from 'expensify-common'; +import lodashIsEqual from 'lodash/isEqual'; +import React, {memo, useMemo, useReducer, useState} from 'react'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import usePermissions from '@hooks/usePermissions'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import * as ReceiptUtils from '@libs/ReceiptUtils'; +import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; +import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; +import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; +import CONST from '@src/CONST'; +import type {IOUAction, IOUType} from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Participant} from '@src/types/onyx/IOU'; +import type {Unit} from '@src/types/onyx/Policy'; +import ConfirmedRoute from './ConfirmedRoute'; +import ConfirmModal from './ConfirmModal'; +import MenuItem from './MenuItem'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; +import PDFThumbnail from './PDFThumbnail'; +import PressableWithoutFocus from './Pressable/PressableWithoutFocus'; +import ReceiptEmptyState from './ReceiptEmptyState'; +import ReceiptImage from './ReceiptImage'; +import ShowMoreButton from './ShowMoreButton'; + +type MoneyRequestConfirmationListFooterProps = { + /** The action to perform */ + action: IOUAction; + + /** Flag indicating if P2P distance requests can be used */ + canUseP2PDistanceRequests: boolean | undefined; + + /** The currency of the transaction */ + currency: string; + + /** Flag indicating if the confirmation is done */ + didConfirm: boolean; + + /** The distance of the transaction */ + distance: number; + + /** The formatted amount of the transaction */ + formattedAmount: string; + + /** The error message for the form */ + formError: string; + + /** Flag indicating if there is a route */ + hasRoute: boolean; + + /** The category of the IOU */ + iouCategory: string; + + /** The comment of the IOU */ + iouComment: string | undefined; + + /** The creation date of the IOU */ + iouCreated: string | undefined; + + /** The currency code of the IOU */ + iouCurrencyCode: string | undefined; + + /** Flag indicating if the IOU is billable */ + iouIsBillable: boolean; + + /** The merchant of the IOU */ + iouMerchant: string | undefined; + + /** The type of the IOU */ + iouType: Exclude; + + /** Flag indicating if the category is required */ + isCategoryRequired: boolean; + + /** Flag indicating if it is a distance request */ + isDistanceRequest: boolean; + + /** Flag indicating if it is editing a split bill */ + isEditingSplitBill: boolean | undefined; + + /** Flag indicating if the merchant is empty */ + isMerchantEmpty: boolean; + + /** Flag indicating if the merchant is required */ + isMerchantRequired: boolean | undefined; + + /** Flag indicating if the transaction is moved from track expense */ + isMovingTransactionFromTrackExpense: boolean; + + /** Flag indicating if it is a policy expense chat */ + isPolicyExpenseChat: boolean; + + /** Flag indicating if it is read-only */ + isReadOnly: boolean; + + /** Flag indicating if it is an invoice type */ + isTypeInvoice: boolean; + + /** Function to toggle billable */ + onToggleBillable?: (isOn: boolean) => void; + + /** The policy */ + policy: OnyxEntry; + + /** The policy tag lists */ + policyTagLists: Array>; + + /** The rate of the transaction */ + rate: number | undefined; + + /** The filename of the receipt */ + receiptFilename: string; + + /** The path of the receipt */ + receiptPath: string; + + /** The report action ID */ + reportActionID: string | undefined; + + /** The report ID */ + reportID: string; + + /** The selected participants */ + selectedParticipants: Participant[]; + + /** Flag indicating if the field error should be displayed */ + shouldDisplayFieldError: boolean; + + /** Flag indicating if the receipt should be displayed */ + shouldDisplayReceipt: boolean; + + /** Flag indicating if the categories should be shown */ + shouldShowCategories: boolean; + + /** Flag indicating if the merchant should be shown */ + shouldShowMerchant: boolean; + + /** Flag indicating if the smart scan fields should be shown */ + shouldShowSmartScanFields: boolean; + + /** Flag indicating if the tax should be shown */ + shouldShowTax: boolean; + + /** The transaction */ + transaction: OnyxEntry; + + /** The transaction ID */ + transactionID: string; + + /** The unit */ + unit: Unit | undefined; +}; + +function MoneyRequestConfirmationListFooter({ + action, + canUseP2PDistanceRequests, + currency, + didConfirm, + distance, + formattedAmount, + formError, + hasRoute, + iouCategory, + iouComment, + iouCreated, + iouCurrencyCode, + iouIsBillable, + iouMerchant, + iouType, + isCategoryRequired, + isDistanceRequest, + isEditingSplitBill, + isMerchantEmpty, + isMerchantRequired, + isMovingTransactionFromTrackExpense, + isPolicyExpenseChat, + isReadOnly, + isTypeInvoice, + onToggleBillable, + policy, + policyTagLists, + rate, + receiptFilename, + receiptPath, + reportActionID, + reportID, + selectedParticipants, + shouldDisplayFieldError, + shouldDisplayReceipt, + shouldShowCategories, + shouldShowMerchant, + shouldShowSmartScanFields, + shouldShowTax, + transaction, + transactionID, + unit, +}: MoneyRequestConfirmationListFooterProps) { + const styles = useThemeStyles(); + const theme = useTheme(); + const {translate, toLocaleDigit} = useLocalize(); + const {isOffline} = useNetwork(); + const {canUseViolations} = usePermissions(iouType); + const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); + + // A flag and a toggler for showing the rest of the form fields + const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); + + const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); + const [invalidAttachmentPrompt, setInvalidAttachmentPrompt] = useState(translate('attachmentPicker.protectedPDFNotSupported')); + + // A flag for showing the tags field + // TODO: remove the !isTypeInvoice from this condition after BE supports tags for invoices: https://github.com/Expensify/App/issues/41281 + const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists) && !isTypeInvoice, [isPolicyExpenseChat, isTypeInvoice, policyTagLists]); + + const senderWorkspace = useMemo(() => { + const senderWorkspaceParticipant = selectedParticipants.find((participant) => participant.isSender); + return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${senderWorkspaceParticipant?.policyID}`]; + }, [allPolicies, selectedParticipants]); + + const canUpdateSenderWorkspace = useMemo(() => { + const isInvoiceRoomParticipant = selectedParticipants.some((participant) => participant.isInvoiceRoom); + + return PolicyUtils.canSendInvoice(allPolicies) && !!transaction?.isFromGlobalCreate && !isInvoiceRoomParticipant; + }, [allPolicies, selectedParticipants, transaction?.isFromGlobalCreate]); + + const isTypeSend = iouType === CONST.IOU.TYPE.PAY; + const taxRates = policy?.taxRates ?? null; + // In Send Money and Split Bill with Scan flow, we don't allow the Merchant or Date to be edited. For distance requests, don't show the merchant as there's already another "Distance" menu item + const shouldShowDate = (shouldShowSmartScanFields || isDistanceRequest) && !isTypeSend; + // Determines whether the tax fields can be modified. + // The tax fields can only be modified if the component is not in read-only mode + // and it is not a distance request. + const canModifyTaxFields = !isReadOnly && !isDistanceRequest; + // A flag for showing the billable field + const shouldShowBillable = policy?.disabledFields?.defaultBillable === false; + // Do not hide fields in case of paying someone + const shouldShowAllFields = !!isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || !!isEditingSplitBill; + // Calculate the formatted tax amount based on the transaction's tax amount and the IOU currency code + const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode); + // Get the tax rate title based on the policy and transaction + const taxRateTitle = TransactionUtils.getTaxName(policy, transaction); + // Determine if the merchant error should be displayed + const shouldDisplayMerchantError = isMerchantRequired && (shouldDisplayFieldError || formError === 'iou.error.invalidMerchant') && isMerchantEmpty; + // The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate") + const shouldShowReceiptEmptyState = iouType === CONST.IOU.TYPE.SUBMIT && PolicyUtils.isPaidGroupPolicy(policy); + const { + image: receiptImage, + thumbnail: receiptThumbnail, + isThumbnail, + fileExtension, + isLocalFile, + } = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); + const resolvedThumbnail = isLocalFile ? receiptThumbnail : tryResolveUrlFromApiRoot(receiptThumbnail ?? ''); + const resolvedReceiptImage = isLocalFile ? receiptImage : tryResolveUrlFromApiRoot(receiptImage ?? ''); + + // An intermediate structure that helps us classify the fields as "primary" and "supplementary". + // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. + const classifiedFields = [ + { + item: ( + { + if (isDistanceRequest) { + return; + } + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} + style={[styles.moneyRequestMenuItem, styles.mt2]} + titleStyle={styles.moneyRequestConfirmationAmount} + disabled={didConfirm} + brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + errorText={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? translate('common.error.enterAmount') : ''} + /> + ), + shouldShow: shouldShowSmartScanFields, + isSupplementary: false, + }, + { + item: ( + { + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID), + ); + }} + style={[styles.moneyRequestMenuItem]} + titleStyle={styles.flex1} + disabled={didConfirm} + interactive={!isReadOnly} + numberOfLinesTitle={2} + /> + ), + shouldShow: true, + isSupplementary: false, + }, + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm} + // todo: handle edit for transaction while moving from track expense + interactive={!isReadOnly && !isMovingTransactionFromTrackExpense} + /> + ), + shouldShow: isDistanceRequest && !canUseP2PDistanceRequests, + isSupplementary: false, + }, + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm} + interactive={!isReadOnly} + /> + ), + shouldShow: isDistanceRequest && canUseP2PDistanceRequests, + isSupplementary: false, + }, + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm} + interactive={!!rate && !isReadOnly && isPolicyExpenseChat} + /> + ), + shouldShow: isDistanceRequest && canUseP2PDistanceRequests, + isSupplementary: false, + }, + { + item: ( + { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} + disabled={didConfirm} + interactive={!isReadOnly} + brickRoadIndicator={shouldDisplayMerchantError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + errorText={shouldDisplayMerchantError ? translate('common.error.fieldRequired') : ''} + rightLabel={isMerchantRequired && !shouldDisplayMerchantError ? translate('common.required') : ''} + numberOfLinesTitle={2} + /> + ), + shouldShow: shouldShowMerchant, + isSupplementary: !isMerchantRequired, + }, + { + item: ( + { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID)); + }} + disabled={didConfirm} + interactive={!isReadOnly} + brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + errorText={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? translate('common.error.enterDate') : ''} + /> + ), + shouldShow: shouldShowDate, + isSupplementary: true, + }, + { + item: ( + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID)) + } + style={[styles.moneyRequestMenuItem]} + titleStyle={styles.flex1} + disabled={didConfirm} + interactive={!isReadOnly} + rightLabel={isCategoryRequired && canUseViolations ? translate('common.required') : ''} + /> + ), + shouldShow: shouldShowCategories, + isSupplementary: action === CONST.IOU.ACTION.CATEGORIZE ? false : !isCategoryRequired, + }, + ...policyTagLists.map(({name, required}, index) => { + const isTagRequired = required ?? false; + return { + item: ( + + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(action, iouType, index, transactionID, reportID, Navigation.getActiveRouteWithoutParams(), reportActionID), + ) + } + style={[styles.moneyRequestMenuItem]} + disabled={didConfirm} + interactive={!isReadOnly} + rightLabel={isTagRequired && canUseViolations ? translate('common.required') : ''} + /> + ), + shouldShow: shouldShowTags, + isSupplementary: !isTagRequired, + }; + }), + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm} + interactive={canModifyTaxFields} + /> + ), + shouldShow: shouldShowTax, + isSupplementary: true, + }, + { + item: ( + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm} + interactive={canModifyTaxFields} + /> + ), + shouldShow: shouldShowTax, + isSupplementary: true, + }, + { + item: ( + + onToggleBillable?.(isOn)} + isActive={iouIsBillable} + disabled={isReadOnly} + titleStyle={!iouIsBillable && {color: theme.textSupporting}} + wrapperStyle={styles.flex1} + /> + + ), + shouldShow: shouldShowBillable, + isSupplementary: true, + }, + ]; + + const primaryFields: JSX.Element[] = []; + const supplementaryFields: JSX.Element[] = []; + + classifiedFields.forEach((field) => { + if (field.shouldShow && !field.isSupplementary) { + primaryFields.push(field.item); + } else if (field.shouldShow && field.isSupplementary) { + supplementaryFields.push(field.item); + } + }); + + const receiptThumbnailContent = useMemo( + () => ( + + {isLocalFile && Str.isPDF(receiptFilename) ? ( + Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(reportID ?? '', transactionID ?? ''))} + accessibilityRole={CONST.ROLE.BUTTON} + accessibilityLabel={translate('accessibilityHints.viewAttachment')} + disabled={!shouldDisplayReceipt} + disabledStyle={styles.cursorDefault} + > + { + setIsAttachmentInvalid(true); + setInvalidAttachmentPrompt(translate('attachmentPicker.protectedPDFNotSupported')); + }} + onLoadError={() => { + setInvalidAttachmentPrompt(translate('attachmentPicker.errorWhileSelectingCorruptedAttachment')); + setIsAttachmentInvalid(true); + }} + /> + + ) : ( + Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(reportID ?? '', transactionID ?? ''))} + disabled={!shouldDisplayReceipt || isThumbnail} + accessibilityRole={CONST.ROLE.BUTTON} + accessibilityLabel={translate('accessibilityHints.viewAttachment')} + disabledStyle={styles.cursorDefault} + > + + + )} + + ), + [ + styles.moneyRequestImage, + styles.cursorDefault, + isLocalFile, + receiptFilename, + translate, + shouldDisplayReceipt, + resolvedReceiptImage, + isAttachmentInvalid, + isThumbnail, + resolvedThumbnail, + receiptThumbnail, + fileExtension, + isDistanceRequest, + reportID, + transactionID, + ], + ); + + const navigateBack = () => { + Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); + }; + + return ( + <> + {isTypeInvoice && ( + { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_SEND_FROM.getRoute(iouType, transaction?.transactionID ?? '-1', reportID, Navigation.getActiveRouteWithoutParams())); + }} + style={styles.moneyRequestMenuItem} + labelStyle={styles.mt2} + titleStyle={styles.flex1} + disabled={didConfirm} + /> + )} + {isDistanceRequest && ( + + + + )} + {!isDistanceRequest && + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + (receiptImage || receiptThumbnail + ? receiptThumbnailContent + : shouldShowReceiptEmptyState && ( + + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()), + ) + } + /> + ))} + {primaryFields} + {!shouldShowAllFields && ( + + )} + {shouldShowAllFields && supplementaryFields} + + + ); +} + +MoneyRequestConfirmationListFooter.displayName = 'MoneyRequestConfirmationListFooter'; + +export default memo( + MoneyRequestConfirmationListFooter, + (prevProps, nextProps) => + lodashIsEqual(prevProps.action, nextProps.action) && + prevProps.canUseP2PDistanceRequests === nextProps.canUseP2PDistanceRequests && + prevProps.currency === nextProps.currency && + prevProps.didConfirm === nextProps.didConfirm && + prevProps.distance === nextProps.distance && + prevProps.formattedAmount === nextProps.formattedAmount && + prevProps.formError === nextProps.formError && + prevProps.hasRoute === nextProps.hasRoute && + prevProps.iouCategory === nextProps.iouCategory && + prevProps.iouComment === nextProps.iouComment && + prevProps.iouCreated === nextProps.iouCreated && + prevProps.iouCurrencyCode === nextProps.iouCurrencyCode && + prevProps.iouIsBillable === nextProps.iouIsBillable && + prevProps.iouMerchant === nextProps.iouMerchant && + prevProps.iouType === nextProps.iouType && + prevProps.isCategoryRequired === nextProps.isCategoryRequired && + prevProps.isDistanceRequest === nextProps.isDistanceRequest && + prevProps.isEditingSplitBill === nextProps.isEditingSplitBill && + prevProps.isMerchantEmpty === nextProps.isMerchantEmpty && + prevProps.isMerchantRequired === nextProps.isMerchantRequired && + prevProps.isMovingTransactionFromTrackExpense === nextProps.isMovingTransactionFromTrackExpense && + prevProps.isPolicyExpenseChat === nextProps.isPolicyExpenseChat && + prevProps.isReadOnly === nextProps.isReadOnly && + prevProps.isTypeInvoice === nextProps.isTypeInvoice && + prevProps.onToggleBillable === nextProps.onToggleBillable && + lodashIsEqual(prevProps.policy, nextProps.policy) && + lodashIsEqual(prevProps.policyTagLists, nextProps.policyTagLists) && + prevProps.rate === nextProps.rate && + prevProps.receiptFilename === nextProps.receiptFilename && + prevProps.receiptPath === nextProps.receiptPath && + prevProps.reportActionID === nextProps.reportActionID && + prevProps.reportID === nextProps.reportID && + lodashIsEqual(prevProps.selectedParticipants, nextProps.selectedParticipants) && + prevProps.shouldDisplayFieldError === nextProps.shouldDisplayFieldError && + prevProps.shouldDisplayReceipt === nextProps.shouldDisplayReceipt && + prevProps.shouldShowCategories === nextProps.shouldShowCategories && + prevProps.shouldShowMerchant === nextProps.shouldShowMerchant && + prevProps.shouldShowSmartScanFields === nextProps.shouldShowSmartScanFields && + prevProps.shouldShowTax === nextProps.shouldShowTax && + lodashIsEqual(prevProps.transaction, nextProps.transaction) && + prevProps.transactionID === nextProps.transactionID && + prevProps.unit === nextProps.unit, +); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index b10a09e4f7e8..82b33a674b18 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -80,7 +80,6 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID; const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID ?? '-1']); const shouldShowMarkAsCashButton = isDraft && hasAllPendingRTERViolations; - const deleteTransaction = useCallback(() => { if (parentReportAction) { const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? '-1' : '-1'; @@ -245,6 +244,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow medium text={translate('iou.reviewDuplicates')} style={[styles.p0]} + onPress={() => { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(report.reportID)); + }} /> )} @@ -266,6 +268,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow medium text={translate('iou.reviewDuplicates')} style={[styles.w100, styles.pr0]} + onPress={() => { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(report.reportID)); + }} /> )} diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 70354c4a4676..28e1d81b30e4 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -13,6 +13,7 @@ import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {ReceiptError, ReceiptErrors} from '@src/types/onyx/Transaction'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import CustomStylesForChildrenProvider from './CustomStylesForChildrenProvider'; import MessagesRow from './MessagesRow'; /** @@ -134,7 +135,7 @@ function OfflineWithFeedback({ style={[needsOpacity ? styles.offlineFeedback.pending : {}, contentContainerStyle]} needsOffscreenAlphaCompositing={shouldRenderOffscreen ? needsOpacity && needsOffscreenAlphaCompositing : undefined} > - {children} + {children} )} {shouldShowErrorMessages && hasErrorMessages && ( diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx b/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx index e866f76e1237..da5ce1e43085 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx @@ -110,12 +110,18 @@ function GenericPressable( ref.current?.blur(); Accessibility.moveAccessibilityFocus(nextFocusRef); } - const onPressResult = onPress(event); - return onPressResult; + return onPress(event); }, [shouldUseHapticsOnPress, onPress, nextFocusRef, ref, isDisabled], ); + const voidOnPressHandler = useCallback( + (...args: Parameters) => { + onPressHandler(...args); + }, + [onPressHandler], + ); + const onKeyboardShortcutPressHandler = useCallback( (event?: GestureResponderEvent | KeyboardEvent) => { onPressHandler(event); @@ -159,8 +165,8 @@ function GenericPressable( aria-disabled={isDisabled} aria-keyshortcuts={keyboardShortcut && `${keyboardShortcut.modifiers.join('')}+${keyboardShortcut.shortcutKey}`} // ios-only form of inputs - onMagicTap={!isDisabled ? onPressHandler : undefined} - onAccessibilityTap={!isDisabled ? onPressHandler : undefined} + onMagicTap={!isDisabled ? voidOnPressHandler : undefined} + onAccessibilityTap={!isDisabled ? voidOnPressHandler : undefined} accessible={accessible} // eslint-disable-next-line react/jsx-props-no-spreading {...rest} diff --git a/src/components/PromotedActionsBar.tsx b/src/components/PromotedActionsBar.tsx index b98ab7fcbc32..b3bfe34df542 100644 --- a/src/components/PromotedActionsBar.tsx +++ b/src/components/PromotedActionsBar.tsx @@ -5,7 +5,12 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as HeaderUtils from '@libs/HeaderUtils'; import * as Localize from '@libs/Localize'; +import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActions from '@userActions/Report'; +import * as Session from '@userActions/Session'; +import CONST from '@src/CONST'; +import type {ReportAction} from '@src/types/onyx'; import type OnyxReport from '@src/types/onyx/Report'; import Button from './Button'; import type {ThreeDotsMenuItem} from './HeaderWithBackButton/types'; @@ -15,21 +20,34 @@ type PromotedAction = { key: string; } & ThreeDotsMenuItem; -type PromotedActionsType = Record<'pin' | 'share', (report: OnyxReport) => PromotedAction> & { +type BasePromotedActions = typeof CONST.PROMOTED_ACTIONS.PIN | typeof CONST.PROMOTED_ACTIONS.SHARE | typeof CONST.PROMOTED_ACTIONS.JOIN; + +type PromotedActionsType = Record PromotedAction> & { message: (params: {accountID?: number; login?: string}) => PromotedAction; +} & { + hold: (params: {isTextHold: boolean; reportAction: ReportAction | undefined}) => PromotedAction; }; const PromotedActions = { pin: (report) => ({ - key: 'pin', + key: CONST.PROMOTED_ACTIONS.PIN, ...HeaderUtils.getPinMenuItem(report), }), share: (report) => ({ - key: 'share', + key: CONST.PROMOTED_ACTIONS.SHARE, ...HeaderUtils.getShareMenuItem(report), }), + join: (report) => ({ + key: CONST.PROMOTED_ACTIONS.JOIN, + icon: Expensicons.ChatBubbles, + text: Localize.translateLocal('common.join'), + onSelected: Session.checkIfActionIsAllowed(() => { + Navigation.dismissModal(); + ReportActions.joinRoom(report); + }), + }), message: ({accountID, login}) => ({ - key: 'message', + key: CONST.PROMOTED_ACTIONS.MESSAGE, icon: Expensicons.CommentBubbles, text: Localize.translateLocal('common.message'), onSelected: () => { @@ -43,6 +61,15 @@ const PromotedActions = { } }, }), + hold: ({isTextHold, reportAction}) => ({ + key: CONST.PROMOTED_ACTIONS.HOLD, + icon: Expensicons.Stopwatch, + text: Localize.translateLocal(`iou.${isTextHold ? 'hold' : 'unhold'}`), + onSelected: () => { + Navigation.dismissModal(); + ReportUtils.changeMoneyRequestHoldStatus(reportAction); + }, + }), } satisfies PromotedActionsType; type PromotedActionsBarProps = { @@ -61,10 +88,6 @@ function PromotedActionsBar({promotedActions, containerStyle}: PromotedActionsBa return null; } - if (promotedActions.length === 0) { - return null; - } - return ( {promotedActions.map(({key, onSelected, ...props}) => ( diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index b8f02f83d1cd..15f9cee3705c 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -59,6 +59,9 @@ type MoneyRequestActionProps = MoneyRequestActionOnyxProps & { /** Styles to be assigned to Container */ style?: StyleProp; + + /** Whether context menu should be shown on press */ + shouldDisplayContextMenu?: boolean; }; function MoneyRequestAction({ @@ -75,11 +78,11 @@ function MoneyRequestAction({ isHovered = false, style, isWhisper = false, + shouldDisplayContextMenu = true, }: MoneyRequestActionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const isSplitBillAction = ReportActionsUtils.isSplitBillAction(action); const isTrackExpenseAction = ReportActionsUtils.isTrackExpenseAction(action); @@ -132,6 +135,7 @@ function MoneyRequestAction({ containerStyles={[styles.cursorPointer, isHovered ? styles.reportPreviewBoxHoverBorder : undefined, style]} isHovered={isHovered} isWhisper={isWhisper} + shouldDisplayContextMenu={shouldDisplayContextMenu} /> ); } diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 952e66cb1154..05df4acac5a6 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -1,8 +1,10 @@ +import {useRoute} from '@react-navigation/native'; import lodashSortBy from 'lodash/sortBy'; import truncate from 'lodash/truncate'; import React, {useMemo} from 'react'; import {View} from 'react-native'; import type {GestureResponderEvent} from 'react-native'; +import Button from '@components/Button'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {ReceiptScan} from '@components/Icon/Expensicons'; @@ -35,7 +37,10 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import variables from '@styles/variables'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Report from '@userActions/Report'; +import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -60,12 +65,14 @@ function MoneyRequestPreviewContent({ isHovered = false, isWhisper = false, transactionViolations, + shouldDisplayContextMenu = true, }: MoneyRequestPreviewProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const {windowWidth} = useWindowDimensions(); + const route = useRoute(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const sessionAccountID = session?.accountID; @@ -111,12 +118,23 @@ function MoneyRequestPreviewContent({ const isCardTransaction = TransactionUtils.isCardTransaction(transaction); const isSettled = ReportUtils.isSettled(iouReport?.reportID); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + const isReviewDuplicateTransactionPage = route.name === SCREENS.TRANSACTION_DUPLICATE.REVIEW; + const isFullySettled = isSettled && !isSettlementOrApprovalPartial; const isFullyApproved = ReportUtils.isReportApproved(iouReport) && !isSettlementOrApprovalPartial; const shouldShowRBR = hasNoticeTypeViolations || hasViolations || hasFieldErrors || (!isFullySettled && !isFullyApproved && isOnHold); const showCashOrCard = isCardTransaction ? translate('iou.card') : translate('iou.cash'); const shouldShowHoldMessage = !(isSettled && !isSettlementOrApprovalPartial) && isOnHold; + // Get transaction violations for given transaction id from onyx, find duplicated transactions violations and get duplicates + const duplicates = useMemo( + () => + transactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction?.transactionID}`]?.find( + (violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, + )?.data?.duplicates ?? [], + [transaction?.transactionID, transactionViolations], + ); + /* Show the merchant for IOUs and expenses only if: - the merchant is not empty, is custom, or is not related to scanning smartscan; @@ -145,6 +163,9 @@ function MoneyRequestPreviewContent({ }; const showContextMenu = (event: GestureResponderEvent) => { + if (!shouldDisplayContextMenu) { + return; + } showContextMenuForReport(event, contextMenuAnchor, reportID, action, checkIfContextMenuActive); }; @@ -382,6 +403,17 @@ function MoneyRequestPreviewContent({ ]} > {childContainer} + {isReviewDuplicateTransactionPage && ( +