diff --git a/.github/actions/javascript/authorChecklist/categories/newComponentCategory.ts b/.github/actions/javascript/authorChecklist/categories/newComponentCategory.ts index 71c413e5003b..042d91a5b956 100644 --- a/.github/actions/javascript/authorChecklist/categories/newComponentCategory.ts +++ b/.github/actions/javascript/authorChecklist/categories/newComponentCategory.ts @@ -1,4 +1,4 @@ -import github from '@actions/github'; +import * as github from '@actions/github'; import {parse} from '@babel/parser'; import traverse from '@babel/traverse'; import CONST from '@github/libs/CONST'; diff --git a/.github/actions/javascript/authorChecklist/index.js b/.github/actions/javascript/authorChecklist/index.js index 7c2bcf8618b8..846d436c5a47 100644 --- a/.github/actions/javascript/authorChecklist/index.js +++ b/.github/actions/javascript/authorChecklist/index.js @@ -16738,12 +16738,35 @@ exports["default"] = generateDynamicChecksAndCheckForCompletion; "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 })); exports.detectReactComponent = void 0; -const github_1 = __importDefault(__nccwpck_require__(5438)); +const github = __importStar(__nccwpck_require__(5438)); const parser_1 = __nccwpck_require__(5026); const traverse_1 = __importDefault(__nccwpck_require__(1380)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); @@ -16810,7 +16833,7 @@ async function detectReactComponentInFile(filename) { owner: CONST_1.default.GITHUB_OWNER, repo: CONST_1.default.APP_REPO, path: filename, - ref: github_1.default.context.payload.pull_request?.head.ref, + ref: github.context.payload.pull_request?.head.ref, }; try { const { data } = await GithubUtils_1.default.octokit.repos.getContent(params); @@ -16846,6 +16869,7 @@ exports["default"] = newComponentCategory; "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', @@ -16860,6 +16884,10 @@ const CONST = { 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`, }; @@ -16869,7 +16897,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -16900,7 +16928,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -16909,20 +16936,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -17047,7 +17062,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -17065,7 +17080,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -17083,7 +17098,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -17262,7 +17277,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -17274,7 +17289,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -17286,7 +17301,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -17314,9 +17329,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/awaitStagingDeploys/awaitStagingDeploys.ts b/.github/actions/javascript/awaitStagingDeploys/awaitStagingDeploys.ts index 1cf58dc03f8f..7de78e257dc4 100644 --- a/.github/actions/javascript/awaitStagingDeploys/awaitStagingDeploys.ts +++ b/.github/actions/javascript/awaitStagingDeploys/awaitStagingDeploys.ts @@ -2,7 +2,7 @@ import lodashThrottle from 'lodash/throttle'; import {getStringInput} from '@github/libs/ActionUtils'; import CONST from '@github/libs/CONST'; -import GitHubUtils, {POLL_RATE} from '@github/libs/GithubUtils'; +import GitHubUtils from '@github/libs/GithubUtils'; import {promiseDoWhile} from '@github/libs/promiseWhile'; type CurrentStagingDeploys = Awaited>['data']['workflow_runs']; @@ -41,20 +41,23 @@ function run() { }), ]) .then((responses) => { + console.info('[awaitStagingDeploys] listWorkflowRuns responses', responses); const workflowRuns = responses[0].data.workflow_runs; if (!tag && typeof responses[1] === 'object') { workflowRuns.push(...responses[1].data.workflow_runs); } + console.info('[awaitStagingDeploys] workflowRuns', workflowRuns); return workflowRuns; }) .then((workflowRuns) => (currentStagingDeploys = workflowRuns.filter((workflowRun) => workflowRun.status !== 'completed'))) - .then(() => + .then(() => { + console.info('[awaitStagingDeploys] currentStagingDeploys', currentStagingDeploys); console.log( !currentStagingDeploys.length ? 'No current staging deploys found' : `Found ${currentStagingDeploys.length} staging deploy${currentStagingDeploys.length > 1 ? 's' : ''} still running...`, - ), - ); + ); + }); console.info('[awaitStagingDeploys] run() throttleFunc', throttleFunc); return promiseDoWhile( @@ -63,7 +66,7 @@ function run() { throttleFunc, // Poll every 60 seconds instead of every 10 seconds - POLL_RATE * 6, + CONST.POLL_RATE * 6, ), ); } diff --git a/.github/actions/javascript/awaitStagingDeploys/index.js b/.github/actions/javascript/awaitStagingDeploys/index.js index 5b3ea89f6024..4b76cc962968 100644 --- a/.github/actions/javascript/awaitStagingDeploys/index.js +++ b/.github/actions/javascript/awaitStagingDeploys/index.js @@ -12120,29 +12120,6 @@ function wrappy (fn, cb) { "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 }; }; @@ -12151,7 +12128,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); const throttle_1 = __importDefault(__nccwpck_require__(2891)); const ActionUtils_1 = __nccwpck_require__(6981); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GithubUtils_1 = __importStar(__nccwpck_require__(9296)); +const GithubUtils_1 = __importDefault(__nccwpck_require__(9296)); const promiseWhile_1 = __nccwpck_require__(9438); function run() { console.info('[awaitStagingDeploys] run()'); @@ -12181,20 +12158,25 @@ function run() { }), ]) .then((responses) => { + console.info('[awaitStagingDeploys] listWorkflowRuns responses', responses); const workflowRuns = responses[0].data.workflow_runs; if (!tag && typeof responses[1] === 'object') { workflowRuns.push(...responses[1].data.workflow_runs); } + console.info('[awaitStagingDeploys] workflowRuns', workflowRuns); return workflowRuns; }) .then((workflowRuns) => (currentStagingDeploys = workflowRuns.filter((workflowRun) => workflowRun.status !== 'completed'))) - .then(() => console.log(!currentStagingDeploys.length - ? 'No current staging deploys found' - : `Found ${currentStagingDeploys.length} staging deploy${currentStagingDeploys.length > 1 ? 's' : ''} still running...`)); + .then(() => { + console.info('[awaitStagingDeploys] currentStagingDeploys', currentStagingDeploys); + console.log(!currentStagingDeploys.length + ? 'No current staging deploys found' + : `Found ${currentStagingDeploys.length} staging deploy${currentStagingDeploys.length > 1 ? 's' : ''} still running...`); + }); console.info('[awaitStagingDeploys] run() throttleFunc', throttleFunc); return (0, promiseWhile_1.promiseDoWhile)(() => !!currentStagingDeploys.length, (0, throttle_1.default)(throttleFunc, // Poll every 60 seconds instead of every 10 seconds - GithubUtils_1.POLL_RATE * 6)); + CONST_1.default.POLL_RATE * 6)); } if (require.main === require.cache[eval('__filename')]) { run(); @@ -12272,6 +12254,7 @@ exports.getStringInput = getStringInput; "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', @@ -12286,6 +12269,10 @@ const CONST = { 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`, }; @@ -12295,7 +12282,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -12326,7 +12313,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -12335,20 +12321,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -12473,7 +12447,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -12491,7 +12465,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -12509,7 +12483,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -12688,7 +12662,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -12700,7 +12674,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -12712,7 +12686,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -12740,9 +12714,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/checkDeployBlockers/index.js b/.github/actions/javascript/checkDeployBlockers/index.js index da9f8f0f9e32..842deb1cbb5d 100644 --- a/.github/actions/javascript/checkDeployBlockers/index.js +++ b/.github/actions/javascript/checkDeployBlockers/index.js @@ -11527,6 +11527,7 @@ exports["default"] = run; "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', @@ -11541,6 +11542,10 @@ const CONST = { 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`, }; @@ -11550,7 +11555,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11581,7 +11586,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11590,20 +11594,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11728,7 +11720,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11746,7 +11738,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11764,7 +11756,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11943,7 +11935,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -11955,7 +11947,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -11967,7 +11959,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -11995,9 +11987,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js index 228ae24f4f62..127fb1fe3dca 100644 --- a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js @@ -14339,6 +14339,7 @@ exports["default"] = run; "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', @@ -14353,6 +14354,10 @@ const CONST = { 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`, }; @@ -14520,7 +14525,7 @@ exports["default"] = { /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -14551,7 +14556,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -14560,20 +14564,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -14698,7 +14690,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -14716,7 +14708,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -14734,7 +14726,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -14913,7 +14905,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -14925,7 +14917,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -14937,7 +14929,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -14965,9 +14957,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/getArtifactInfo/index.js b/.github/actions/javascript/getArtifactInfo/index.js index 58c0757cc4d0..77f61f491fec 100644 --- a/.github/actions/javascript/getArtifactInfo/index.js +++ b/.github/actions/javascript/getArtifactInfo/index.js @@ -11488,6 +11488,7 @@ exports["default"] = run; "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', @@ -11502,6 +11503,10 @@ const CONST = { 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`, }; @@ -11511,7 +11516,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11542,7 +11547,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11551,20 +11555,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11689,7 +11681,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11707,7 +11699,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11725,7 +11717,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11904,7 +11896,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -11916,7 +11908,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -11928,7 +11920,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -11956,9 +11948,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts b/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts index 2ef789df76da..ecf242f00cc2 100644 --- a/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts +++ b/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts @@ -1,5 +1,5 @@ import * as core from '@actions/core'; -import github from '@actions/github'; +import * as github from '@actions/github'; import {getJSONInput} from '@github/libs/ActionUtils'; import GithubUtils from '@github/libs/GithubUtils'; import GitUtils from '@github/libs/GitUtils'; diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js index 3b845b6daa37..8f9f9deea896 100644 --- a/.github/actions/javascript/getDeployPullRequestList/index.js +++ b/.github/actions/javascript/getDeployPullRequestList/index.js @@ -11498,7 +11498,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); -const github_1 = __importDefault(__nccwpck_require__(5438)); +const github = __importStar(__nccwpck_require__(5438)); const ActionUtils_1 = __nccwpck_require__(6981); const GithubUtils_1 = __importDefault(__nccwpck_require__(9296)); const GitUtils_1 = __importDefault(__nccwpck_require__(1547)); @@ -11509,8 +11509,8 @@ async function run() { const deployEnv = isProductionDeploy ? 'production' : 'staging'; console.log(`Looking for PRs deployed to ${deployEnv} in ${inputTag}...`); const completedDeploys = (await GithubUtils_1.default.octokit.actions.listWorkflowRuns({ - owner: github_1.default.context.repo.owner, - repo: github_1.default.context.repo.repo, + owner: github.context.repo.owner, + repo: github.context.repo.repo, // eslint-disable-next-line @typescript-eslint/naming-convention workflow_id: 'platformDeploy.yml', status: 'completed', @@ -11603,6 +11603,7 @@ exports.getStringInput = getStringInput; "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', @@ -11617,6 +11618,10 @@ const CONST = { 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`, }; @@ -11784,7 +11789,7 @@ exports["default"] = { /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11815,7 +11820,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11824,20 +11828,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11962,7 +11954,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11980,7 +11972,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11998,7 +11990,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -12177,7 +12169,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -12189,7 +12181,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -12201,7 +12193,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -12229,9 +12221,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/getPullRequestDetails/index.js b/.github/actions/javascript/getPullRequestDetails/index.js index 23bcb28119f1..14814367e3cd 100644 --- a/.github/actions/javascript/getPullRequestDetails/index.js +++ b/.github/actions/javascript/getPullRequestDetails/index.js @@ -11590,6 +11590,7 @@ exports.getStringInput = getStringInput; "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', @@ -11604,6 +11605,10 @@ const CONST = { 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`, }; @@ -11613,7 +11618,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11644,7 +11649,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11653,20 +11657,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11791,7 +11783,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11809,7 +11801,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11827,7 +11819,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -12006,7 +11998,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -12018,7 +12010,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -12030,7 +12022,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -12058,9 +12050,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/getReleaseBody/index.js b/.github/actions/javascript/getReleaseBody/index.js index 99f686c513c5..6c746e26a4a4 100644 --- a/.github/actions/javascript/getReleaseBody/index.js +++ b/.github/actions/javascript/getReleaseBody/index.js @@ -11534,6 +11534,7 @@ exports.getStringInput = getStringInput; "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', @@ -11548,6 +11549,10 @@ const CONST = { 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`, }; @@ -11557,7 +11562,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11588,7 +11593,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11597,20 +11601,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11735,7 +11727,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11753,7 +11745,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11771,7 +11763,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11950,7 +11942,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -11962,7 +11954,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -11974,7 +11966,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -12002,9 +11994,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/isStagingDeployLocked/index.js b/.github/actions/javascript/isStagingDeployLocked/index.js index e171e90646d1..f71b89dc051c 100644 --- a/.github/actions/javascript/isStagingDeployLocked/index.js +++ b/.github/actions/javascript/isStagingDeployLocked/index.js @@ -11488,6 +11488,7 @@ exports["default"] = run; "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', @@ -11502,6 +11503,10 @@ const CONST = { 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`, }; @@ -11511,7 +11516,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11542,7 +11547,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11551,20 +11555,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11689,7 +11681,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11707,7 +11699,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11725,7 +11717,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11904,7 +11896,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -11916,7 +11908,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -11928,7 +11920,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -11956,9 +11948,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/index.js b/.github/actions/javascript/markPullRequestsAsDeployed/index.js index 5dbc6767da01..804d3ea610f3 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/index.js +++ b/.github/actions/javascript/markPullRequestsAsDeployed/index.js @@ -11685,6 +11685,7 @@ exports.getStringInput = getStringInput; "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', @@ -11699,6 +11700,10 @@ const CONST = { 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`, }; @@ -11708,7 +11713,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11739,7 +11744,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11748,20 +11752,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11886,7 +11878,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11904,7 +11896,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11922,7 +11914,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -12101,7 +12093,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -12113,7 +12105,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -12125,7 +12117,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -12153,9 +12145,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js index b15e5950396a..0b8eb29f1750 100644 --- a/.github/actions/javascript/postTestBuildComment/index.js +++ b/.github/actions/javascript/postTestBuildComment/index.js @@ -11587,6 +11587,7 @@ exports["default"] = run; "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', @@ -11601,6 +11602,10 @@ const CONST = { 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`, }; @@ -11610,7 +11615,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11641,7 +11646,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11650,20 +11654,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11788,7 +11780,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11806,7 +11798,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11824,7 +11816,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -12003,7 +11995,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -12015,7 +12007,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -12027,7 +12019,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -12055,9 +12047,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/reopenIssueWithComment/index.js b/.github/actions/javascript/reopenIssueWithComment/index.js index de5c3adcccf1..d4341ce37dc4 100644 --- a/.github/actions/javascript/reopenIssueWithComment/index.js +++ b/.github/actions/javascript/reopenIssueWithComment/index.js @@ -11498,6 +11498,7 @@ reopenIssueWithComment() "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', @@ -11512,6 +11513,10 @@ const CONST = { 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`, }; @@ -11521,7 +11526,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11552,7 +11557,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11561,20 +11565,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11699,7 +11691,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11717,7 +11709,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11735,7 +11727,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11914,7 +11906,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -11926,7 +11918,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -11938,7 +11930,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -11966,9 +11958,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/reviewerChecklist/index.js b/.github/actions/javascript/reviewerChecklist/index.js index d1b9cf65a7bb..cc1c0b5a581b 100644 --- a/.github/actions/javascript/reviewerChecklist/index.js +++ b/.github/actions/javascript/reviewerChecklist/index.js @@ -11590,6 +11590,7 @@ getNumberOfItemsFromReviewerChecklist() "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', @@ -11604,6 +11605,10 @@ const CONST = { 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`, }; @@ -11613,7 +11618,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11644,7 +11649,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11653,20 +11657,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11791,7 +11783,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11809,7 +11801,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11827,7 +11819,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -12006,7 +11998,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -12018,7 +12010,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -12030,7 +12022,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -12058,9 +12050,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/actions/javascript/verifySignedCommits/index.js b/.github/actions/javascript/verifySignedCommits/index.js index 1813f2fe74e2..aea35331b1d0 100644 --- a/.github/actions/javascript/verifySignedCommits/index.js +++ b/.github/actions/javascript/verifySignedCommits/index.js @@ -11530,6 +11530,7 @@ GithubUtils_1.default.octokit.pulls "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', @@ -11544,6 +11545,10 @@ const CONST = { 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`, }; @@ -11553,7 +11558,7 @@ exports["default"] = CONST; /***/ }), /***/ 9296: -/***/ (function(module, exports, __nccwpck_require__) { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11584,7 +11589,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.POLL_RATE = exports.ISSUE_OR_PULL_REQUEST_REGEX = void 0; /* eslint-disable @typescript-eslint/naming-convention, import/no-import-module-exports */ const core = __importStar(__nccwpck_require__(2186)); const utils_1 = __nccwpck_require__(3030); @@ -11593,20 +11597,8 @@ const plugin_throttling_1 = __nccwpck_require__(9968); const EmptyObject_1 = __nccwpck_require__(8227); const arrayDifference_1 = __importDefault(__nccwpck_require__(7034)); const CONST_1 = __importDefault(__nccwpck_require__(9873)); -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); -exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; -exports.POLL_RATE = POLL_RATE; class GithubUtils { static internalOctokit; - static POLL_RATE; /** * Initialize internal octokit * @@ -11731,7 +11723,7 @@ class GithubUtils { return []; } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -11749,7 +11741,7 @@ class GithubUtils { return []; } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11767,7 +11759,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -11946,7 +11938,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL) { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -11958,7 +11950,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL) { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -11970,7 +11962,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL) { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -11998,9 +11990,6 @@ class GithubUtils { } } exports["default"] = GithubUtils; -// This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. -// Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; /***/ }), diff --git a/.github/libs/CONST.ts b/.github/libs/CONST.ts index 9d5f559ccd9c..8d9aae9bff83 100644 --- a/.github/libs/CONST.ts +++ b/.github/libs/CONST.ts @@ -1,3 +1,5 @@ +const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); + const GIT_CONST = { GITHUB_OWNER: 'Expensify', APP_REPO: 'App', @@ -13,6 +15,10 @@ const CONST = { 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`, } as const; diff --git a/.github/libs/GithubUtils.ts b/.github/libs/GithubUtils.ts index 00427e7e49e7..f445fc368559 100644 --- a/.github/libs/GithubUtils.ts +++ b/.github/libs/GithubUtils.ts @@ -14,17 +14,6 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import arrayDifference from '@src/utils/arrayDifference'; import CONST from './CONST'; -const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); -const PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/pull/([0-9]+).*`); -const ISSUE_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/issues/([0-9]+).*`); -const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/.*/.*/(?:pull|issues)/([0-9]+).*`); - -/** - * The standard rate in ms at which we'll poll the GitHub API to check for status changes. - * It's 10 seconds :) - */ -const POLL_RATE = 10000; - type OctokitOptions = {method: string; url: string; request: {retryCount: number}}; type ListForRepoResult = RestEndpointMethodTypes['issues']['listForRepo']['response']; @@ -75,8 +64,6 @@ type InternalOctokit = OctokitCore & Api & {paginate: PaginateInterface}; class GithubUtils { static internalOctokit: InternalOctokit | undefined; - static POLL_RATE: number; - /** * Initialize internal octokit * @@ -217,7 +204,7 @@ class GithubUtils { } PRListSection = PRListSection[1]; - const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isVerified: match[1] === 'x', @@ -238,7 +225,7 @@ class GithubUtils { } deployBlockerSection = deployBlockerSection[1]; - const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2], number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -258,7 +245,7 @@ class GithubUtils { return []; } internalQASection = internalQASection[1]; - const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ + const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({ url: match[2].split('-')[0].trim(), number: Number.parseInt(match[3], 10), isResolved: match[1] === 'x', @@ -487,7 +474,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Pull Request. */ static getPullRequestNumberFromURL(URL: string): number { - const matches = URL.match(PULL_REQUEST_REGEX); + const matches = URL.match(CONST.PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Pull Request!`); } @@ -500,7 +487,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue. */ static getIssueNumberFromURL(URL: string): number { - const matches = URL.match(ISSUE_REGEX); + const matches = URL.match(CONST.ISSUE_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a Github Issue!`); } @@ -513,7 +500,7 @@ class GithubUtils { * @throws {Error} If the URL is not a valid Github Issue or Pull Request. */ static getIssueOrPullRequestNumberFromURL(URL: string): number { - const matches = URL.match(ISSUE_OR_PULL_REQUEST_REGEX); + const matches = URL.match(CONST.ISSUE_OR_PULL_REQUEST_REGEX); if (!Array.isArray(matches) || matches.length !== 2) { throw new Error(`Provided URL ${URL} is not a valid Github Issue or Pull Request!`); } @@ -546,7 +533,5 @@ class GithubUtils { export default GithubUtils; // This is a temporary solution to allow the use of the GithubUtils class in both TypeScript and JavaScript. // Once all the files that import GithubUtils are migrated to TypeScript, this can be removed. -module.exports = GithubUtils; -export {ISSUE_OR_PULL_REQUEST_REGEX, POLL_RATE}; export type {ListForRepoMethod, InternalOctokit, CreateCommentResponse, StagingDeployCashData}; diff --git a/Gemfile.lock b/Gemfile.lock index beb2c1762936..e1b4fc2ec7e4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,11 +3,12 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (7.0.8) + activesupport (6.1.7.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) + zeitwerk (~> 2.3) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) @@ -80,7 +81,8 @@ GEM declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20240107) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) escape (0.0.4) @@ -187,11 +189,11 @@ GEM google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.3.1) - google-cloud-storage (1.47.0) + google-cloud-storage (1.37.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.31.0) + google-apis-storage_v1 (~> 0.1) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) @@ -260,6 +262,9 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.9.1) unicode-display_width (2.5.0) word_wrap (1.0.0) xcodeproj (1.23.0) @@ -273,6 +278,7 @@ GEM rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) + zeitwerk (2.6.13) PLATFORMS arm64-darwin-21 @@ -292,4 +298,4 @@ RUBY VERSION ruby 2.6.10p210 BUNDLED WITH - 2.4.19 + 2.3.22 diff --git a/android/app/build.gradle b/android/app/build.gradle index 889a9ec9e395..b1538d22de8f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001046000 - versionName "1.4.60-0" + versionCode 1001046100 + versionName "1.4.61-0" } flavorDimensions "default" diff --git a/assets/images/avatars/fallback-avatar.svg b/assets/images/avatars/fallback-avatar.svg index b4584d910190..69293d72aed9 100644 --- a/assets/images/avatars/fallback-avatar.svg +++ b/assets/images/avatars/fallback-avatar.svg @@ -1 +1,10 @@ - \ No newline at end of file + + + + + + + diff --git a/assets/images/simple-illustrations/simple-illustration__abacus.svg b/assets/images/simple-illustrations/simple-illustration__abacus.svg new file mode 100644 index 000000000000..df94ab653982 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__abacus.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/simple-illustrations/simple-illustration__binoculars.svg b/assets/images/simple-illustrations/simple-illustration__binoculars.svg new file mode 100644 index 000000000000..381be8988873 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__binoculars.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/simple-illustrations/simple-illustration__company-card.svg b/assets/images/simple-illustrations/simple-illustration__company-card.svg new file mode 100644 index 000000000000..4121bbeeb205 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__company-card.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/simple-illustrations/simple-illustration__piggybank.svg b/assets/images/simple-illustrations/simple-illustration__piggybank.svg new file mode 100644 index 000000000000..a9cf2b02c5dc --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__piggybank.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/simple-illustrations/simple-illustration__receiptupload.svg b/assets/images/simple-illustrations/simple-illustration__receiptupload.svg new file mode 100644 index 000000000000..b8fe5101715f --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__receiptupload.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/assets/images/simple-illustrations/simple-illustration__splitbill.svg b/assets/images/simple-illustrations/simple-illustration__splitbill.svg new file mode 100644 index 000000000000..dfed7535ee90 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__splitbill.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/simple-illustrations/simple-illustration__teachers-unite.svg b/assets/images/simple-illustrations/simple-illustration__teachers-unite.svg new file mode 100644 index 000000000000..b4edd9513722 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__teachers-unite.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index 3d0d16b00587..20de9415bcd6 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -17,109 +17,114 @@ platforms: - href: getting-started title: Getting Started icon: /assets/images/accounting.svg - description: From setting up your account to ensuring you get the most out of Expensify’s suite of features, click here to get started on streamlining your expense management journey. + description: Set up your account to optimize Expensify's features. - - href: settings - title: Settings - icon: /assets/images/gears.svg - description: Discover how to personalize your profile, add secondary logins, and grant delegated access to employees with our comprehensive guide on Account Settings. - - - href: bank-accounts-and-credit-cards - title: Bank Accounts & Credit Cards - icon: /assets/images/bank-card.svg - description: Find out how to connect Expensify to your financial institutions, track credit card transactions, and best practices for reconciling company cards. + - href: workspaces + title: Workspaces + icon: /assets/images/shield.svg + description: Configure rules, settings, and limits for your company’s spending. - - href: expensify-billing - title: Expensify Billing - icon: /assets/images/subscription-annual.svg - description: Review Expensify's subscription options, plan types, and payment methods. + - href: expenses + title: Expenses + icon: /assets/images/money-into-wallet.svg + description: Learn more about expense tracking and submission. - href: reports title: Reports icon: /assets/images/money-receipt.svg description: Set approval workflows and use Expensify’s automated report features. + - href: domains + title: Domains + icon: /assets/images/domains.svg + description: Claim and verify your company’s domain to access additional management and security features. + + - href: bank-accounts-and-payments + title: Bank Accounts & Payments + icon: /assets/images/send-money.svg + description: Send direct reimbursements, pay invoices, and receive payment. + + - href: connect-credit-cards + title: Connect Credit Cards + icon: /assets/images/bank-card.svg + description: Track credit card transactions and reconcile company cards. + - href: expensify-card title: Expensify Card icon: /assets/images/hand-card.svg - description: Explore how the Expensify Card combines convenience and security to enhance everyday business transactions. Discover how to apply for, oversee, and maximize your card perks here. + description: Explore the perks and benefits of the Expensify Card. + + - href: copilots-and-delegates + title: Copilots & Delegates + icon: /assets/images/envelope-receipt.svg + description: Assign Copilots and delegate report approvals. - href: expensify-partner-program title: Expensify Partner Program icon: /assets/images/handshake.svg - description: Discover how to get the most out of Expensify as an ExpensifyApproved! accountant partner. Learn how to set up your clients, receive CPE credits, and take advantage of your partner discount. - - - href: expenses - title: Expenses - icon: /assets/images/money-into-wallet.svg - description: Learn more about expense tracking and submission. - - - href: insights-and-custom-reporting - title: Insights & Custom Reporting - icon: /assets/images/monitor.svg - description: From exporting reports to creating custom templates, here is where you can learn more about Expensify's versatile export options. + description: Discover the benefits of becoming an Expensify Partner. - href: integrations title: Integrations icon: /assets/images/workflow.svg - description: Enhance Expensify’s capabilities by integrating it with your accounting or HR software. Here is where you can learn more about creating a synchronized financial management ecosystem. + description: Integrate with accounting or HR software to streamline expense approvals. - - href: copilots-and-delegates - title: Copilots & Delegates - icon: /assets/images/envelope-receipt.svg - description: Assign Copilots and delegate report approvals. - - - href: send-payments - title: Send Payments - icon: /assets/images/send-money.svg - description: Uncover step-by-step guidance on sending direct reimbursements to employees, paying an invoice to a vendor, and utilizing third-party payment options. - - - href: workspaces - title: Workspaces - icon: /assets/images/shield.svg - description: Configure rules, settings, and limits for your company’s spending. + - href: spending-insights + title: Spending Insights + icon: /assets/images/monitor.svg + description: Create custom export templates to understand spending insights. - - href: domains - title: Domains - icon: /assets/images/domains.svg - description: Claim and verify your company’s domain to access additional management and security features. - + - href: settings + title: Settings + icon: /assets/images/gears.svg + description: Manage profile settings and notifications. + + - href: expensify-billing + title: Expensify Billing + icon: /assets/images/subscription-annual.svg + description: Review Expensify's subscription options, plan types, and payment methods. + + - href: new-expensify title: New Expensify hub-title: New Expensify - Help & Resources - hub-description: Questions? Find the answers by clicking a Category or using the search bar located in the left-hand menu. + hub-description: Questions? Find the answers by clicking a Category or using the search bar. url: new.expensify.com description: "Your account settings look like this:" image: /assets/images/settings-new-dot.svg hubs: + - href: getting-started + title: Getting Started + icon: /assets/images/accounting.svg + description: Set up your account to optimize Expensify's features. + - href: chat title: Chat icon: /assets/images/chat-bubble.svg - description: Enhance your financial experience using Expensify's chat feature, offering quick and secure communication for personalized support and payment transfers. + description: Use Expensify's chat feature to split bills, chat with employees, and manage payments. - - href: account-settings - title: Account Settings - icon: /assets/images/gears.svg - description: Discover how to personalize your profile, add secondary logins, and grant delegated access to employees with our comprehensive guide on Account Settings. + - href: workspaces + title: Workspaces + icon: /assets/images/shield.svg + description: Configure rules, settings, and limits for your company’s spending. + + - href: expenses + title: Expenses + icon: /assets/images/money-into-wallet.svg + description: Learn more about expense tracking and submission. - - href: bank-accounts - title: Bank Accounts + - href: bank-accounts-and-payments + title: Bank Accounts & Payments icon: /assets/images/bank-card.svg - description: Find out how to connect Expensify to your financial institutions, track credit card transactions, and best practices for reconciling company cards. - - - href: billing-and-plan-types - title: Billing & Plan Types - icon: /assets/images/subscription-annual.svg - description: Here is where you can review Expensify's billing and subscription options, plan types, and payment methods. + description: Send direct reimbursements, pay invoices, and receive payment. - href: expensify-card title: Expensify Card icon: /assets/images/hand-card.svg - description: Explore how the Expensify Card combines convenience and security to enhance everyday business transactions. Discover how to apply for, oversee, and maximize your card perks here. - - - href: payments - title: Payments - icon: /assets/images/money-into-wallet.svg - description: Whether you submit an expense report or an invoice, find out here how to ensure a smooth and timely payback process every time. + description: Explore the perks and benefits of the Expensify Card. + - href: settings + title: Settings + icon: /assets/images/gears.svg + description: Manage profile settings and notifications. diff --git a/docs/articles/expensify-classic/send-payments/Pay-Bills.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Pay-Bills.md similarity index 100% rename from docs/articles/expensify-classic/send-payments/Pay-Bills.md rename to docs/articles/expensify-classic/bank-accounts-and-payments/Pay-Bills.md diff --git a/docs/articles/expensify-classic/send-payments/Reimbursing-Reports.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports.md similarity index 100% rename from docs/articles/expensify-classic/send-payments/Reimbursing-Reports.md rename to docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports.md diff --git a/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Third-Party-Payments.md similarity index 100% rename from docs/articles/expensify-classic/send-payments/Third-Party-Payments.md rename to docs/articles/expensify-classic/bank-accounts-and-payments/Third-Party-Payments.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursements.md b/docs/articles/expensify-classic/connect-credit-cards/Global-Reimbursements.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursements.md rename to docs/articles/expensify-classic/connect-credit-cards/Global-Reimbursements.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md b/docs/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md rename to docs/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD.md b/docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD.md rename to docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md rename to docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md rename to docs/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Commercial-Card-Feeds.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds.md rename to docs/articles/expensify-classic/connect-credit-cards/company-cards/Commercial-Card-Feeds.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Company-Card-Settings.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Company-Card-Settings.md rename to docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-ANZ.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Connect-ANZ.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-ANZ.md rename to docs/articles/expensify-classic/connect-credit-cards/company-cards/Connect-ANZ.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Direct-Bank-Connections.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md rename to docs/articles/expensify-classic/connect-credit-cards/company-cards/Direct-Bank-Connections.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Reconciliation.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation.md rename to docs/articles/expensify-classic/connect-credit-cards/company-cards/Reconciliation.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md similarity index 93% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md rename to docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md index bf4b21440b3c..814bf8fc559b 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md +++ b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md @@ -8,6 +8,12 @@ Whether you're encountering issues related to company cards, require assistance ## How to add company cards to Expensify You can add company credit cards under the Domain settings in your Expensify account by navigating to *Settings* > *Domain* > _Domain Name_ > *Company Cards* and clicking *Import Card/Bank* and following the prompts. +## Known issues importing transactions +The first step should always be to "Update" your card, either from Settings > Your Account > Credit Card Import or Settings > Domain > [Domain Name] > Company Cards for centrally managed cards. If a "Fix" or "Fix card" option appears, follow the steps to fix the connection. If this fails to import your missing transactions, there is a known issue whereby some transactions will not import for certain API-based company card connections. So far this has been reported on American Express, Chase and Wells Fargo. This can be temporarily resolved by creating the expenses manually instead: + +- [Manually add the expenses](https://help.expensify.com/articles/expensify-classic/expenses/expenses/Add-an-expense) +- [Upload the expenses via CSV](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import) + # Errors connecting company cards ## Error: Too many attempts diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md b/docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md rename to docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-USD.md b/docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-USD.md similarity index 100% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-USD.md rename to docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-USD.md diff --git a/docs/articles/expensify-classic/copilots-and-delegates/User-Roles.md b/docs/articles/expensify-classic/copilots-and-delegates/User-Roles.md deleted file mode 100644 index 65238457f1a9..000000000000 --- a/docs/articles/expensify-classic/copilots-and-delegates/User-Roles.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: User Roles -description: Each member has a role that defines what they can see and do in the workspace. ---- - -# Overview - -This guide is for those who are part of a **Group Workspace**. - -Each member has a role that defines what they can see and do in the workspace. Most members will have the role of "Employee." - -# How to manage user roles - -To find and edit the roles of group workspace members, go to **Settings > Workspaces > Group > _[Workspace Name]_ > Members > Workspace Members** - -Here you'll see the list of members in your group workspace. To change their roles, click **Settings** next to the member’s name and choose the role that the member needs. - -Next, let’s go over the various user roles that are available on a group workspace. - -### The Employee Role - -- **What can they do:** Employees can only see their own expense reports or reports that have been submitted to or shared with them. They can't change settings or invite new users. -- **Who is it for:** Regular employees who only need to manage their own expenses, or managers who are reviewing expense reports for a few users but don’t need global visibility. -- **Approvers:** Members who approve expenses can either be Employees, Admins, or Workspace Auditors, depending on how much control they need. -- **Billable:** Employees are billable actors if they take actions on a report on your Group Workspace (including **SmartScanning** a receipt). - -### Workspace Admin Role - -- **What can they do:** Admins have full control. They can change settings, invite members, and view all reports. They can also process reimbursements if they have access to the company’s account. -- **Billing Owners:** Billing owners are Admins by default. **Workspace Admins** are assigned by the owner or another admin. -- **Billable:** Yes, if they perform actions like changing settings or inviting users. Just viewing reports is not billable. - -### Workspace Auditor Role - -- **What can they do:** Workspace Auditors can see all reports, make comments, and export them. They can also mark reports as reimbursed if they're the final approver. -- **Who is it for:** Accountants, bookkeepers, and internal or external audit agents who need to view but not edit workspace settings. -- **Billable:** Yes, if they perform any actions like commenting or exporting a report. Viewing alone doesn't incur a charge. - -### Technical Contact - -- **What can they do:** In case of connection issues, alerts go to the billing owner by default. You can set a technical contact if you want alerts to go to an IT administrator instead. -- **How to set one:** Go to **Settings > Workspaces > Group > [Workspace Name] > Connections > Technical Contact**. -- **Billable:** The technical contact doesn’t need to be a group workspace member and so is not counted towards your billable activity. - -Note: running expense analytics from **Insights** follows the same rules. All the reports and data graphs you generate will be created based on the expense data you have access to. - -# Deep Dive - -## Expense Data Visibility - -The amount of expense data you can see depends on your role within any group workspaces you're part of: - -- **Employees:** Whether you're on a free or paid plan, if you're not approving expenses, you'll only see your own expenses. -- **Approvers:** If you approve expenses for your team and also submit your own, you can view both individual and team-wide expenses and analytics. -- **Admins:** Users with an admin role can see analytics and data for every expense report made by anyone on the workspace. - -If you need to see more data, here are some options: - -- **Become an Admin:** Check within your organization if you can be upgraded to an admin role in your group workspaces. -- **Become a Copilot:** Ask to be added as a **Copilot** to an existing admin account, which will allow you some additional viewing privileges. -- **Become an Approver:** You could also be added as an **Approver** in an existing workflow to view more data. - - diff --git a/docs/articles/expensify-classic/workspaces/SAML-SSO.md b/docs/articles/expensify-classic/domains/SAML-SSO.md similarity index 100% rename from docs/articles/expensify-classic/workspaces/SAML-SSO.md rename to docs/articles/expensify-classic/domains/SAML-SSO.md diff --git a/docs/articles/expensify-classic/reports/Expense-Rules.md b/docs/articles/expensify-classic/expenses/Expense-Rules.md similarity index 100% rename from docs/articles/expensify-classic/reports/Expense-Rules.md rename to docs/articles/expensify-classic/expenses/Expense-Rules.md diff --git a/docs/articles/expensify-classic/expenses/Expense-Types.md b/docs/articles/expensify-classic/expenses/Expense-Types.md new file mode 100644 index 000000000000..9d19dbb4f9ba --- /dev/null +++ b/docs/articles/expensify-classic/expenses/Expense-Types.md @@ -0,0 +1,44 @@ +--- +title: Expense Types +description: Details of the different Expense filters and Expense Types +--- + +# Overview +Expense types help categorize different expenses for better financial management. While reimbursable and non-reimbursable expenses are common, Expensify offers various other options to suit your needs. Let's explore the available expense types. + +# How To +## Filtering a Report by Expense Type +Organizing a report by expense type can make it easier to review expenses on a report. +- Open the report you're interested in. +- Click the **Details** icon in the upper right corner of the report, +- Change the “View” to **Detailed** and “Split by” **Reimbursable** or **Billable**. +- You’ll also see the option to **Group by Category** or **Tags**. + + +# Deep Dive +Each report will show the total amount for all expenses in the upper right. Under that total, there will be a breakdown of amounts that are reimbursable, billable, and non-reimbursable (depending on which of those expense types exist on the report). + +## Expense Types +- **Reimbursable Expenses:** Employees pay for these expenses out of their pockets on behalf of the business and are usually reimbursed. They often come from cash, debit cards, or personal credit card purchases. +- **Non-reimbursable Expenses:** The business directly covers these expenses, so there's no need to reimburse the employee. Typically, these expenses are company card expenses. +- **Billable Expenses:** Business or employee expenses must be billed to a specific client or vendor. Choose this option if you need to track expenses for invoicing to customers, clients, or other departments. +- **Per Diem Expenses:** These expenses involve a daily or partial daily rate you can configure in your expense Workspace. +- **Time Expenses:** Employees or jobs are billed based on an hourly rate that you can set within Expensify. +- **Distance Expenses:** These expenses are related to travel for work. + +{% include faq-begin.md %} + +## What’s the difference between a receipt, an expense, and a report attachment? + +- **Expense:** Created when you SmartScan or manually upload a receipt from a purchase. +- **Receipt:** Automatically attached to the expense during the SmartScan process. +- **Report Attachments:** Additional documents that need to be submitted to your approver (e.g., supplemental documents to the purchase) can be added to a report anytime by clicking the paperclip icon in the Reports Comments. + +## How are credits or refunds displayed in Expensify? +In Expensify, a credit is displayed as an expense with a minus (ex. -$1.00) in front of it. That’s because Expensify defaults all expenses as something that needs to be paid by the company. So a credit that is returned to the company is displayed as a negative expense. + +If a report includes a credit or a refund expense, it will offset the total amount on the report. +For example, the report has two reimbursable expenses, $400 and $500. The total Reimbursable is $900. +Conversely, a -$400 and $500 will be a total Reimbursable amount of $500 + +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/expenses/Per-Diem-Expenses.md b/docs/articles/expensify-classic/expenses/Per-Diem-Expenses.md deleted file mode 100644 index e7a43c1d1d61..000000000000 --- a/docs/articles/expensify-classic/expenses/Per-Diem-Expenses.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Per-Diem-Expenses -description: How to create Per Diem expenses on mobile and web. ---- -# Overview - -What are Per Diems? Per diems, short for "per diem allowance" or "daily allowance," are fixed daily payments provided by your employer to cover expenses incurred during business or work-related travel. These allowances simplify expense tracking and reimbursement for meals, lodging, and incidental expenses during a trip. Per Diems can be masterfully tracked in Expensify! - -## How to create per diem expenses - -To add per diem expenses, you need three pieces of information: -1. Where did you go? - Specify your travel destination. -2. How long were you away? - Define the period you're claiming for. -3. Which rate did you use? - Select the appropriate per diem rate (this is set by your employer). - -### Step 1: On either the web or mobile app, click New Expense and choose Per Diem - -### Step 2: Select your travel destination from the Destination dropdown -If your trip involves multiple stops, create a separate Per Diem expense for each destination. Remember, the times are relative to your home city. - -### Step 3: Select your Start date, End date, Start time, and End time -Expensify will automatically calculate the duration of your trip. We'll show you a breakdown of your days: the time between departure and midnight on the first day, the total days in between, and the time between midnight and your return time on the last day. - -### Step 4: Select a Subrate based on the trip duration from the dropdown list -You can include meal deductions or overnight lodging costs if your jurisdiction permits. - -### Step 5: Enter any other required coding and click “Save” - -### Step 6: Submit for Approval -Finally, submit your Per Diem expense for approval, and you'll be on your way to getting reimbursed! - -{% include faq-begin.md %} - -## Can I edit my per diem expenses? -Per Diems cannot be amended. To make changes, delete the expense and recreate it as needed. - -## What if my admin requires daily per diems? -No problem! Create a separate Per Diem expense for each day of your trip. - -## I have questions about the amount I'm due -Reach out to your internal Admin team, as they've configured the rates in your policy to meet specific requirements. - -## Can I add start and end times to per diems? -Unfortunately, you cannot add start and end times to Per Diems in Expensify. -By following these steps, you can efficiently create and manage your Per Diem expenses in Expensify, making the process of tracking and getting reimbursed hassle-free. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/reports/The-Expenses-Page.md b/docs/articles/expensify-classic/expenses/The-Expenses-Page.md similarity index 100% rename from docs/articles/expensify-classic/reports/The-Expenses-Page.md rename to docs/articles/expensify-classic/expenses/The-Expenses-Page.md diff --git a/docs/articles/expensify-classic/expenses/Track-per-diem-expenses.md b/docs/articles/expensify-classic/expenses/Track-per-diem-expenses.md new file mode 100644 index 000000000000..88dd91997592 --- /dev/null +++ b/docs/articles/expensify-classic/expenses/Track-per-diem-expenses.md @@ -0,0 +1,34 @@ +--- +title: Track per diem expenses +description: Add daily allowance expenses for business travel +--- +
+ +A per diem (also called “per diem allowance” or “daily allowance”) is a fixed daily payment provided by an employer to cover expenses during business or work-related travel. These allowances simplify travel expense tracking and reimbursement for meals, lodging, and incidental expenses. + +{% include info.html %} +Before you can add a per diem expense, a Workspace Admin must enable per diem expenses for the workspace and add the per diem rates. If you do not see an option for per diem rates, it is currently unavailable for your workspace, and you’ll need to reach out to one of your Workspace Admins for guidance. +{% include end-info.html %} + +To add a per diem expense, + +1. Click the **Expenses** tab. +2. Click **New Expense** and choose **Per Diem**. +3. Select your travel destination. + - If your trip involves multiple stops, create a separate per diem expense for each destination. +4. Select the start date, end date, start time, and end time for the trip. +5. Select a sub-rate. The available sub-rates are dependent on the trip duration. + - You can include meal deductions or overnight lodging costs if allowed by your workspace. +6. Enter any other required coding information, such as the category, description, or report, and click **Save**. + +# FAQs + +**How do I edit my per diem expenses?** + +Per diem expenses cannot be amended. To make changes, you must delete the expense and recreate it. + +**What if my admin requires daily per diem submissions?** + +No problem! Create a separate per diem expense for each day of your trip. + +
diff --git a/docs/articles/expensify-classic/expensify-billing/Annual-Subscription.md b/docs/articles/expensify-classic/expensify-billing/Annual-Subscription.md deleted file mode 100644 index 67a96610633d..000000000000 --- a/docs/articles/expensify-classic/expensify-billing/Annual-Subscription.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Annual Subscription -description: Learn more about managing your Annual Subscription. ---- -# Overview -An Annual Subscription offers a 50% cost savings on active user pricing while allowing your company to manage multiple Workspaces across your organization and maintain predictable cost for your Expensify activity. - -_For pricing details, see [expensify.com/pricing](http://www.expensify.com/pricing), and find more ways to save with the Expensify Card here._ - -# How to set subscription size -When you first create a subscription, the best practice is to set your subscription size by entering the average number of active users you expect to have each month for the next year. For example, if you expect to have an average of 10 users each month, even if they are not always the same users, set your subscription size to 10. No need to provision and deprovision access to your team, so you still enjoy flexible usage across the entire company! - -If your Workspaces have more than 10 active users in a month, you will pay the unbundled Pay-per-use rate for the additional users. If you’d like to avoid this, you can enable Auto Increase so your subscription size increases based on Workspace user activity. - -An ‘Active User’ is anyone who chats, creates, submits, approves, reimburses, or exports a report in Expensify. This includes actions taken by Copilots and any automated settings. - -To set your subscription size, go to **Settings > Workspaces > Groups > Subscription**. - -If you do not set a specific subscription size, this will be automatically updated based on your past activity: - -* If you’ve never had activity in Expensify, your subscription size will be set after your first month. Work with your Setup Specialist or Account Manager to determine the best subscription size for your team! - -* For existing Workspaces switching to an Annual Subscription, the subscription size is set to the number of active users on your last month’s billing history. - -* If Auto Increase is not selected, and you have more active users than you’ve input as the subscription size, you will be billed for those at the Pay-per-use rate. - -# How to adjust subscription size -You can add users to your subscription at any time. However, note that when your subscription size is increased, you will start a new 12-month subscription at that new subscription size. - -You can increase your subscription size manually or automatically. - -* To manually increase the size, just update the number in your subscription settings (**Settings > Workspaces > Groups > Subscription**). - -* To automatically increase your subscription size, enable **Auto Increase**. This feature manages your subscription by automatically increasing the count whenever there is activity that exceeds your subscription size. (**Settings > Workspaces > Groups > Subscription**) - -Note: After increasing your subscription size, you won't be able to decrease it for the next 12 months. If your active user numbers tend to fluctuate, you might want to keep this feature disabled. This way, you'll only pay for additional active users in the months they are active. Keep in mind that increasing the subscription size will reset your 12-month subscription period. - -# How to disable Auto Renew -By default, your subscription is set to automatically renew after a year. To disable this, click the toggle from your subscription settings before the current subscription ends. (**Settings > Workspaces > Groups > Subscription**) - -If Auto Renew is disabled, then the last bill at the annual rate will be issued on the date listed under the Auto Renew settings. For example, if your subscription expires on March 1, 2021, then February 2021 will be the last month billed at the annual rate. If you do not set a new subscription, March activity will be billed at the Pay-per-use rate. - -We recommend that you review your user count annually on a proactive basis. Set a reminder to review your active user numbers a month before your subscription expires! If you’d like assistance determining your subscription number, please contact your Account Manager or concierge@expensify.com. - -If you need to decrease your subscription size, you can do this in the first billing month before you are billed. Using the example above, this would be during March 2021. diff --git a/docs/articles/expensify-classic/expensify-billing/Billing-Owner.md b/docs/articles/expensify-classic/expensify-billing/Billing-Owner.md deleted file mode 100644 index 49a369c3cb51..000000000000 --- a/docs/articles/expensify-classic/expensify-billing/Billing-Owner.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Billing-Owner -description: The Billing Owner is the person responsible for payment for all usage on a given Workspace ---- -# Overview -In Expensify, each Workspace has a Billing Owner. The Billing Owner is the person responsible for payment for all usage on a given Workspace. The Billing Owner is also a Workspace Admin, but it’s important to note that not all Workspace Admins are Billing Owners. -# How to set a billing owner -If you've just created a new Group Workspace, you first need to add a payment card to your account. You can do this by going to the web app's Home page and completing the payment card task. Alternatively, you can add a payment card directly from the Payments page (**Settings > Account > Payment**). -- If you already own a Group Workspace subscription, you can edit your payment card details or manage subscription options within the web app under **Settings > Workspaces > Group > Subscription**. -- If you're an individual Workspace owner, you can activate a new monthly subscription in the web app by going to **Settings > Workspaces > Individual > Subscription** section. -# How to change the Billing Owner -A Group Workspace's Billing Owner is typically the user who initially created the Workspace. However, any Workspace Admin can take over the role of Billing Owner by choosing to "Take Over Billing." -Any Workspace Admin can take over the billing responsibility of a Group Workspace as long as they are already a member of that Workspace. If you wish to become the Billing Owner of a Workspace you're not currently a member of, you need to contact an existing Workspace Admin and ask them to add you to the Group Workspace. -To take over billing: -1. Go to **Settings > Workspaces > Group**. -1. Click on the relevant Workspace name. -1. Click on "Take Over Billing." If you haven't added a payment card to your settings yet, you'll be prompted to do so to complete the transfer. - -That's it! As the new Billing Owner, you will receive a monthly email receipt for the Group Workspaces you now own. -# How to update payment details in Expensify -If you're a policy billing owner, you can change your payment information like your payment card and billing currency. If you are a billing owner using the Expensify Card, your monthly company policy charges will be billed to your Expensify Card. - -To change your payment details: -1. Log in to your account using a web browser or Android app (not available on iOS). -1. Go to **Settings > Account > Payments**. -1. To change your payment card, click "Change Payment Card" in the Payment Details section. -1. To change your billing currency, click "Change Billing Currency" and choose a new currency. You'll need to enter the CVC code of your payment card. You can pay in USD, GBP, NZD, or AUD. -# Deep Dive -## Taking over an existing subscription -If the previous Billing Owner had a 12-month subscription, it will be transferred to your Expensify account. If you already have an annual subscription, the sizes of both subscriptions will be combined. For example, if you have a subscription for 10 users and take over from someone with 50 users, your subscription will now cover 60 users. To take over the Annual Subscription, you need to transfer billing ownership of all Workspaces under the previous Billing Owner's name. - -## Taking over Consolidated Domain Billing -If a Domain Admin has enabled Consolidated Domain Billing (**Settings > Domains > Domain Name > Domain Admins**), all Group Workspaces owned by users with email addresses matching the domain will be billed to the Consolidated Domain Billing owner. You can take over billing for the entire domain by following these steps: - -To take over billing for the entire domain, you must: -1. Ensure you have a linked card on your **Settings > Account > Billing** page. -1. Be designated as the Primary Domain Admin. -1. Go to **Settings > Domains > _Domain Name_ > Domain Admins** and enable Consolidated Domain Billing. - -Currently, Consolidated Domain Billing simply consolidates the amounts due for each Group Workspace Billing Owner (listed on the **Settings > Workspaces > Group** page). If you want to use the Annual Subscription across all Workspaces on the domain, you must also be the Billing Owner of all Group Workspaces. -{% include faq-begin.md %} -## Why can't I see the option to take over billing? -There could be two reasons: -1. You may not have the role of Workspace Admin. If you can't click on the Workspace name (if it's not a blue hyperlink), you're not a Workspace Admin. Another Workspace Admin for that Workspace must change your role before you can proceed. -1. Your domain might have Consolidated Domain Billing enabled. Refer to the Deep Dive section to understand how to take over Consolidated Domain Billing. -## What if the current Billing Owner is no longer an employee? -There are two ways to resolve this: -1. Have your IT dept. gain access to the account so that you can make yourself an admin. Your IT department may need to recreate the ex-employee's email address. Once your IT department has access to the employee's Home page, you can request a magic link to be sent to that email address to gain access to the account. -1. Have another admin make you a Workspace admin. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/expensify-billing/Individual-Subscription.md b/docs/articles/expensify-classic/expensify-billing/Individual-Subscription.md deleted file mode 100644 index 1d952cb15b1c..000000000000 --- a/docs/articles/expensify-classic/expensify-billing/Individual-Subscription.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: Individual Subscription -description: Learn more about managing an Individual Subscription. ---- -# Overview -An Individual Subscription is a great option for solo entrepreneurs or anyone who needs to track their own expenses or get paid by someone outside their own organization. -A free Individual Subscription includes: -- Up to 25 SmartScans/month -- Expense tracking -- Mileage tracking -- Invoicing -- Bill splitting -- Receive & send money - -To get unlimited SmartScans, you can upgrade your Individual Subscription for $4.99 (USD per month. - - -# How to sign up for an Individual Subscription -## Website -To activate an Individual Subscription from the web: -1. Log into your Expensify account -2. Navigate to **Settings > Workspaces > Individual** -3. Click **Activate Subscription** under **Monthly** -4. If you don't already have a billing card associated with your account, you will be prompted to add one - -Once payment is complete, you’re all set! - -## Mobile App: -1. Tap **Settings** -2. Under the Workspaces section, select **Free Trial** -3. Select **Upgrade** -4.Tap **Subscription** to upgrade your account - - -# How to manage the subscription -## Web and Android: -When you buy a subscription on the web or through an Android device, you'll be asked to enter your billing information immediately. After the purchase, you can easily view or cancel your subscription anytime by going to **Settings > Workspaces > Individual > Subscription > Show Details**. - -## iOS: -If you purchase a monthly subscription on an iOS device, it will be managed, including cancellations, through the App Store rather than within the Expensify app. You can learn how to manage App Store Subscriptions here. - -After purchasing the subscription from the App Store, remember to sync your app by: -1. Log into the Expensify mobile app -2. Click the three bars in the upper left corner -3. Scroll to **Settings** -4. Select **Sync Account** - -The subscription renewal date is the same as the purchase date. For instance, if you sign up for the subscription on September 7th, it will renew automatically on October 7th. You can cancel your subscription anytime during the month if you no longer need unlimited SmartScans. If you do cancel, keep in mind that your subscription (and your ability to SmartScan) will continue until the last day of the billing cycle. - - -{% include faq-begin.md %} -## Can I use an Individual Subscription while on a Collect or Control Plan? -You can! If you want to track expenses separately from your organization’s Workspace, you can sign up for an Individual Subscription. However, only Submit and Track Workspace plans are available when on an Individual Subscription. Collect and Control Workspace plans require an annual or pay-per-use subscription. For more information, visit expensify.com/pricing. - -## Can I cancel an Individual Subscription anytime? -Yep! You can cancel an Individual Subscription anytime. - -## How do I cancel my subscription? -Follow the steps below to cancel a Monthly Subscription started via the website or Android app: -1. Log into your account using your preferred web browser (ex: Firefox, Chrome, Safari) -2. Navigate to **Settings > Workspace > Individual > Subscriptions** -3. Click the **Cancel Subscription** button to cancel your Monthly Subscription - -Your subscription is a pre-purchase for 30 days of unlimited SmartScanning. This means when you cancel you do not get a refund and instead get to use the remainder of the month of unlimited SmartScanning you purchased. - -## How can I cancel my subscription from the iOS app? -If you signed up for the Monthly Subscription via iOS and your iTunes account, you will need to log into iTunes and locate the subscription there in order to cancel it. The ability to cancel an Expensify subscription started via iOS is strictly limited to your iTunes account. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/expensify-billing/Pay-Per-Use-Subscription.md b/docs/articles/expensify-classic/expensify-billing/Pay-Per-Use-Subscription.md deleted file mode 100644 index fac605ada1bd..000000000000 --- a/docs/articles/expensify-classic/expensify-billing/Pay-Per-Use-Subscription.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Pay-per-use Subscription -description: Learn more about your pay-per-use subscription. ---- -# Overview -Pay-per-use is a billing option for people who prefer to use Expensify month to month or on an as-needed basis. On a pay-per-use subscription, you will only pay for active users in that given month. - -**We recommend this billing setup for companies that use Expensify a few months out of the year**. If you have expenses to manage for more than 6 out of 12 months, an [**Annual Subscription**](https://help.expensify.com/articles/expensify-classic/expensify-billing/Annual-Subscription) may better suit your needs. - -# How to start a pay-per-use subscription -1. Create a Group Workspace if you haven’t already by going to **Settings > Workspaces > Group > New Workspace** -2. Once you’ve created your Workspace, under the “Subscription” section on the Group Workspace page, select “Pay-per-use”. - -{% include faq-begin.md %} - -## What is considered an active user? -An active user is anyone who chats, creates, modifies, submits, approves, reimburses, or exports a report in Expensify. This includes actions taken by a Copilot and Workspace automation (such as Scheduled Submit and automated reimbursement). If no one on your Group Workspace uses Expensify in a given month, you will not be billed for that month. - -You can review the number of Active Users by selecting “View Activity” next to your billing receipt (**Settings > Account > Payments > Billing History**). - -## Why do I have pay-per-use users in addition to my Annual Subscription on my Expensify bill? -If you have an Annual Subscription, but go above your set user count, we will charge at the pay-per-use rate for these ad-hoc users. - -If you expect to have an increased number of users for more than 3 out of 12 months, the most cost-effective approach is to increase your Annual Subscription size. - -## Will billing only be in USD currency? -While USD is the default billing currency, we also have GBP, AUD, and NZD billing currencies. You can see the rates on our [pricing](https://www.expensify.com/pricing) page. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/reports/Attendee-Tracking.md b/docs/articles/expensify-classic/reports/Attendee-Tracking.md deleted file mode 100644 index a0bd2c442dbb..000000000000 --- a/docs/articles/expensify-classic/reports/Attendee-Tracking.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Attendee Tracking -description: Attendee Tracking is an easy way to record meeting and event attendees, as well as showing the amount per employee for transparent group spending. ---- - -Attendee Tracking is an easy way to record meeting and event attendees, as well as showing the amount per employee for transparent group spending. - -## How to use Attendee Tracking -Every expense has an Attendees field and will list the expense creator’s name as the default attendee. The default set of attendees is a list of users from your workspaces and domain. You can add custom attendees by entering their names or email addresses into the Attendees field. - -## How to Add Additional Attendees to an Expense -* Go to the attendees field -* Search for the names of the attendees - * The default list will be of internal attendees belonging to your workspace and domain - * External attendees are not part of your workspace or domain, so you will need to enter their name or email -* Select the attendees you would like to add -* Save the expense -* Once added, the list of attendees for each expense will be visible on the expense line -* An amount per employee expense will also be displayed on the report for easy viewing - -![image of an expense with attendee tracking]({{site.url}}/assets/images/attendee-tracking.png){:width="100%"} - -{% include faq-begin.md %} - -## Can I turn off attendee tracking? -Attendee tracking is a standard field on all expenses and cannot be turned off. - -## Can I remove attendees from the dropdown list? -It is not possible to remove attendees from the list once they have been added. - -## Can I remove myself as an attendee from an expense in my account? -Yes, but you will need to have at least one other attendee selected before you can remove yourself from the expense. - -## How can I see the cost breakdown by an attendee for an expense? -If you hover over the expense amount on your Expenses page you will be able to see this figure. This is also displayed on the report level in each expense line, under the total. - -## How is the cost breakdown calculated and can I adjust it? -The cost breakdown is an even split by however many attendees are listed on the report. It is not possible to edit this. - -## How do expense limits work and attendee tracking work? -Expense limits and workspace rules are applied individually per person. If an expense exceeds the limit for any attendee, a violation is triggered for that expense. - -## Will the expense be visible on the other attendees’ Expenses page? -No, the expense will only be shown in the expense creator’s account. - -## Is there a limit on how many attendees I can add to an expense? -There is no limit. - -## How can I remove attendees from an expense? -You can add or remove attendees from an expense as long as they are on a Draft report. Expenses on submitted reports cannot be edited, so you cannot remove attendees from these. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/settings/Enable-two-factor-authentication.md b/docs/articles/expensify-classic/settings/Enable-two-factor-authentication.md new file mode 100644 index 000000000000..3927ec5b7a33 --- /dev/null +++ b/docs/articles/expensify-classic/settings/Enable-two-factor-authentication.md @@ -0,0 +1,26 @@ +--- +title: Enable Two-factor authentication +description: Use 2FA for extra login security +--- +
+ +Add an extra layer of security to help keep your financial data safe and secure by enabling two-factor authentication (2FA). This will require you to enter a code generated by your preferred authenticator app (like Google Authenticator or Microsoft Authenticator) when you log in. + +1. Hover over Settings, then click **Account**. +2. Under the Account Details tab, scroll down to the Two Factor Authentication section and enable the toggle. +3. Save a copy of your backup codes. + - Click **Download** to save a copy of your backup codes to your computer. + - Click **Copy** to paste the codes into a document or other secure location. + +{% include info.html %} +This step is critical—You will lose access to your account if you cannot use your authenticator app and do not have your recovery codes. +{% include end-info.html %} + +4. Click **Continue**. +5. Download or open your authenticator app and either: + - Scan the QR code shown on your computer screen. + - Enter the 6-digit code from your authenticator app into Expensify and click **Verify**. + +When you log in to Expensify in the future, you’ll be emailed a magic code that you’ll use to log in with. Then you’ll be prompted to open your authenticator app to get the 6-digit code and enter it into Expensify. A new code regenerates every few seconds, so the code is always different. If the code time runs out, you can generate a new code as needed. + +
diff --git a/docs/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates.md b/docs/articles/expensify-classic/spending-insights/Custom-Templates.md similarity index 100% rename from docs/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates.md rename to docs/articles/expensify-classic/spending-insights/Custom-Templates.md diff --git a/docs/articles/expensify-classic/insights-and-custom-reporting/Default-Export-Templates.md b/docs/articles/expensify-classic/spending-insights/Default-Export-Templates.md similarity index 100% rename from docs/articles/expensify-classic/insights-and-custom-reporting/Default-Export-Templates.md rename to docs/articles/expensify-classic/spending-insights/Default-Export-Templates.md diff --git a/docs/articles/expensify-classic/insights-and-custom-reporting/Fringe-Benefits.md b/docs/articles/expensify-classic/spending-insights/Fringe-Benefits.md similarity index 100% rename from docs/articles/expensify-classic/insights-and-custom-reporting/Fringe-Benefits.md rename to docs/articles/expensify-classic/spending-insights/Fringe-Benefits.md diff --git a/docs/articles/expensify-classic/insights-and-custom-reporting/Insights.md b/docs/articles/expensify-classic/spending-insights/Insights.md similarity index 100% rename from docs/articles/expensify-classic/insights-and-custom-reporting/Insights.md rename to docs/articles/expensify-classic/spending-insights/Insights.md diff --git a/docs/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options.md b/docs/articles/expensify-classic/spending-insights/Other-Export-Options.md similarity index 100% rename from docs/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options.md rename to docs/articles/expensify-classic/spending-insights/Other-Export-Options.md diff --git a/docs/articles/expensify-classic/workspaces/Budgets.md b/docs/articles/expensify-classic/workspaces/Budgets.md deleted file mode 100644 index b3f0ad3c6f6f..000000000000 --- a/docs/articles/expensify-classic/workspaces/Budgets.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Budgets -description: Track employee spending across categories and tags by using Expensify's Budgets feature. ---- - -# About -Expensify’s Budgets feature allows you to: -- Set monthly and yearly budgets -- Track spending across categories and tags on an individual and workspace basis -- Get notified when a budget has met specific thresholds - -# How-to -## Category Budgets -1. Navigate to **Settings > Group > [Workspace Name] > Categories** -2. Click the **Edit Rules** button for the category you want to add a budget to -3. Select the **Budget** tab at the top of the modal that opens -4. Click the switch next to **Enable Budget** -5. Once enabled, you will see additional settings to configure: - - **Budget frequency**: you can select if you want this to be a monthly or a yearly budget - - **Total workspace budget**: you can enter an amount if you want to set a budget for the entire workspace - - **Per individual budget**: you can enter an amount if you want to set a budget per person - - **Notification threshold** - this is the % in which you will be notified as the budgets are hit - -## Single-level Tag Budgets -1. Navigate to **Settings > Group > [Workspace Name] > Tags** -2. Click **Edit Budget** next to the tag you want to add a budget to -3. Click the switch next to **Enable Budget** -4. Once enabled, you will see additional settings to configure: - - **Budget frequency**: you can select if you want this to be a monthly or a yearly budget - - **Total workspace budget**: you can enter an amount if you want to set a budget for the entire workspace - - **Per individual budget**: you can enter an amount if you want to set a budget per person - - **Notification threshold** - this is the % in which you will be notified as the budgets are hit - -## Multi-level Tag Budgets -1. Navigate to **Settings > Group > [Workspace Name] > Tags** -2. Click the **Edit Tags** button -3. Click the **Edit Budget** button next to the subtag you want to apply a budget to -4. Click the switch next to **Enable Budget** -5. Once enabled, you will see additional settings to configure: - - **Budget frequency**: you can select if you want this to be a monthly or a yearly budget - - **Total workspace budget**: you can enter an amount if you want to set a budget for the entire workspace - - **Per individual budget**: you can enter an amount if you want to set a budget per person - - **Notification threshold** - this is the % in which you will be notified as the budgets are hit - -{% include faq-begin.md %} -## Can I import budgets as a CSV? -At this time, you cannot import budgets via CSV. - -## When will I be notified as a budget is hit? -Notifications are sent twice: - - When your notification threshold is hit (i.e, if you set this as 50%, you’ll be notified when 50% of the budget is met) - - When 100% of the budget is met - -## How will I be notified when a budget is hit? -A message will be sent in the #admins room of the Workspace. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/workspaces/Categories.md b/docs/articles/expensify-classic/workspaces/Categories.md deleted file mode 100644 index 0cd7ba790a9c..000000000000 --- a/docs/articles/expensify-classic/workspaces/Categories.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: Categories -description: Categories are used to classify and organize expenses ---- -# Overview -Categories are commonly used to classify and organize expenses for both personal finances and business accounting. - -The way Categories function will vary depending on whether or not you connect Expensify to a direct accounting integration (i.e., QuickBooks Online, NetSuite, etc.). - -When reviewing this resource, be sure to take a look at the section that applies to your account setup! - -# How to use Categories -- When using an accounting integration, categories are your chart of accounts/ show in expense claims/etc. -- You can have different categories for different workspaces. - -# How to import Categories (no accounting integration connected) - -## Add Categories via spreadsheet -If you need to import multiple categories at once, you can upload a spreadsheet of these parameters directly to Expensify. - -Before importing the category spreadsheet to Expensify, you'll want to format it so that it lists the Category name as well as any optional fields you'd like to include. - -Required fields: -- Category name - -Optional fields: -- GL Code -- Payroll code -- Enabled (TRUE/ FALSE) -- Max Expense amount -- Receipt Required -- Comments (Required/ Not Required) -- Comment Hint -- Expense Limit Type - -Expensify supports the following file formats for uploading Categories in bulk: - -- CSV -- TXT -- XLS -- XLSX - -Once the spreadsheet is formatted, you can upload it to the workspace under **Settings > Workspace > Group >** *[Workspace Name]* **> Categories**. - -From there, the updated Category list will show as available on all expenses submitted on the corresponding workspace. - -## Manually add Categories -If you need to add Categories to your workspace manually, you can follow the steps below. - -On web: -1. Navigate to **Settings > Workspace > Group/Individual >** *[Workspace Name]* **> Categories**. -2. Add new categories under **Add a Category**. - -On mobile: -1. Tap the **three-bar menu icon** at the top left corner of the app. -2. Tap on **Settings** in the menu on the left side. -3. Scroll to the Workspace subhead and click on Categories listed underneath the default Workspace. -4. Add new categories by tapping the **+** button in the upper right corner. To delete a category, on iOS swipe left, on Android press and hold. Tap a category name to edit it. - -## Add sub-categories -Sub-categories are useful when you want to be more specific, i.e. Travel could have a subcategory of airplane or lodging so the outcome would be Travel:airplane or Travel:lodging. - -If you would like to create sub-categories under your category selection drop-down list, you can do so by adding a colon after the name of the desired category and then typing the sub-category (without spaces around the punctuation). - -# How to import Categories with an accounting integration connected -If you connect Expensify to a direct integration such as QuickBooks Online, QuickBooks Desktop, Sage Intacct, Xero, or NetSuite, then Expensify automatically imports the general ledger parameters from your accounting system as Categories. - -When you first connect your accounting integration your categories will most likely be pulled from your chart of accounts, however this can vary depending on the account integration. - -If you need to update your categories in Expensify, you will first need to update them in your accounting system, then sync the connection in Expensify by navigating to **Settings > Workspace > Group >** _[Workspace Name]_ **> Connection > Sync Now**. - -Alternatively, if you update the category details in your accounting integration, be sure to sync the workspace connection so that the updated information is available on the workspace. - -# Deep Dive - -## Category-specific rules and description hints -If you're an admin using a workspace on a Control plan, you have the ability to enable specific rules for each category. - -These settings are valuable if you want to set a special limit for a certain category. e.g. Your default expense limit is $2500 but Entertainment has a limit of $150 per person, per day. You can also require or not require receipts, which is great for allowing things like Mileage or Per Diems to have no receipt. - -To set up Category Rules, go to **Settings > Workspace> Group >** _[Workspace Name]_ **> Categories**. - -Then, click **Edit Rules** next to the category name for which you'd like to define a rule. - -- **GL Code and Payroll Code**: These are optional fields if these categories need to be associated with either of these codes in your accounting or payroll systems. GL code will be automatically populated if connecting to an accounting integration. -- **Max Amount**: Allows you to set specific expense amount caps based on the expense category. Using **Limit type**, you can define this **per individual expense**, or **per day** (for expenses in a category on an expense report). -- **Receipts**: Allows you to decide whether you want to require receipts based on the category of the expense. For instance, it's common for companies to disable the receipt requirement for Toll expenses. -- **Description**: Allows you to decide whether to require the `description` field to be filled out based on the category of the expense. -- **Description Hint**: This allows you to place a hint in the `description` field. This will appear in light gray font on the expense edit screen in this field to prompt the expense creator to fill in the field accordingly. -- **Rule Enforcement**: If users are in violation of these rules, those violations will be shown in red on the report. Any category-specific violations will only be shown once a category has been selected for a given expense. - -## Make categories required -This means all expenses must be coded with a Category. If they do not have a category, there will be a violation on the expense which can prevent submission. - - -## Update Category rules via spreadsheet -You can update Category rules en masse by exporting them to CSV, editing the spreadsheet, and then importing them back to the categories page. - -This allows you to quickly add new categories and set GL codes, payroll codes, and description hints. This feature is only available for indirect integration setups. - -## Category approvers -Workspace admins can add additional approvers who must approve any expenses categorized with a particular category. - -To configure category approvers: - -1. Go to Settings > Workspace > Group > _[Select Workspace]_ > Categories -2. Click Edit Rules next to the category name that requires a category approver and use the "Approver" field - -The expense will route to this Category approver, before following the approval workflow set up in the People table - -## Auto-categorize card expenses with default categories - -If you're importing card transactions, **Default Categorization** will provide a massive benefit to your company's workflow by **automatically coding** expenses to the proper GL. -- Once configured according to your GL, the default category will detect the type of merchant for an expense based on its Merchant Category Code (MCC) and associate that expense with the proper GL account. -- This time-saver keeps employees from having to manually code expenses and provides admins with the peace of mind that the expenses coming in for approval are more reliably associated with the correct GL account. -- Best of all, this works for personal and company cards alike! - -## Setting up Default Categories - -1. First, go to **Settings** and select the **group Workspace** that you want to configure. -2. Go to the **Categories** tab and scroll down to **Default Categories**. -3. Under the **Category** column, select the account that is most closely associated with the merchant group for all groups that apply to expenses that you and your coworkers submit. If you are unsure, just leave the group **Uncategorized** and the expense will not come in pre-categorized. -4. You're well on your way to expense automation freedom! - -## Default Categories based on specific MCC codes - -If you require more granular detail, the MCC Editor gives you even greater control over which MCC Codes are assigned to which Categories. The MCC Editor can be found just below the Default Categories table. - -## Implicit categorization -Over time, Expensify will learn how you categorize certain merchants and then automatically apply that category to the same merchant in the future. - -- You can always change the category; we'll try to remember that correction for next time! -- Any Expense Rules you have set up will always take precedence over implicit categories. -- Implicit categorization will **only** apply to expenses if you have **not** explicitly set a category already. Changing the category on one expense does not change it for any other expense that has an explicit category already assigned. - -This built-in feature will only use the categories from the currently active workspace on your account. You can change the active workspace by clicking your account icon in the app and selecting the correct workspace name before you SmartScan. - -## Category violations -Category violations can happen for the following reasons: - -- An employee categorized an expense with a category not included in the workspace's categories. This would throw a "category out of workspace" violation. -- If you change your categories importing from an accounting integration, this can cause an old category to still be in use on an open report which would throw a violation on submission. Simply reselect a proper category to clear violation. - -If Scheduled Submit is enabled on a workspace, expenses with category violations will not be auto-submitted unless the expense has a comment added. - -{% include faq-begin.md %} - -## The correct category list isn't showing when one of my employees is categorizing their expenses. Why is this happening? -Its possible the employee is defaulted to their personal workspace so the expenses are not pulling the correct categories to choose from. Check to be sure the report is listed under the correct workspace by looking under the details section on top right of report. - -## Will the account numbers from our accounting system (QuickBooks Online, Sage Intacct, etc.) show in the Category list when employees are choosing what chart of accounts category to code their expense to? -The GL account numbers will be visible in the workspace settings when connected to a Control-level workspace for workspace admins to see. We do not provide this information in an employee-facing capacity because most employees do not have access to that information within the accounting integration. -If you wish to have this information available to your employees when they are categorizing their expenses, you can edit the account name in your accounting software to include the GL number — i.e. **Accounts Payable - 12345** -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/workspaces/Create-categories.md b/docs/articles/expensify-classic/workspaces/Create-categories.md new file mode 100644 index 000000000000..446cd55585c7 --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Create-categories.md @@ -0,0 +1,84 @@ +--- +title: Create categories +description: Use categories to classify and organize expenses +--- + +
+ +Categories can help you classify expenses by expense type. For example, Expensify automatically provides default categories like fees, office supplies, travel, and more. + +You can choose to enable, disable, or edit the default Expensify categories, or you can add or import your own custom categories. You can also add subcategories underneath existing categories. + +# Enable, disable, or edit default categories + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Categories** tab on the left. +5. Enable, disable, edit, or delete any category as desired. + - **To enable/disable**: Click the toggle to the left of the category. Or to enable or disable all of the categories, use the toggle at the top of the toggle column. + - **To edit**: Click the category name and type in the new name. + - **To delete**: Click the X to the right of the category. + +# Add custom categories + +## Automatic import with accounting integration + +Expensify automatically imports your expense-related general ledger accounts as categories when you use an accounting integration (for example, QuickBooks Online, QuickBooks Desktop, Sage Intacct, Xero, or NetSuite). + +To update your categories in Expensify, you must first update the category in your accounting system. Then in Expensify, + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Connections** tab on the left. +5. Click **Sync Now**. + +## Manually add individual categories + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Categories** tab on the left. +5. Scroll down to the bottom of the Categories section to the Add a Category field. +6. Type the name for your new category into the field and click **Add**. + +## Import custom categories + +You can add a list of categories by importing them in a .csv, .txt, .xls, or .xlsx spreadsheet. + +1. Create your categories spreadsheet with at least a column containing the category name. However, you can also add any of the following fields: + - GL Code + - Payroll code + - Enabled (TRUE/ FALSE) + - Max Expense amount + - Receipt Required + - Comments (Required/ Not Required) + - Comment Hint + - Expense Limit Type +2. In Expensify, hover over Settings, then click **Workspaces**. +3. Click the **Group** tab on the left. +4. Click the desired workspace name. +5. Click the **Categories** tab on the left. +6. Click **Import from Spreadsheet**. +7. Review the guidelines, select the checkbox if your file has headers as the first row, and click **Upload File**. + +{% include info.html %} +Each time you upload a list of categories, it will override your previous list. To avoid losing categories, update your current spreadsheet and re-import it into Expensify. +{% include end-info.html %} + +# Add subcategories + +You can create subcategories under a main category. For example, if you have a category for travel that you want flight and lodging to fall under, you can create them as subcategories. + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Categories** tab on the left. +5. Scroll down to the bottom of the Categories section to the Add a Category field. +6. Type the name for your main category into the field, followed by a colon, then add the name of the subcategory and click **Add**. For example, to make a Flight subcategory under Travel, you'll enter “Travel: Flight”. Repeat this step for each subcategory. + +The new subcategories will show up as a dropdown list under the category when an expense is created. The text before the colon will show as the category header (which will not be selectable). The member will have to select an item from the subcategories to add it to the expense. + +
+ diff --git a/docs/articles/expensify-classic/reports/Currency.md b/docs/articles/expensify-classic/workspaces/Currency.md similarity index 100% rename from docs/articles/expensify-classic/reports/Currency.md rename to docs/articles/expensify-classic/workspaces/Currency.md diff --git a/docs/articles/expensify-classic/workspaces/Enable-per-diem-expenses.md b/docs/articles/expensify-classic/workspaces/Enable-per-diem-expenses.md new file mode 100644 index 000000000000..3ab757e75e9a --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Enable-per-diem-expenses.md @@ -0,0 +1,23 @@ +--- +title: Enable per diem expenses +description: Allow employees to add per diem expenses +--- +
+ +In order for Workspace Members to submit per diem expenses, a Workspace Admin must first enable per diem expenses and set the per diem rates. + +To enable and set per diem rates, + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Per Diem** tab on the left. +5. Click the Per Diem toggle to enable it. +6. Create a .csv, .txt, .xls, or .xlsx spreadsheet containing four columns: Destination, Sub-rate, Amount, and Currency. You’ll want a different row for each location that an employee may travel to, which may include states and/or countries to help account for cost differences across various locations. Here are some example templates you can use: + - [Germany multi-subrates](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fs3-us-west-1.amazonaws.com%2Fconcierge-responses-expensify-com%2Fuploads%252F1596692482998-Germany%2B-%2BPer%2BDiem.csv) + - [Sweden multi-subrates](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fs3-us-west-1.amazonaws.com%2Fconcierge-responses-expensify-com%2Fuploads%252F1604410653223-Swedish%2BPer%2BDiem%2BRates.csv) + - [South Africa single rates](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fs3-us-west-1.amazonaws.com%2Fconcierge-responses-expensify-com%2Fuploads%252F1596692413995-SA%2BPer%2BDiem%2BRates.csv) +7. Click **Import from spreadsheet**. +8. Click **Upload** to select your spreadsheet. + +
diff --git a/docs/articles/expensify-classic/workspaces/Invite-members-and-assign-roles.md b/docs/articles/expensify-classic/workspaces/Invite-members-and-assign-roles.md new file mode 100644 index 000000000000..26032d06c1d0 --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Invite-members-and-assign-roles.md @@ -0,0 +1,85 @@ +--- +title: Invite members and assign roles +description: Invite new members to your workspace and assign them a role +--- +
+ +Workspace Admins can invite new members to a workspace either by: + +- Enabling automatic access for members who sign up for Expensify using their domain email address (like yourname@yourcompany.com) +- Sending a link (you can copy the link and send it in Slack, Teams, etc.) +- Sending an invitation email +- Importing a list of new members + +# Enable automatic access with company email + +Enabling pre-approvals allows members to automatically join your workspace when they create an Expensify account using their domain email address (like yourname@yourcompany.com). + +To enable automatic sign-up to your workspace: +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Members** tab on the left. +5. Below your Workspace Joining Link, enable “Pre-approve join requests from validated users at {domain name}.” + +# Invite with a link + +You can copy your workspace’s unique link and share it with someone you want to invite to your workspace. + +To find your workspace’s unique link: +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Members** tab on the left. +5. Copy your Workspace Joining Link and send it via Slack, Teams, or any other communication method. + +# Send an invitation email + +To send an email invitation to your workspace, + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Members** tab on the left. +5. Click **Invite**. +6. Enter the phone number or email address of the person you’re inviting. + +{% include info.html %} +If you’re inviting multiple people who will be assigned the same role, you can enter multiple email addresses or phone numbers by separating them with a comma. +{% include end-info.html %} + +7. Select a role for the new member. The following table shows the permissions available for each role: + +| | Employee | Auditor | Workspace Admin | +|---------------------------|----------------------------------|---------|-----------------| +| Submit reports | Yes | Yes | Yes | +| Comment on reports | Yes | Yes | Yes | +| Approve workspace reports | Only reports submitted to them | Yes | Yes | +| Edit workspace settings | No | No | Yes | + +8. If your workspace uses Advanced Approvals, select “Approves to.” This determines who the member’s reports must be approved by, if applicable. If “no one” is selected, then if the member submits a report, anyone with the Auditor or Workspace Admin role can approve their reports. +9. Add a personal message, if desired. This message will appear in the invitation email or message. +10. Click **Invite**. + +# Import a group of members + +You can add multiple members to your workspace at once by importing a .csv, .txt, .xls, or .xlsx list. + +To add members in bulk, + +1. Create a spreadsheet with an email and role column header and information for all of the members you want to add to your workspace. You can also include any of the following columns: + - Submits To + - Approves To + - Approval Limit + - Over Limit Forward To +2. Hover over Settings, then click **Workspaces**. +3. Click the **Group** tab on the left. +4. Click the desired workspace name. +5. Click the **Members** tab on the left. +6. Click **Import from spreadsheet**. +7. Match the columns in your spreadsheet with the Expensify data they correspond to. +8. Click **Import**. + +If you are utilizing the Advanced Approval feature, you can specify who each member should submit their expense reports to and who an approver should send approved reports for the next step in the approval process. If someone is the final approver, you can leave this field blank. + +
diff --git a/docs/articles/expensify-classic/workspaces/Navigate-multiple-workspaces.md b/docs/articles/expensify-classic/workspaces/Navigate-multiple-workspaces.md new file mode 100644 index 000000000000..e89298cc0da9 --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Navigate-multiple-workspaces.md @@ -0,0 +1,21 @@ +--- +title: Navigate multiple workspaces +description: Using more than one Expensify workspace +--- +
+ +If you have have multiple workspaces (whether its an individual workspace and a group workspace or multiple group workspaces), you’ll want to +- Set a default workspace (Some domains have your default automatically set. In this case, you cannot change your default workspace). +- Select your workspace before creating an expense or report to ensure it’s posted to the correct workspace. + +# Set default workspace + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. To the right of the desired workspace, click **Make Default** or click the settings icon and select **Make Default**. + +# Select workspace for expenses & reports + +Click your profile image and select the workspace from the list at the bottom of the menu. If you have multiple workspaces that you use frequently, always double check that the correct workspace is selected before you create a new expense or report. + +
diff --git a/docs/articles/expensify-classic/workspaces/Per-Diem.md b/docs/articles/expensify-classic/workspaces/Per-Diem.md deleted file mode 100644 index 87aef233aeb1..000000000000 --- a/docs/articles/expensify-classic/workspaces/Per-Diem.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: Expensify Per Diem Settings -description: Expensify Per Diems support simple, pre-determined, tax-free allowances set by your jurisdiction or company. ---- -# Overview - -Per Diems are a flat rate given based on a timed range traveled for business purposes regardless of actual expenses incurred. A Per Diem is only based on the time you travel for work: it starts when you leave your home and ends when you arrive home. - -Per Diems themselves are created to alleviate much of the heavy lifting that expense reporting is well known for. Per Diem claims generally remove the hassle of saving multiple receipts and allow you to claim back a simple, pre-determined, tax-free allowance set by your jurisdiction or company. - -# How to set up Per Diems in Expensify - -Per Diem rates are set up in your company Group workspace from **Settings** > **Workspaces** > **Group** > [_Workspace Name_] > [**Per Diem**](https://expensify.com/policy?param={"policyID":"20AB6A03EB9CE54D"}#js_policyEditor_perDiem). From there, click the toggle to enable the Per Diem feature. - -Next, you'll notice four headings: - -- Destination -- Sub-rate -- Amount -- Currency - -## Configuring your Per Diem spreadsheet - -These four headings are crucial to the setup. You'll want to configure a spreadsheet with your Destinations, filtering down to Subrates with different Amounts and Currencies for each. - -Some example spreadsheets can be found below: - -- 🇩🇪 [Here](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1596692482998-Germany+-+Per+Diem.csv) for an example of Germany multi-subrates -- 🇸🇪 [Here](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1604410653223-Swedish+Per+Diem+Rates.csv) for an example of Sweden multi-subrates -- 🇿🇦 [Here](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1596692413995-SA+Per+Diem+Rates.csv) for an example of South Africa single rates - -When uploading this spreadsheet in Expensify, you'll be asked to map each heading to the relevant column. - -Once uploaded, every rate is editable in-line. Updating a single Destination will update the name for all Destinations, as will the Currency. Updating the Amount and Subrate names will not update for all Destinations though, only the individual Destination. All Destinations will also be alphabetically paginated for easy navigation. - -And that's it! You're done and ready to start approving your Per Diems! - -# Deep dives - -The real challenge of supporting Per Diem for everybody is that there are few standards between jurisdictions, which still rely heavily on these for employee expenses. There is no standard set of rates, deductions, or incidentals across countries. - -Expensify supports multiple countries by allowing full customization via a blank slate of Destinations and Subrates. Each Destination can have a few, or as many Subrates as needed, and these Subrates each have a fully customizable Amount (positive or negative to handle deductions or additions) and Currency. - -## What makes up a Per Diem rate? - -When calculating the reimbursable total of a Per Diem expense, the total typically also depends on three things: - -**Geography:** - -- The destination in which you're traveling and working. -- This is defined by the location of the trip. - -**Time working away from home:** - -- The total of which is broken down based on the Subrates configured (i.e. per 8 hours for Part Days, per 24 hours for Full Days etc). -- This is defined by the length of the trip. - -**Additional Allowances:** - -- Any expenses that are covered by a Per Diem Subrate configured on the workspace (traditionally subsistence expenses: meals & lodging etc, but occasionally also sometimes transport, entertainment etc). -- This is defined by the employee on the trip. - -From an accounting perspective, Per Diem expenses typically fall in a single Per Diem account regardless of which rates are used. Expensify allows you to set a Default Per Diem category, so employees never need to worry about this. - -Most companies will also set a Description Hint, which allows admins to take the hassle out of understanding Per Diem rules from employees when creating their Per Diems. - -## What are the pros and cons of Per Diems? - -**Pros:** - -- Per Diems allow for simple pre-set amounts for trips, reducing or removing the need for traveling employees to save purchase receipts. This allows for approval without documentation up to an amount the company is willing to reimburse which saves the admin time. The only approval is vetting the correct Per Diem use. -- It can encourage prudence as employees know their set limits per day and are unlikely to incur spend over-and-above this, as this could come at a personal cost, depending on the company workspace. -- Per Diems grant more predictability when budgeting for travel. -- Global tax-free rates are often set by a State or jurisdiction, alleviating the responsibility for admins to create "appropriate" reimbursable amounts. - -**Cons:** - -- Sometimes, jurisdiction-set amounts can be deemed incorrect or too low. In these cases, it can be challenging to establish a fair and realistic per diem for different costs in different locations. Additionally, allowing more than a tax-free amount adds undue labor for admin teams to split out taxable and tax-free expense reimbursements. -- Set Per Diems might restrict employee choices that could have benefitted the company, i.e., a sales team member not picking up a full dinner tab or pushing split bill reclamation back onto employees after an individual picks up the tab and each user submits their own Per Diem claims. -- It does not eliminate employee expense fraud, and reduced receipt requirements may make it easier. -- As a business, you can never be sure that your expenses bill matches what employees have had to spend. Because you're reimbursing pre-determined amounts, there may be substantial hidden savings you're not taking advantage of. - -## How to manage existing rates and avoid duplicates - -When you _Export to CSV_, Expensify also assigns a Rate ID to each existing rate, allowing you to edit and overwrite existing rates when you upload a spreadsheet of rates, preventing duplicate rates from being uploaded. It is useful when holding multiple years' worth of rates within a workspace for a small period of overlapping claims. - -Note: _This rate ID corresponds to the Destination+Subrate. You cannot overwrite Destinations, but you can overwrite the Subrate within a Destination by using this rate ID. Always use the “Clear Rate” option with a fresh upload when removing large numbers of rates rather than deleting them individually._ - -{% include faq-begin.md %} - -## How do I report on my team's Per Diem expenses? - -Great question! We’ve added a Per Diem export for users to export Per Diem expenses, detailing start dates, end dates, and the number of days the trip took, amongst some standard fields to export. However, if your jurisdiction requires some extra data, reach out to Concierge or your account manager. - -## What if I need help setting the exact rate amounts and currencies? - -Right now, Expensify can't help determine what these should be. They vary widely based on your country of origin, the state within that jurisdiction, your company workspace, and the time (usually year) you traveled. There's a demonstration spreadsheet [here](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1596692482998-Germany+-+Per+Diem.csv), but it shouldn't be used for actual claims unless verified by your internal finance team or accountants. -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/workspaces/Set-budgets.md b/docs/articles/expensify-classic/workspaces/Set-budgets.md new file mode 100644 index 000000000000..162d15fd0342 --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Set-budgets.md @@ -0,0 +1,55 @@ +--- +title: Set budgets +description: Track employee spending across categories and tags +--- +
+ +Expensify allows workspace admins to create budgets to: +- Set monthly and yearly budget caps +- Track spending across categories and tags +- Get notified when a budget reaches a certain limit + +You can set budgets for specific categories and/or tags. +- **Category budgets**: Add budgets for different expense types like fees, office supplies, travel, meals and entertainment, and more. +- **Tag budgets**: Add budgets for different departments, projects, locations, cost centers, customers, etc. + +{% include info.html %} +Budgets can only be added to Control workspaces. This feature is not available for Collect workspaces. +{% include end-info.html %} + +# Set category budgets + +Once you create your categories, you can enable a budget for each category using the following steps: + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Categories** tab. +5. Click **Edit** next to the category you want to add a budget to. +6. Click the **Budget** tab at the top. +7. Click the toggle to enable budgets (the dot will be green when enabled). +8. Select your budget settings: + - **Budget frequency**: Determine if it will be a monthly or yearly budget. + - **Total workspace budget**: To set an overall budget cap for the workspace, enter the cap amount into the field. + - **Per individual budget**: To set a budget for each member of the workspace, enter the cap amount into the field. + - **Notification threshold**: You’ll automatically receive a notification when 100% of your set budgets have been reached. To receive an additional notification when your budget has reached a specific percentage, enter the percent amount into the field. +9. Click **Save**. + +# Set tag budgets + +Once you create your tags, you can enable a budget for each category using the following steps: + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Tags** tab. +5. Click **Edit** next to the tag you want to add a budget to. +6. Click the toggle to enable budgets (the dot will be green when enabled). +7. Select your budget settings: + - **Budget frequency**: Determine if it will be a monthly or yearly budget. + - **Total workspace budget**: To set an overall budget cap for the workspace, enter the cap amount into the field. + - **Per individual budget**: To set a budget for each member of the workspace, enter the cap amount into the field. + - **Notification threshold**: You’ll automatically receive a notification when 100% of your set budgets have been reached. To receive an additional notification when your budget has reached a specific percentage, enter the percent amount into the field. +8. Click **Save**. + +
diff --git a/docs/articles/expensify-classic/workspaces/Set-up-category-automation.md b/docs/articles/expensify-classic/workspaces/Set-up-category-automation.md new file mode 100644 index 000000000000..b0d4f8ed4beb --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Set-up-category-automation.md @@ -0,0 +1,28 @@ +--- +title: Set up category automation +description: Automatically add categories to expenses +--- +
+ +Expensify learns how you categorize certain merchants over time and then automatically applies that category to the same merchant in the future. However, +- You can always change the category. Expensify will also learn your corrections over time and adjust how it automatically categorizes them. +- Any expense rules you have set up will always take precedence over this automation. This feature only applies to expenses if you have not explicitly set a category already. + +{% include info.html %} +This feature only uses categories listed under the selected workspace. You can change the workspace by clicking your account profile image and selecting the correct workspace from the list before adding a new expense. +{% include end-info.html %} + +You can also set up automatic category assignments based on the merchant code. + +# Set merchant code automation + +Expensify automatically detects the merchant type for most imported credit card expenses based on the merchant category code (MCC). To have your expenses automatically categorized to one of your custom categories based on the MCC, use the following steps: + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Categories** tab on the left. +5. Scroll down to the Default Categories section. +6. Click the Edit icon for the default category to which category will be associated with the default. + +
diff --git a/docs/articles/new-expensify/bank-accounts/Connect-a-Bank-Account.md b/docs/articles/new-expensify/bank-accounts-and-payments/Connect-a-Bank-Account.md similarity index 99% rename from docs/articles/new-expensify/bank-accounts/Connect-a-Bank-Account.md rename to docs/articles/new-expensify/bank-accounts-and-payments/Connect-a-Bank-Account.md index a618c80f98ed..bc0676231544 100644 --- a/docs/articles/new-expensify/bank-accounts/Connect-a-Bank-Account.md +++ b/docs/articles/new-expensify/bank-accounts-and-payments/Connect-a-Bank-Account.md @@ -96,7 +96,7 @@ If you need to enable direct debits from your verified bank account, your bank w If using Expensify to process Bill payments, you'll also need to whitelist the ACH IDs from our partner [Stripe](https://support.stripe.com/questions/ach-direct-debit-company-ids-for-stripe?): - The ACH CompanyIDs (1800948598 and 4270465600) -- The ACH Originator Name (Stripe Payments company) +- The ACH Originator Name (expensify.com) If using Expensify to process international reimbursements from your USD bank account, you'll also need to whitelist the ACH IDs from our partner CorPay: - The ACH CompanyIDs (1522304924 and 2522304924) diff --git a/docs/articles/new-expensify/payments/Distance-Requests.md b/docs/articles/new-expensify/expenses/Distance-Requests.md similarity index 100% rename from docs/articles/new-expensify/payments/Distance-Requests.md rename to docs/articles/new-expensify/expenses/Distance-Requests.md diff --git a/docs/articles/new-expensify/payments/Referral-Program.md b/docs/articles/new-expensify/expenses/Referral-Program.md similarity index 100% rename from docs/articles/new-expensify/payments/Referral-Program.md rename to docs/articles/new-expensify/expenses/Referral-Program.md diff --git a/docs/articles/new-expensify/payments/Request-Money.md b/docs/articles/new-expensify/expenses/Request-Money.md similarity index 100% rename from docs/articles/new-expensify/payments/Request-Money.md rename to docs/articles/new-expensify/expenses/Request-Money.md diff --git a/docs/articles/new-expensify/getting-started/Coming-Soon.md b/docs/articles/new-expensify/getting-started/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/getting-started/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/account-settings/Preferences.md b/docs/articles/new-expensify/settings/Preferences.md similarity index 100% rename from docs/articles/new-expensify/account-settings/Preferences.md rename to docs/articles/new-expensify/settings/Preferences.md diff --git a/docs/articles/new-expensify/account-settings/Profile.md b/docs/articles/new-expensify/settings/Profile.md similarity index 100% rename from docs/articles/new-expensify/account-settings/Profile.md rename to docs/articles/new-expensify/settings/Profile.md diff --git a/docs/articles/new-expensify/account-settings/Security.md b/docs/articles/new-expensify/settings/Security.md similarity index 100% rename from docs/articles/new-expensify/account-settings/Security.md rename to docs/articles/new-expensify/settings/Security.md diff --git a/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md b/docs/articles/new-expensify/workspaces/The-Free-Plan.md similarity index 100% rename from docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md rename to docs/articles/new-expensify/workspaces/The-Free-Plan.md diff --git a/docs/expensify-classic/hubs/bank-accounts-and-credit-cards/index.html b/docs/expensify-classic/hubs/bank-accounts-and-credit-cards/index.html deleted file mode 100644 index 2f91f0913d6e..000000000000 --- a/docs/expensify-classic/hubs/bank-accounts-and-credit-cards/index.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Bank Accounts & Credit Cards ---- - -{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/bank-accounts-and-payments/index.html b/docs/expensify-classic/hubs/bank-accounts-and-payments/index.html new file mode 100644 index 000000000000..22e39250aea4 --- /dev/null +++ b/docs/expensify-classic/hubs/bank-accounts-and-payments/index.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Bank accounts & payments +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/bank-accounts-and-credit-cards/business-bank-accounts.html b/docs/expensify-classic/hubs/connect-credit-cards/business-bank-accounts.html similarity index 100% rename from docs/expensify-classic/hubs/bank-accounts-and-credit-cards/business-bank-accounts.html rename to docs/expensify-classic/hubs/connect-credit-cards/business-bank-accounts.html diff --git a/docs/expensify-classic/hubs/bank-accounts-and-credit-cards/company-cards.html b/docs/expensify-classic/hubs/connect-credit-cards/company-cards.html similarity index 100% rename from docs/expensify-classic/hubs/bank-accounts-and-credit-cards/company-cards.html rename to docs/expensify-classic/hubs/connect-credit-cards/company-cards.html diff --git a/docs/expensify-classic/hubs/bank-accounts-and-credit-cards/deposit-accounts.html b/docs/expensify-classic/hubs/connect-credit-cards/deposit-accounts.html similarity index 100% rename from docs/expensify-classic/hubs/bank-accounts-and-credit-cards/deposit-accounts.html rename to docs/expensify-classic/hubs/connect-credit-cards/deposit-accounts.html diff --git a/docs/new-expensify/hubs/billing-and-plan-types/index.html b/docs/expensify-classic/hubs/connect-credit-cards/index.html similarity index 62% rename from docs/new-expensify/hubs/billing-and-plan-types/index.html rename to docs/expensify-classic/hubs/connect-credit-cards/index.html index b49b2b62b1d6..e21df799a132 100644 --- a/docs/new-expensify/hubs/billing-and-plan-types/index.html +++ b/docs/expensify-classic/hubs/connect-credit-cards/index.html @@ -1,6 +1,6 @@ --- layout: default -title: Billing & Plan Types +title: Connect Credit Cards --- {% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/account-settings/index.html b/docs/expensify-classic/hubs/spending-insights/index.html similarity index 65% rename from docs/new-expensify/hubs/account-settings/index.html rename to docs/expensify-classic/hubs/spending-insights/index.html index 434761a6c4fa..68b302394ff3 100644 --- a/docs/new-expensify/hubs/account-settings/index.html +++ b/docs/expensify-classic/hubs/spending-insights/index.html @@ -1,6 +1,6 @@ --- layout: default -title: Account Settings +title: Spending Insights --- {% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/bank-accounts-and-payments/index.html b/docs/new-expensify/hubs/bank-accounts-and-payments/index.html new file mode 100644 index 000000000000..94db3c798710 --- /dev/null +++ b/docs/new-expensify/hubs/bank-accounts-and-payments/index.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Bank Accounts & Payments +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/bank-accounts/index.html b/docs/new-expensify/hubs/bank-accounts/index.html deleted file mode 100644 index 2f91f0913d6e..000000000000 --- a/docs/new-expensify/hubs/bank-accounts/index.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Bank Accounts & Credit Cards ---- - -{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/payments/index.html b/docs/new-expensify/hubs/expenses/index.html similarity index 74% rename from docs/new-expensify/hubs/payments/index.html rename to docs/new-expensify/hubs/expenses/index.html index d22f7e375f2e..66aa2c74ac57 100644 --- a/docs/new-expensify/hubs/payments/index.html +++ b/docs/new-expensify/hubs/expenses/index.html @@ -1,6 +1,6 @@ --- layout: default -title: Payments +title: Expenses --- {% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/expensify-partner-program/index.html b/docs/new-expensify/hubs/expensify-partner-program/index.html deleted file mode 100644 index c0a192c6e916..000000000000 --- a/docs/new-expensify/hubs/expensify-partner-program/index.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Expensify Partner Program ---- - -{% include hub.html %} diff --git a/docs/new-expensify/hubs/send-payments/index.html b/docs/new-expensify/hubs/getting-started/index.html similarity index 67% rename from docs/new-expensify/hubs/send-payments/index.html rename to docs/new-expensify/hubs/getting-started/index.html index c8275af5c353..8c68adfdac5c 100644 --- a/docs/new-expensify/hubs/send-payments/index.html +++ b/docs/new-expensify/hubs/getting-started/index.html @@ -1,6 +1,6 @@ --- layout: default -title: Send Payments +title: Getting started --- {% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/insights-and-custom-reporting/index.html b/docs/new-expensify/hubs/settings/index.html similarity index 74% rename from docs/expensify-classic/hubs/insights-and-custom-reporting/index.html rename to docs/new-expensify/hubs/settings/index.html index 16c96cb51d01..44e730f30a2b 100644 --- a/docs/expensify-classic/hubs/insights-and-custom-reporting/index.html +++ b/docs/new-expensify/hubs/settings/index.html @@ -1,6 +1,6 @@ --- layout: default -title: Exports +title: Settings --- {% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/workspace-and-domain-settings/index.html b/docs/new-expensify/hubs/workspace-and-domain-settings/index.html deleted file mode 100644 index c4e5d06d6b8a..000000000000 --- a/docs/new-expensify/hubs/workspace-and-domain-settings/index.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Workspace & Domain Settings ---- - -{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/send-payments/index.html b/docs/new-expensify/hubs/workspaces/index.html similarity index 69% rename from docs/expensify-classic/hubs/send-payments/index.html rename to docs/new-expensify/hubs/workspaces/index.html index c8275af5c353..436c9fcfecb1 100644 --- a/docs/expensify-classic/hubs/send-payments/index.html +++ b/docs/new-expensify/hubs/workspaces/index.html @@ -1,6 +1,6 @@ --- layout: default -title: Send Payments +title: Workspaces --- {% include hub.html %} \ No newline at end of file diff --git a/docs/redirects.csv b/docs/redirects.csv index 9c12c4b0048a..209ac995c1b6 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -76,3 +76,8 @@ https://help.expensify.com/articles/expensify-classic/workspace-and-domain-setti https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/Budgets,https://help.expensify.com/articles/expensify-classic/workspaces/Budgets https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/Reimbursement,https://help.expensify.com/articles/expensify-classic/send-payments/Reimbursing-Reports https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/Tags,https://help.expensify.com/articles/expensify-classic/workspaces/Tags +https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/User-Roles,https://help.expensify.com/articles/expensify-classic/workspaces/Change-member-workspace-roles +https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/tax-tracking,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Apply-Tax +https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/User-Roles.html,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/ +https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Owner,https://help.expensify.com/articles/expensify-classic/workspaces/Assign-billing-owner-and-payment-account +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options.html,https://help.expensify.com/articles/expensify-classic/spending-insights/Other-Export-Options diff --git a/ios/NewExpensify/AppDelegate.mm b/ios/NewExpensify/AppDelegate.mm index f5ddba46f5f1..f4f7f3bc8dbc 100644 --- a/ios/NewExpensify/AppDelegate.mm +++ b/ios/NewExpensify/AppDelegate.mm @@ -44,7 +44,12 @@ - (BOOL)application:(UIApplication *)application // stopped by a native module in the JS so we can measure total time starting // in the native layer and ending in the JS layer. [RCTStartupTimer start]; - + + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"isFirstRunComplete"]) { + [UIApplication sharedApplication].applicationIconBadgeNumber = 0; + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isFirstRunComplete"]; + } + return YES; } diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 61c7db831152..ad29816ca2d4 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.60 + 1.4.61 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.60.0 + 1.4.61.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 8ed76a46562e..30ebc444e07c 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.60 + 1.4.61 CFBundleSignature ???? CFBundleVersion - 1.4.60.0 + 1.4.61.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 1be7e95439a5..5cfad9bf91a6 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.60 + 1.4.61 CFBundleVersion - 1.4.60.0 + 1.4.61.0 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 24ef0704be25..32a8bca75bcd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1363,7 +1363,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.35): + - RNLiveMarkdown (0.1.38): - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1415,7 +1415,7 @@ PODS: - SDWebImage/Core (~> 5.17) - SocketRocket (0.6.1) - Turf (2.7.0) - - VisionCamera (4.0.0-beta.11): + - VisionCamera (4.0.0-beta.13): - React - React-callinvoker - React-Core @@ -1904,7 +1904,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 25b969a1ffc806b9f9ad2e170d4a3b049c6af85e RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: aaf5afb231515d8ddfdef5f2928581e8ff606ad4 + RNLiveMarkdown: 0f7819903c63a786bbb80fd620baba10d43dfe18 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: fcf7f1cbdc8bd7569c267d07284e8a5c7bee06ed RNPermissions: 9b086c8f05b2e2faa587fdc31f4c5ab4509728aa @@ -1920,7 +1920,7 @@ SPEC CHECKSUMS: SDWebImageWebPCoder: af09429398d99d524cae2fe00f6f0f6e491ed102 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 - VisionCamera: b6b6f46949eae83b71429c971162af337ef34fa3 + VisionCamera: 9f26224fce1233ffdad9fa4e56863e3de2190dc0 Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 PODFILE CHECKSUM: a431c146e1501391834a2f299a74093bac53b530 diff --git a/package-lock.json b/package-lock.json index 377e4feb079b..82146ed8e249 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "new.expensify", - "version": "1.4.60-0", + "version": "1.4.61-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.60-0", + "version": "1.4.61-0", "hasInstallScript": true, "license": "MIT", "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.35", + "@expensify/react-native-live-markdown": "0.1.38", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -50,7 +50,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#8dd2cb43ee29a369ccc459e6ad59bcdeaf579e6f", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -94,7 +94,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#cd778b55e419bf09ecf377b1e3ac013b7758434d", + "react-native-onyx": "2.0.23", "react-native-pager-view": "6.2.2", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -114,7 +114,7 @@ "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "3.8.0", - "react-native-vision-camera": "^4.0.0-beta.11", + "react-native-vision-camera": "^4.0.0-beta.13", "react-native-web": "^0.19.9", "react-native-web-linear-gradient": "^1.1.2", "react-native-web-sound": "^0.1.3", @@ -202,7 +202,7 @@ "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", - "electron": "^29.0.0", + "electron": "^29.2.0", "electron-builder": "24.13.2", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", @@ -3097,9 +3097,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.35", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.35.tgz", - "integrity": "sha512-W0FFIiU/sT+AwIrIOUHiNAHYjODAkEdYsf75tfBbkA6v2byHPxUlbzaJrZEQc0HgbvtAfTf9iQQqGWjNqe4pog==", + "version": "0.1.38", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.38.tgz", + "integrity": "sha512-0EcXvK/eqeJdesX8DBibJ+V2KX9n5Gbmg0fWTk93mGOUA70h3W6lO68nuch40X+RgQdOgIf50BMfzbBzzVdJwA==", "engines": { "node": ">= 18.0.0" }, @@ -25715,10 +25715,11 @@ } }, "node_modules/electron": { - "version": "29.0.0", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-29.2.0.tgz", + "integrity": "sha512-ALKrCN52RG4g9prx4DriXSPnY5WoiyRUCNp7zEVQuoiNOpHTNqMMpRidQAHzntV4hajF1LMWHVoBkwqIs1jHhg==", "dev": true, "hasInstallScript": true, - "license": "MIT", "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^20.9.0", @@ -27495,8 +27496,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#8dd2cb43ee29a369ccc459e6ad59bcdeaf579e6f", - "integrity": "sha512-ddo/GaZPDmCm6RSSYb3zDU3JLdqfkxWwd3gk1FsJO4NysJcrG7Yum43fO+qn/6I9mDKTYTsxnVt/KdEQRUtN5A==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", + "integrity": "sha512-/NAZoAXqeqFWHvC61dueqq9VjRugF69urUtDdDhsfvu1sQE2PCnBoM7a+ACoAEWRYrnP82cyHHhdSA8e7fPuAg==", "license": "MIT", "dependencies": { "classnames": "2.5.0", @@ -39474,10 +39475,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.10", - "resolved": "git+ssh://git@github.com/Expensify/react-native-onyx.git#cd778b55e419bf09ecf377b1e3ac013b7758434d", - "integrity": "sha512-0ky9ISCNhuch0onIkeu/XFhVHoLp7gFwg6Q5TZYt1Wfmu/48Z9fjJxKxw5PRLfNBtwDQZQWKYFAf/CIoBxnmZw==", - "license": "MIT", + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.23.tgz", + "integrity": "sha512-A0SuipCwAswl8+hBlr9tNPTBdxixKBJkcdP8YpgUC072/4Kcfvv0pfpfQOHoeCR/bP2wvDagpdN3VtebRfoVMg==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -39769,9 +39769,9 @@ } }, "node_modules/react-native-vision-camera": { - "version": "4.0.0-beta.11", - "resolved": "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-4.0.0-beta.11.tgz", - "integrity": "sha512-cKg/nwT0q0H1ivEVG+PQt/QxhFgf/dd7SiMm7bCzlSCmt0T2tXyIdLsuY312iKBB2qasQZOYtzRsoQjipHQkDw==", + "version": "4.0.0-beta.13", + "resolved": "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-4.0.0-beta.13.tgz", + "integrity": "sha512-a5ypYsr9XsERfx2UCmL1oSMC/Irra6X0Ixv0/hCgM/2ndt/aK94A6FwONr1kA/GRyU67ekOhk8HwUfxx+hi6dQ==", "peerDependencies": { "react": "*", "react-native": "*", diff --git a/package.json b/package.json index f3bbe2b7dd11..e0a2f10f9cf8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.60-0", + "version": "1.4.61-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -62,7 +62,7 @@ }, "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.35", + "@expensify/react-native-live-markdown": "0.1.38", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -101,7 +101,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#8dd2cb43ee29a369ccc459e6ad59bcdeaf579e6f", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -145,7 +145,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx#cd778b55e419bf09ecf377b1e3ac013b7758434d", + "react-native-onyx": "2.0.23", "react-native-pager-view": "6.2.2", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -165,7 +165,7 @@ "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "3.8.0", - "react-native-vision-camera": "^4.0.0-beta.11", + "react-native-vision-camera": "^4.0.0-beta.13", "react-native-web": "^0.19.9", "react-native-web-linear-gradient": "^1.1.2", "react-native-web-sound": "^0.1.3", @@ -253,7 +253,7 @@ "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", - "electron": "^29.0.0", + "electron": "^29.2.0", "electron-builder": "24.13.2", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", diff --git a/src/App.tsx b/src/App.tsx index 61874dc72fb0..a3a9f7a3f3b6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,7 +16,6 @@ import HTMLEngineProvider from './components/HTMLEngineProvider'; import InitialURLContextProvider from './components/InitialURLContextProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; -import OptionsListContextProvider from './components/OptionListContextProvider'; import PopoverContextProvider from './components/PopoverProvider'; import SafeArea from './components/SafeArea'; import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider'; @@ -83,7 +82,6 @@ function App({url}: AppProps) { FullScreenContextProvider, VolumeContextProvider, VideoPopoverMenuContextProvider, - OptionsListContextProvider, ]} > diff --git a/src/CONST.ts b/src/CONST.ts index 49019170da66..f9229d5185b4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -56,6 +56,15 @@ const chatTypes = { // Explicit type annotation is required const cardActiveStates: number[] = [2, 3, 4, 7]; +const onboardingChoices = { + TRACK: 'newDotTrack', + EMPLOYER: 'newDotEmployer', + MANAGE_TEAM: 'newDotManageTeam', + PERSONAL_SPEND: 'newDotPersonalSpend', + CHAT_SPLIT: 'newDotSplitChat', + LOOKING_AROUND: 'newDotLookingAround', +}; + const CONST = { MERGED_ACCOUNT_PREFIX: 'MERGED_', DEFAULT_POLICY_ROOM_CHAT_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL], @@ -63,6 +72,8 @@ const CONST = { // Note: Group and Self-DM excluded as these are not tied to a Workspace WORKSPACE_ROOM_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL, chatTypes.POLICY_ROOM, chatTypes.POLICY_EXPENSE_CHAT], ANDROID_PACKAGE_NAME, + ANIMATED_HIGHLIGHT_DELAY: 500, + ANIMATED_HIGHLIGHT_DURATION: 500, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, ANIMATION_IN_TIMING: 100, @@ -840,6 +851,7 @@ const CONST = { BOTTOM_DOCKED: 'bottom_docked', POPOVER: 'popover', RIGHT_DOCKED: 'right_docked', + ONBOARDING: 'onboarding', }, ANCHOR_ORIGIN_VERTICAL: { TOP: 'top', @@ -852,6 +864,11 @@ const CONST = { RIGHT: 'right', }, POPOVER_MENU_PADDING: 8, + RESTORE_FOCUS_TYPE: { + DEFAULT: 'default', + DELETE: 'delete', + PRESERVE: 'preserve', + }, }, TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', @@ -1260,6 +1277,24 @@ const CONST = { TERMS: 'TermsStep', ACTIVATE: 'ActivateStep', }, + STEP_REFACTOR: { + ADD_BANK_ACCOUNT: 'AddBankAccountStep', + ADDITIONAL_DETAILS: 'AdditionalDetailsStep', + VERIFY_IDENTITY: 'VerifyIdentityStep', + TERMS_AND_FEES: 'TermsAndFeesStep', + }, + STEP_NAMES: ['1', '2', '3', '4'], + SUBSTEP_INDEXES: { + BANK_ACCOUNT: { + ACCOUNT_NUMBERS: 0, + }, + PERSONAL_INFO: { + LEGAL_NAME: 0, + DATE_OF_BIRTH: 1, + SSN: 2, + ADDRESS: 3, + }, + }, TIER_NAME: { PLATINUM: 'PLATINUM', GOLD: 'GOLD', @@ -3409,6 +3444,11 @@ const CONST = { HIDE_TIME_TEXT_WIDTH: 250, MIN_WIDTH: 170, MIN_HEIGHT: 120, + CONTROLS_STATUS: { + SHOW: 'show', + HIDE: 'hide', + VOLUME_ONLY: 'volumeOnly', + }, CONTROLS_POSITION: { NATIVE: 32, NORMAL: 8, @@ -3439,6 +3479,73 @@ const CONST = { MINIMUM_WORKSPACES_TO_SHOW_SEARCH: 8, }, + WELCOME_VIDEO_URL: `${CLOUDFRONT_URL}/videos/intro-1280.mp4`, + + ONBOARDING_CHOICES: {...onboardingChoices}, + + ONBOARDING_CONCIERGE: { + [onboardingChoices.TRACK]: + "# Welcome to Expensify, let's start tracking your expenses!\n" + + "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + '\n' + + "To track your expenses, create a workspace to keep everything in one place. Here's how:\n" + + '1. From the home screen, click the green + button > New Workspace\n' + + '2. Give your workspace a name (e.g. "My business expenses”).\n' + + '\n' + + 'Then, add expenses to your workspace:\n' + + '1. Find your workspace using the search field.\n' + + '2. Click the gray + button next to the message field.\n' + + '3. Click Request money, then add your expense type.\n' + + '\n' + + "We'll store all expenses in your new workspace for easy access. Let me know if you have any questions!", + [onboardingChoices.EMPLOYER]: + '# Welcome to Expensify, the fastest way to get paid back!\n' + + "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + '\n' + + 'To submit expenses for reimbursement:\n' + + '1. From the home screen, click the green + button > Request money.\n' + + "2. Enter an amount or scan a receipt, then input your boss's email.\n" + + '\n' + + "That'll send a request to get you paid back. Let me know if you have any questions!", + [onboardingChoices.MANAGE_TEAM]: + "# Welcome to Expensify, let's start managing your team's expenses!\n" + + "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + '\n' + + "To manage your team's expenses, create a workspace to keep everything in one place. Here's how:\n" + + '1. From the home screen, click the green + button > New Workspace\n' + + '2. Give your workspace a name (e.g. “Sales team expenses”).\n' + + '\n' + + 'Then, invite your team to your workspace via the Members pane and connect a business bank account to reimburse them. Let me know if you have any questions!', + [onboardingChoices.PERSONAL_SPEND]: + "# Welcome to Expensify, let's start tracking your expenses!\n" + + "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + '\n' + + "To track your expenses, create a workspace to keep everything in one place. Here's how:\n" + + '1. From the home screen, click the green + button > New Workspace\n' + + '2. Give your workspace a name (e.g. "My expenses”).\n' + + '\n' + + 'Then, add expenses to your workspace:\n' + + '1. Find your workspace using the search field.\n' + + '2. Click the gray + button next to the message field.\n' + + '3. Click Request money, then add your expense type.\n' + + '\n' + + "We'll store all expenses in your new workspace for easy access. Let me know if you have any questions!", + [onboardingChoices.CHAT_SPLIT]: + '# Welcome to Expensify, where splitting the bill is an easy conversation!\n' + + "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + '\n' + + 'To split an expense:\n' + + '1. From the home screen, click the green + button > Request money.\n' + + '2. Enter an amount or scan a receipt, then choose who you want to split it with.\n' + + '\n' + + "We'll send a request to each person so they can pay you back. Let me know if you have any questions!", + [onboardingChoices.LOOKING_AROUND]: + '# Welcome to Expensify!\n' + + "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + '\n' + + "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", + }, + REPORT_FIELD_TITLE_FIELD_ID: 'text_title', MOBILE_PAGINATION_SIZE: 15, diff --git a/src/NAVIGATORS.ts b/src/NAVIGATORS.ts index 3bc9c5e57b9b..f199d2841ec0 100644 --- a/src/NAVIGATORS.ts +++ b/src/NAVIGATORS.ts @@ -7,5 +7,7 @@ export default { BOTTOM_TAB_NAVIGATOR: 'BottomTabNavigator', LEFT_MODAL_NAVIGATOR: 'LeftModalNavigator', RIGHT_MODAL_NAVIGATOR: 'RightModalNavigator', + ONBOARDING_MODAL_NAVIGATOR: 'OnboardingModalNavigator', + WELCOME_VIDEO_MODAL_NAVIGATOR: 'WelcomeVideoModalNavigator', FULL_SCREEN_NAVIGATOR: 'FullScreenNavigator', } as const; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 46b2c5f8055c..0a21cb17df7e 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -276,6 +276,9 @@ const ONYXKEYS = { // Max height supported for HTML element MAX_CANVAS_HEIGHT: 'maxCanvasHeight', + /** Onboarding Purpose selected by the user during Onboarding flow */ + ONBOARDING_PURPOSE_SELECTED: 'onboardingPurposeSelected', + // Max width supported for HTML element MAX_CANVAS_WIDTH: 'maxCanvasWidth', @@ -374,6 +377,8 @@ const ONYXKEYS = { PROFILE_SETTINGS_FORM_DRAFT: 'profileSettingsFormDraft', DISPLAY_NAME_FORM: 'displayNameForm', DISPLAY_NAME_FORM_DRAFT: 'displayNameFormDraft', + ONBOARDING_PERSONAL_DETAILS_FORM: 'onboardingPersonalDetailsForm', + ONBOARDING_PERSONAL_DETAILS_FORM_DRAFT: 'onboardingPersonalDetailsFormDraft', ROOM_NAME_FORM: 'roomNameForm', ROOM_NAME_FORM_DRAFT: 'roomNameFormDraft', REPORT_DESCRIPTION_FORM: 'reportDescriptionForm', @@ -430,8 +435,8 @@ const ONYXKEYS = { REPORT_FIELD_EDIT_FORM_DRAFT: 'reportFieldEditFormDraft', REIMBURSEMENT_ACCOUNT_FORM: 'reimbursementAccount', REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft', - PERSONAL_BANK_ACCOUNT: 'personalBankAccountForm', - PERSONAL_BANK_ACCOUNT_DRAFT: 'personalBankAccountFormDraft', + PERSONAL_BANK_ACCOUNT_FORM: 'personalBankAccountForm', + PERSONAL_BANK_ACCOUNT_FORM_DRAFT: 'personalBankAccountFormDraft', EXIT_SURVEY_REASON_FORM: 'exitSurveyReasonForm', EXIT_SURVEY_REASON_FORM_DRAFT: 'exitSurveyReasonFormDraft', EXIT_SURVEY_RESPONSE_FORM: 'exitSurveyResponseForm', @@ -461,6 +466,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM]: FormTypes.CloseAccountForm; [ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM]: FormTypes.ProfileSettingsForm; [ONYXKEYS.FORMS.DISPLAY_NAME_FORM]: FormTypes.DisplayNameForm; + [ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM]: FormTypes.DisplayNameForm; [ONYXKEYS.FORMS.ROOM_NAME_FORM]: FormTypes.RoomNameForm; [ONYXKEYS.FORMS.REPORT_DESCRIPTION_FORM]: FormTypes.ReportDescriptionForm; [ONYXKEYS.FORMS.LEGAL_NAME_FORM]: FormTypes.LegalNameForm; @@ -491,7 +497,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM]: FormTypes.GetPhysicalCardForm; [ONYXKEYS.FORMS.REPORT_FIELD_EDIT_FORM]: FormTypes.ReportFieldEditForm; [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm; - [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm; + [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; [ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS]: FormTypes.AdditionalDetailStepForm; [ONYXKEYS.FORMS.POLICY_TAG_NAME_FORM]: FormTypes.PolicyTagNameForm; @@ -627,6 +633,7 @@ type OnyxValuesMapping = { [ONYXKEYS.MAX_CANVAS_AREA]: number; [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; [ONYXKEYS.MAX_CANVAS_WIDTH]: number; + [ONYXKEYS.ONBOARDING_PURPOSE_SELECTED]: string; [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: boolean; [ONYXKEYS.LAST_VISITED_PATH]: string | undefined; [ONYXKEYS.RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 8130c271a2db..f5d653abe5a7 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -109,6 +109,7 @@ const ROUTES = { }, SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card', SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account', + SETTINGS_ADD_BANK_ACCOUNT_REFACTOR: 'settings/wallet/add-bank-account-refactor', SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments', SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: { route: 'settings/wallet/card/:domain/digital-details/update-address', @@ -180,6 +181,8 @@ const ROUTES = { getRoute: (backTo?: string) => getUrlWithBackToParam('settings/exit-survey/confirm', backTo), }, + SETTINGS_SAVE_THE_WORLD: 'settings/teachersunite', + KEYBOARD_SHORTCUTS: 'keyboard-shortcuts', NEW: 'new', @@ -438,10 +441,10 @@ const ROUTES = { ONBOARD_MANAGE_EXPENSES: 'onboard/manage-expenses', ONBOARD_EXPENSIFY_CLASSIC: 'onboard/expensify-classic', - TEACHERS_UNITE: 'teachersunite', - I_KNOW_A_TEACHER: 'teachersunite/i-know-a-teacher', - I_AM_A_TEACHER: 'teachersunite/i-am-a-teacher', - INTRO_SCHOOL_PRINCIPAL: 'teachersunite/intro-school-principal', + TEACHERS_UNITE: 'settings/teachersunite', + I_KNOW_A_TEACHER: 'settings/teachersunite/i-know-a-teacher', + I_AM_A_TEACHER: 'settings/teachersunite/i-am-a-teacher', + INTRO_SCHOOL_PRINCIPAL: 'settings/teachersunite/intro-school-principal', ERECEIPT: { route: 'eReceipt/:transactionID', @@ -620,11 +623,11 @@ const ROUTES = { }, WORKSPACE_MEMBER_DETAILS: { route: 'settings/workspaces/:policyID/members/:accountID', - getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/members/${accountID}`, backTo), + getRoute: (policyID: string, accountID: number) => `settings/workspaces/${policyID}/members/${accountID}` as const, }, WORKSPACE_MEMBER_ROLE_SELECTION: { route: 'settings/workspaces/:policyID/members/:accountID/role-selection', - getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/members/${accountID}/role-selection`, backTo), + getRoute: (policyID: string, accountID: number) => `settings/workspaces/${policyID}/members/${accountID}/role-selection` as const, }, WORKSPACE_OWNER_CHANGE_SUCCESS: { route: 'settings/workspaces/:policyID/change-owner/:accountID/success', @@ -681,6 +684,11 @@ const ROUTES = { getRoute: (contentType: string, backTo?: string) => getUrlWithBackToParam(`referral/${contentType}`, backTo), }, PROCESS_MONEY_REQUEST_HOLD: 'hold-request-educational', + ONBOARDING_ROOT: 'onboarding', + ONBOARDING_PERSONAL_DETAILS: 'onboarding/personal-details', + ONBOARDING_PURPOSE: 'onboarding/purpose', + WELCOME_VIDEO_ROOT: 'onboarding/welcome-video', + TRANSACTION_RECEIPT: { route: 'r/:reportID/transaction/:transactionID/receipt', getRoute: (reportID: string, transactionID: string) => `r/${reportID}/transaction/${transactionID}/receipt` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index cf864fd96b3e..6c9262deeeb3 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -29,9 +29,11 @@ const SCREENS = { WORKSPACES: 'Settings_Workspaces', SECURITY: 'Settings_Security', ABOUT: 'Settings_About', + SAVE_THE_WORLD: 'Settings_TeachersUnite', APP_DOWNLOAD_LINKS: 'Settings_App_Download_Links', ADD_DEBIT_CARD: 'Settings_Add_Debit_Card', ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account', + ADD_BANK_ACCOUNT_REFACTOR: 'Settings_Add_Bank_Account_Refactor', CLOSE: 'Settings_Close', TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth', REPORT_CARD_LOST_OR_DAMAGED: 'Settings_ReportCardLostOrDamaged', @@ -126,6 +128,9 @@ const SCREENS = { REFERRAL: 'Referral', PROCESS_MONEY_REQUEST_HOLD: 'ProcessMoneyRequestHold', }, + ONBOARDING_MODAL: { + ONBOARDING: 'Onboarding', + }, SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop', SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', DESKTOP_SIGN_IN_REDIRECT: 'DesktopSignInRedirect', @@ -241,7 +246,6 @@ const SCREENS = { CATEGORIES_SETTINGS: 'Categories_Settings', MORE_FEATURES: 'Workspace_More_Features', MEMBER_DETAILS: 'Workspace_Member_Details', - MEMBER_DETAILS_ROLE_SELECTION: 'Workspace_Member_Details_Role_Selection', OWNER_CHANGE_CHECK: 'Workspace_Owner_Change_Check', OWNER_CHANGE_SUCCESS: 'Workspace_Owner_Change_Success', OWNER_CHANGE_ERROR: 'Workspace_Owner_Change_Error', @@ -271,12 +275,21 @@ const SCREENS = { EDIT_CURRENCY: 'SplitDetails_Edit_Currency', }, + ONBOARDING: { + PERSONAL_DETAILS: 'Onboarding_Personal_Details', + PURPOSE: 'Onboarding_Purpose', + }, + ONBOARD_ENGAGEMENT: { ROOT: 'Onboard_Engagement_Root', MANAGE_TEAMS_EXPENSES: 'Manage_Teams_Expenses', EXPENSIFY_CLASSIC: 'Expenisfy_Classic', }, + WELCOME_VIDEO: { + ROOT: 'Welcome_Video_Root', + }, + I_KNOW_A_TEACHER: 'I_Know_A_Teacher', INTRO_SCHOOL_PRINCIPAL: 'Intro_School_Principal', I_AM_A_TEACHER: 'I_Am_A_Teacher', diff --git a/src/components/AddPlaidBankAccount.tsx b/src/components/AddPlaidBankAccount.tsx index 4111d9cc8e6f..366f14ec9780 100644 --- a/src/components/AddPlaidBankAccount.tsx +++ b/src/components/AddPlaidBankAccount.tsx @@ -62,6 +62,9 @@ type AddPlaidBankAccountProps = AddPlaidBankAccountOnyxProps & { /** Is displayed in new VBBA */ isDisplayedInNewVBBA?: boolean; + /** Is displayed in new enable wallet flow */ + isDisplayedInWalletFlow?: boolean; + /** Text to display on error message */ errorText?: string; @@ -84,6 +87,7 @@ function AddPlaidBankAccount({ isDisplayedInNewVBBA = false, errorText = '', onInputChange = () => {}, + isDisplayedInWalletFlow = false, }: AddPlaidBankAccountProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -255,10 +259,10 @@ function AddPlaidBankAccount({ return {renderPlaidLink()}; } - if (isDisplayedInNewVBBA) { + if (isDisplayedInNewVBBA || isDisplayedInWalletFlow) { return ( - {translate('bankAccount.chooseAnAccount')} + {translate(isDisplayedInWalletFlow ? 'walletPage.chooseYourBankAccount' : 'bankAccount.chooseAnAccount')} {!!text && {text}} - + +