From 68a36ae62a725eb4fc5ae4687397b297c5f3fe36 Mon Sep 17 00:00:00 2001 From: Pierre Michel Date: Fri, 21 Jul 2023 15:52:39 -0600 Subject: [PATCH 001/449] Add longPress support for TaskPreview Signed-off-by: Pierre Michel --- src/components/ReportActionItem/TaskPreview.js | 17 +++++++++++++++++ src/pages/home/report/ReportActionItem.js | 3 +++ 2 files changed, 20 insertions(+) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 472c71298852..7f18a65e420e 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -22,6 +22,10 @@ import * as ReportUtils from '../../libs/ReportUtils'; import RenderHTML from '../RenderHTML'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import personalDetailsPropType from '../../pages/personalDetailsPropType'; +import {showContextMenuForReport} from '../ShowContextMenuContext'; +import reportPropTypes from '../../pages/reportPropTypes'; +import refPropTypes from '../refPropTypes'; +import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; const propTypes = { /** All personal details asssociated with user */ @@ -49,6 +53,16 @@ const propTypes = { ownerAccountID: PropTypes.number, }), + /* Onyx Props */ + /** chatReport associated with taskReport */ + chatReport: reportPropTypes, + + /** Popover context menu anchor, used for showing context menu */ + contextMenuAnchor: refPropTypes, + + /** Callback for updating context menu active state, used for showing context menu */ + checkIfContextMenuActive: PropTypes.func, + ...withLocalizePropTypes, }; @@ -74,6 +88,9 @@ function TaskPreview(props) { Navigation.navigate(ROUTES.getReportRoute(props.taskReportID))} + onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => ControlSelection.unblock()} + onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)} style={[styles.flexRow, styles.justifyContentBetween]} accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={props.translate('task.task')} diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 0b67a728ba0e..b15e92e29b62 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -281,8 +281,11 @@ function ReportActionItem(props) { children = ( ); } else { From 0efba40908b3ea408f222edee2d6e9b23d855387 Mon Sep 17 00:00:00 2001 From: Pierre Michel Date: Fri, 21 Jul 2023 16:27:14 -0600 Subject: [PATCH 002/449] Add ControlSelection to imports Signed-off-by: Pierre Michel --- src/components/ReportActionItem/TaskPreview.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 7f18a65e420e..9d66d16b9b54 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -26,6 +26,7 @@ import {showContextMenuForReport} from '../ShowContextMenuContext'; import reportPropTypes from '../../pages/reportPropTypes'; import refPropTypes from '../refPropTypes'; import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; +import ControlSelection from '../../libs/ControlSelection'; const propTypes = { /** All personal details asssociated with user */ From 68b2444a762ab9bf8a604a6c5d34891225f51c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Tue, 19 Sep 2023 17:41:02 +0200 Subject: [PATCH 003/449] migrate SelectionScraper --- .../{index.native.js => index.native.ts} | 0 .../SelectionScraper/{index.js => index.ts} | 71 ++++++++++--------- 2 files changed, 37 insertions(+), 34 deletions(-) rename src/libs/SelectionScraper/{index.native.js => index.native.ts} (100%) rename src/libs/SelectionScraper/{index.js => index.ts} (72%) diff --git a/src/libs/SelectionScraper/index.native.js b/src/libs/SelectionScraper/index.native.ts similarity index 100% rename from src/libs/SelectionScraper/index.native.js rename to src/libs/SelectionScraper/index.native.ts diff --git a/src/libs/SelectionScraper/index.js b/src/libs/SelectionScraper/index.ts similarity index 72% rename from src/libs/SelectionScraper/index.js rename to src/libs/SelectionScraper/index.ts index 44b87deba796..79639cd0f28a 100644 --- a/src/libs/SelectionScraper/index.js +++ b/src/libs/SelectionScraper/index.ts @@ -1,7 +1,7 @@ import render from 'dom-serializer'; import {parseDocument} from 'htmlparser2'; -import _ from 'underscore'; import Str from 'expensify-common/lib/str'; +import {DataNode, Element, Node} from 'domhandler'; import CONST from '../../CONST'; const elementsWillBeSkipped = ['html', 'body']; @@ -9,17 +9,17 @@ const tagAttribute = 'data-testid'; /** * Reads html of selection. If browser doesn't support Selection API, returns empty string. - * @returns {String} HTML of selection as String */ -const getHTMLOfSelection = () => { +const getHTMLOfSelection = (): string => { // If browser doesn't support Selection API, return an empty string. - if (!window.getSelection) { + const selection = window.getSelection(); + + if (!selection || !window.getSelection) { return ''; } - const selection = window.getSelection(); if (selection.rangeCount <= 0) { - return window.getSelection().toString(); + return window.getSelection()?.toString() ?? ''; } const div = document.createElement('div'); @@ -64,17 +64,17 @@ const getHTMLOfSelection = () => { // and finally commonAncestorContainer.parentNode.closest('data-testid') is targeted dom. if (range.commonAncestorContainer instanceof HTMLElement) { parent = range.commonAncestorContainer.closest(`[${tagAttribute}]`); - } else { - parent = range.commonAncestorContainer.parentNode.closest(`[${tagAttribute}]`); + } else if (range.commonAncestorContainer.parentNode) { + parent = (range.commonAncestorContainer.parentNode as HTMLElement).closest(`[${tagAttribute}]`); } // Keep traversing up to clone all parents with 'data-testid' attribute. while (parent) { const cloned = parent.cloneNode(); cloned.appendChild(child); - child = cloned; + child = cloned as DocumentFragment; - parent = parent.parentNode.closest(`[${tagAttribute}]`); + parent = (parent.parentNode as HTMLElement).closest(`[${tagAttribute}]`); } div.appendChild(child); @@ -96,58 +96,61 @@ const getHTMLOfSelection = () => { /** * Clears all attributes from dom elements - * @param {Object} dom htmlparser2 dom representation - * @param {Boolean} isChildOfEditorElement - * @returns {Object} htmlparser2 dom representation */ -const replaceNodes = (dom, isChildOfEditorElement) => { - let domName = dom.name; - let domChildren; - const domAttribs = {}; - let data; +const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { + const domElement = dom as Element; + let domName = domElement.name; + let domChildren: Node[] = []; + const domAttribs = {} as Element['attribs']; + let data = ''; // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. - if (dom.type === 'text') { - data = Str.htmlEncode(dom.data); + if (dom.type.toString() === 'text') { + data = Str.htmlEncode((dom as DataNode).data); } // We are skipping elements which has html and body in data-testid, since ExpensiMark can't parse it. Also this data // has no meaning for us. - if (dom.attribs && dom.attribs[tagAttribute]) { - if (!elementsWillBeSkipped.includes(dom.attribs[tagAttribute])) { - domName = dom.attribs[tagAttribute]; + if (domElement.attribs?.[tagAttribute]) { + if (!elementsWillBeSkipped.includes(domElement.attribs[tagAttribute])) { + domName = domElement.attribs[tagAttribute]; } - } else if (dom.name === 'div' && dom.children.length === 1 && isChildOfEditorElement) { + } else if (domElement.name === 'div' && domElement.children.length === 1 && isChildOfEditorElement) { // We are excluding divs that are children of our editor element and have only one child to prevent // additional newlines from being added in the HTML to Markdown conversion process. - return replaceNodes(dom.children[0], isChildOfEditorElement); + return replaceNodes(domElement.children[0], isChildOfEditorElement); } // We need to preserve href attribute in order to copy links. - if (dom.attribs && dom.attribs.href) { - domAttribs.href = dom.attribs.href; + if (domElement.attribs?.href) { + domAttribs.href = domElement.attribs.href; + } + + if (domElement.children) { + domChildren = domElement.children.map((c) => replaceNodes(c, isChildOfEditorElement || !!domElement.attribs?.[tagAttribute])); } - if (dom.children) { - domChildren = _.map(dom.children, (c) => replaceNodes(c, isChildOfEditorElement || !_.isEmpty(dom.attribs && dom.attribs[tagAttribute]))); + if (data) { + return { + ...dom, + data, + } as DataNode; } return { ...dom, - data, name: domName, attribs: domAttribs, children: domChildren, - }; + } as Element; }; /** * Resolves the current selection to values and produces clean HTML. - * @returns {String} resolved selection in the HTML format */ -const getCurrentSelection = () => { +const getCurrentSelection = (): string => { const domRepresentation = parseDocument(getHTMLOfSelection()); - domRepresentation.children = _.map(domRepresentation.children, replaceNodes); + domRepresentation.children = domRepresentation.children.map((item) => replaceNodes(item, false)); // Newline characters need to be removed here because the HTML could contain both newlines and
tags, and when //
tags are converted later to markdown, it creates duplicate newline characters. This means that when the content From 43adfe9e58fd0c7e78ec5edaef30be0d9ab8d01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Thu, 21 Sep 2023 15:06:18 +0200 Subject: [PATCH 004/449] review changes --- src/libs/SelectionScraper/index.native.ts | 8 ++++++-- src/libs/SelectionScraper/index.ts | 7 ++++--- src/libs/SelectionScraper/types.ts | 3 +++ 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 src/libs/SelectionScraper/types.ts diff --git a/src/libs/SelectionScraper/index.native.ts b/src/libs/SelectionScraper/index.native.ts index 3872ece30b66..7712906f05e6 100644 --- a/src/libs/SelectionScraper/index.native.ts +++ b/src/libs/SelectionScraper/index.native.ts @@ -1,4 +1,8 @@ +import GetCurrentSelection from './types'; + +// This is a no-op function for native devices because they wouldn't be able to support Selection API like a website. +const getCurrentSelection: GetCurrentSelection = () => ''; + export default { - // This is a no-op function for native devices because they wouldn't be able to support Selection API like a website. - getCurrentSelection: () => '', + getCurrentSelection, }; diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index 79639cd0f28a..6660d5a394cb 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -3,6 +3,7 @@ import {parseDocument} from 'htmlparser2'; import Str from 'expensify-common/lib/str'; import {DataNode, Element, Node} from 'domhandler'; import CONST from '../../CONST'; +import GetCurrentSelection from './types'; const elementsWillBeSkipped = ['html', 'body']; const tagAttribute = 'data-testid'; @@ -44,7 +45,7 @@ const getHTMLOfSelection = (): string => { // If clonedSelection has no text content this data has no meaning to us. if (clonedSelection.textContent) { - let parent; + let parent: globalThis.Element | null = null; let child = clonedSelection; // If selection starts and ends within same text node we use its parentNode. This is because we can't @@ -101,7 +102,7 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { const domElement = dom as Element; let domName = domElement.name; let domChildren: Node[] = []; - const domAttribs = {} as Element['attribs']; + const domAttribs: Element['attribs'] = {}; let data = ''; // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. @@ -148,7 +149,7 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { /** * Resolves the current selection to values and produces clean HTML. */ -const getCurrentSelection = (): string => { +const getCurrentSelection: GetCurrentSelection = () => { const domRepresentation = parseDocument(getHTMLOfSelection()); domRepresentation.children = domRepresentation.children.map((item) => replaceNodes(item, false)); diff --git a/src/libs/SelectionScraper/types.ts b/src/libs/SelectionScraper/types.ts new file mode 100644 index 000000000000..d33338883dd4 --- /dev/null +++ b/src/libs/SelectionScraper/types.ts @@ -0,0 +1,3 @@ +type GetCurrentSelection = () => string; + +export default GetCurrentSelection; From 6841a5407f3303696ebde4c00ea920215ca99109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 22 Sep 2023 11:40:35 +0200 Subject: [PATCH 005/449] add return comment --- src/libs/SelectionScraper/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index 6660d5a394cb..52fe69fcb75e 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -10,6 +10,7 @@ const tagAttribute = 'data-testid'; /** * Reads html of selection. If browser doesn't support Selection API, returns empty string. + * @returns HTML of selection as String */ const getHTMLOfSelection = (): string => { // If browser doesn't support Selection API, return an empty string. From ceaaf3359fdeb3569bf652059dc50f7a9a63ccc4 Mon Sep 17 00:00:00 2001 From: April Bekkala Date: Wed, 27 Sep 2023 22:33:14 -0500 Subject: [PATCH 006/449] Create Copilot Adding the Copilot page info --- .../account-settings/Copilot | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/articles/expensify-classic/account-settings/Copilot diff --git a/docs/articles/expensify-classic/account-settings/Copilot b/docs/articles/expensify-classic/account-settings/Copilot new file mode 100644 index 000000000000..dbd26af12d88 --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Copilot @@ -0,0 +1,78 @@ +--- +title: Copilot +description: Safely delegate tasks without sharing login information. +--- + + +# About + + +# How-to + + +# Deep Dive + + +# FAQ + From decc2982e22af9f257c2798a73b06ffb2a8e8f04 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 28 Sep 2023 12:16:41 +0800 Subject: [PATCH 007/449] run webpack dev on https --- config/webpack/webpack.dev.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index 19999491395e..c8af82d1d855 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -1,8 +1,8 @@ const path = require('path'); const portfinder = require('portfinder'); -const {DefinePlugin} = require('webpack'); -const {merge} = require('webpack-merge'); -const {TimeAnalyticsPlugin} = require('time-analytics-webpack-plugin'); +const { DefinePlugin } = require('webpack'); +const { merge } = require('webpack-merge'); +const { TimeAnalyticsPlugin } = require('time-analytics-webpack-plugin'); const getCommonConfig = require('./webpack.common'); const BASE_PORT = 8082; @@ -13,20 +13,20 @@ const BASE_PORT = 8082; * @returns {Configuration} */ module.exports = (env = {}) => - portfinder.getPortPromise({port: BASE_PORT}).then((port) => { + portfinder.getPortPromise({ port: BASE_PORT }).then((port) => { // Check if the USE_WEB_PROXY variable has been provided // and rewrite any requests to the local proxy server const proxySettings = process.env.USE_WEB_PROXY === 'false' ? {} : { - proxy: { - '/api': 'http://[::1]:9000', - '/staging': 'http://[::1]:9000', - '/chat-attachments': 'http://[::1]:9000', - '/receipts': 'http://[::1]:9000', - }, - }; + proxy: { + '/api': 'http://[::1]:9000', + '/staging': 'http://[::1]:9000', + '/chat-attachments': 'http://[::1]:9000', + '/receipts': 'http://[::1]:9000', + }, + }; const baseConfig = getCommonConfig(env); @@ -44,6 +44,7 @@ module.exports = (env = {}) => ...proxySettings, historyApiFallback: true, port, + https: true, }, plugins: [ new DefinePlugin({ From b0f3f2371e557b73ce96b51488410ff5300014df Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 10:38:38 +0800 Subject: [PATCH 008/449] add task to generate certificates --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d013caa1c402..f7f329363145 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "test:e2e": "node tests/e2e/testRunner.js --development", "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", - "workflow-test:generate": "node workflow_tests/utils/preGenerateTest.js" + "workflow-test:generate": "node workflow_tests/utils/preGenerateTest.js", + "setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem new.expensify.com.dev localhost 127.0.0.1" }, "dependencies": { "@expensify/react-native-web": "0.18.15", From 325213a4d9b48095682c3c6b30d7a7fbee39910f Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 10:39:11 +0800 Subject: [PATCH 009/449] use generated certs in webpack dev --- config/webpack/webpack.dev.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index c8af82d1d855..5c705257ab2a 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -44,7 +44,10 @@ module.exports = (env = {}) => ...proxySettings, historyApiFallback: true, port, - https: true, + https: { + key: path.join(__dirname, 'key.pem'), + cert: path.join(__dirname, 'certificate.pem') + } }, plugins: [ new DefinePlugin({ From c46d0fa0d3ed1ff08b762416be6d09659e612e91 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 10:39:59 +0800 Subject: [PATCH 010/449] update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d3b4daac04d7..335efdc5586a 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,6 @@ tsconfig.tsbuildinfo # Yalc .yalc yalc.lock + +# Local https certificate/key +config/webpack/*.pem From 0140258f2a40fe513c40d79abb644f39ed9aff5b Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 12:07:45 +0800 Subject: [PATCH 011/449] add mkcert instructions --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index fce7cee8dcdd..537f0d0101ad 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,18 @@ These instructions should get you set up ready to work on New Expensify 🙌 1. Install `nvm` then `node` & `npm`: `brew install nvm && nvm install` 2. Install `watchman`: `brew install watchman` 3. Install dependencies: `npm install` +4. Install `mkcert`: `brew install mkcert` followed by `npm run setup-https`. If you're using another OS, follow the instructions [here](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#installation). You can use any IDE or code editing tool for developing on any platform. Use your favorite! ## Recommended `node` setup In order to have more consistent builds, we use a strict `node` and `npm` version as defined in the `package.json` `engines` field and `.nvmrc` file. `npm install` will fail if you do not use the version defined, so it is recommended to install `node` via `nvm` for easy node version management. Automatic `node` version switching can be installed for [`zsh`](https://github.com/nvm-sh/nvm#zsh) or [`bash`](https://github.com/nvm-sh/nvm#bash) using `nvm`. +## Configuring HTTPS +The webpack development server now uses https. If you're using a mac, you can simply run `npm run setup-https`. + +If you're using another operating system, you will need to ensure `mkcert` is installed, and then follow the instructions in the repository to generate certificates valid for `new.expesify.com.dev` and `localhost`. The certificate should be named `certificate.pem` and the key should be named `key.pem`. They should be placed in `config/webpack`. + ## Running the web app 🕸 * To run the **development web app**: `npm run web` * Changes applied to Javascript will be applied automatically via WebPack as configured in `webpack.dev.js` From 34b573291053ef606ff1e346fb825518190d2d34 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 12:08:02 +0800 Subject: [PATCH 012/449] prettier --- config/webpack/webpack.dev.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index 5c705257ab2a..ac7696293b76 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -1,8 +1,8 @@ const path = require('path'); const portfinder = require('portfinder'); -const { DefinePlugin } = require('webpack'); -const { merge } = require('webpack-merge'); -const { TimeAnalyticsPlugin } = require('time-analytics-webpack-plugin'); +const {DefinePlugin} = require('webpack'); +const {merge} = require('webpack-merge'); +const {TimeAnalyticsPlugin} = require('time-analytics-webpack-plugin'); const getCommonConfig = require('./webpack.common'); const BASE_PORT = 8082; @@ -13,20 +13,20 @@ const BASE_PORT = 8082; * @returns {Configuration} */ module.exports = (env = {}) => - portfinder.getPortPromise({ port: BASE_PORT }).then((port) => { + portfinder.getPortPromise({port: BASE_PORT}).then((port) => { // Check if the USE_WEB_PROXY variable has been provided // and rewrite any requests to the local proxy server const proxySettings = process.env.USE_WEB_PROXY === 'false' ? {} : { - proxy: { - '/api': 'http://[::1]:9000', - '/staging': 'http://[::1]:9000', - '/chat-attachments': 'http://[::1]:9000', - '/receipts': 'http://[::1]:9000', - }, - }; + proxy: { + '/api': 'http://[::1]:9000', + '/staging': 'http://[::1]:9000', + '/chat-attachments': 'http://[::1]:9000', + '/receipts': 'http://[::1]:9000', + }, + }; const baseConfig = getCommonConfig(env); @@ -46,8 +46,8 @@ module.exports = (env = {}) => port, https: { key: path.join(__dirname, 'key.pem'), - cert: path.join(__dirname, 'certificate.pem') - } + cert: path.join(__dirname, 'certificate.pem'), + }, }, plugins: [ new DefinePlugin({ From 2d80cbd1ea5044a0e2a54334a13a7d51fa69bb68 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 16:22:42 +0800 Subject: [PATCH 013/449] change host to new.expensify.com.dev --- README.md | 4 ++++ config/webpack/webpack.dev.js | 1 + 2 files changed, 5 insertions(+) diff --git a/README.md b/README.md index 537f0d0101ad..6b5f96de6b72 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ These instructions should get you set up ready to work on New Expensify 🙌 2. Install `watchman`: `brew install watchman` 3. Install dependencies: `npm install` 4. Install `mkcert`: `brew install mkcert` followed by `npm run setup-https`. If you're using another OS, follow the instructions [here](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#installation). +5. Create a host entry in your local hosts file, `/etc/hosts` for new.expensify.com.dev pointing to localhost: +``` +127.0.0.1 new.expensify.com.dev +``` You can use any IDE or code editing tool for developing on any platform. Use your favorite! diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index ac7696293b76..1e7074b16277 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -44,6 +44,7 @@ module.exports = (env = {}) => ...proxySettings, historyApiFallback: true, port, + host: 'new.expensify.com.dev', https: { key: path.join(__dirname, 'key.pem'), cert: path.join(__dirname, 'certificate.pem'), From d7b9c815938dbdf97302e80895da2e6210b92d77 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 29 Sep 2023 11:39:56 +0200 Subject: [PATCH 014/449] Migrate environment lib to TS --- .../{Environment.js => Environment.ts} | 25 ++++++------------- .../{index.android.js => index.android.ts} | 11 +++++--- .../{index.ios.js => index.ios.ts} | 6 ++--- .../betaChecker/{index.js => index.ts} | 4 +-- .../{index.native.js => index.native.ts} | 11 +++----- .../getEnvironment/{index.js => index.ts} | 7 ++---- 6 files changed, 24 insertions(+), 40 deletions(-) rename src/libs/Environment/{Environment.js => Environment.ts} (69%) rename src/libs/Environment/betaChecker/{index.android.js => index.android.ts} (89%) rename src/libs/Environment/betaChecker/{index.ios.js => index.ios.ts} (67%) rename src/libs/Environment/betaChecker/{index.js => index.ts} (70%) rename src/libs/Environment/getEnvironment/{index.native.js => index.native.ts} (75%) rename src/libs/Environment/getEnvironment/{index.js => index.ts} (53%) diff --git a/src/libs/Environment/Environment.js b/src/libs/Environment/Environment.ts similarity index 69% rename from src/libs/Environment/Environment.js rename to src/libs/Environment/Environment.ts index c039b49d33aa..f14e21eac90d 100644 --- a/src/libs/Environment/Environment.js +++ b/src/libs/Environment/Environment.ts @@ -1,5 +1,4 @@ import Config from 'react-native-config'; -import lodashGet from 'lodash/get'; import CONST from '../../CONST'; import getEnvironment from './getEnvironment'; import CONFIG from '../../CONFIG'; @@ -20,40 +19,32 @@ const OLDDOT_ENVIRONMENT_URLS = { /** * Are we running the app in development? - * - * @return {boolean} */ -function isDevelopment() { - return lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.DEV; +function isDevelopment(): boolean { + return (Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.DEV; } /** * Are we running an internal test build? - * - * @return {boolean} */ -function isInternalTestBuild() { - return lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.ADHOC && lodashGet(Config, 'PULL_REQUEST_NUMBER', ''); +function isInternalTestBuild(): boolean { + return !!((Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.ADHOC && (Config?.PULL_REQUEST_NUMBER ?? '')); } /** * Get the URL based on the environment we are in - * - * @returns {Promise} */ -function getEnvironmentURL() { +function getEnvironmentURL(): Promise { return new Promise((resolve) => { - getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment])); + getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment as keyof typeof ENVIRONMENT_URLS])); }); } /** * Get the corresponding oldDot URL based on the environment we are in - * - * @returns {Promise} */ -function getOldDotEnvironmentURL() { - return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment]); +function getOldDotEnvironmentURL(): Promise { + return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment as keyof typeof OLDDOT_ENVIRONMENT_URLS]); } export {getEnvironment, isInternalTestBuild, isDevelopment, getEnvironmentURL, getOldDotEnvironmentURL}; diff --git a/src/libs/Environment/betaChecker/index.android.js b/src/libs/Environment/betaChecker/index.android.ts similarity index 89% rename from src/libs/Environment/betaChecker/index.android.js rename to src/libs/Environment/betaChecker/index.android.ts index e74648973c34..8b3afed3b2f3 100644 --- a/src/libs/Environment/betaChecker/index.android.js +++ b/src/libs/Environment/betaChecker/index.android.ts @@ -8,15 +8,18 @@ import * as AppUpdate from '../../actions/AppUpdate'; let isLastSavedBeta = false; Onyx.connect({ key: ONYXKEYS.IS_BETA, - callback: (value) => (isLastSavedBeta = value), + callback: (value) => { + if (!value) { + return; + } + isLastSavedBeta = value; + }, }); /** * Check the GitHub releases to see if the current build is a beta build or production build - * - * @returns {Promise} */ -function isBetaBuild() { +function isBetaBuild(): Promise { return new Promise((resolve) => { fetch(CONST.GITHUB_RELEASE_URL) .then((res) => res.json()) diff --git a/src/libs/Environment/betaChecker/index.ios.js b/src/libs/Environment/betaChecker/index.ios.ts similarity index 67% rename from src/libs/Environment/betaChecker/index.ios.js rename to src/libs/Environment/betaChecker/index.ios.ts index 65b3ea935b04..2d6079e30a1c 100644 --- a/src/libs/Environment/betaChecker/index.ios.js +++ b/src/libs/Environment/betaChecker/index.ios.ts @@ -2,12 +2,10 @@ import {NativeModules} from 'react-native'; /** * Check to see if the build is staging (TestFlight) or production - * - * @returns {Promise} */ -function isBetaBuild() { +function isBetaBuild(): Promise { return new Promise((resolve) => { - NativeModules.EnvironmentChecker.isBeta().then((isBeta) => { + NativeModules.EnvironmentChecker.isBeta().then((isBeta: boolean) => { resolve(isBeta); }); }); diff --git a/src/libs/Environment/betaChecker/index.js b/src/libs/Environment/betaChecker/index.ts similarity index 70% rename from src/libs/Environment/betaChecker/index.js rename to src/libs/Environment/betaChecker/index.ts index 9d0d4af5741b..cda7c297624f 100644 --- a/src/libs/Environment/betaChecker/index.js +++ b/src/libs/Environment/betaChecker/index.ts @@ -1,9 +1,7 @@ /** * There's no beta build in non native - * - * @returns {Promise} */ -function isBetaBuild() { +function isBetaBuild(): Promise { return Promise.resolve(false); } diff --git a/src/libs/Environment/getEnvironment/index.native.js b/src/libs/Environment/getEnvironment/index.native.ts similarity index 75% rename from src/libs/Environment/getEnvironment/index.native.js rename to src/libs/Environment/getEnvironment/index.native.ts index 73014c4beffb..1e3333c6a881 100644 --- a/src/libs/Environment/getEnvironment/index.native.js +++ b/src/libs/Environment/getEnvironment/index.native.ts @@ -1,28 +1,25 @@ -import lodashGet from 'lodash/get'; import Config from 'react-native-config'; import betaChecker from '../betaChecker'; import CONST from '../../../CONST'; -let environment = null; +let environment: string | null = null; /** * Returns a promise that resolves with the current environment string value - * - * @returns {Promise} */ -function getEnvironment() { +function getEnvironment(): Promise { return new Promise((resolve) => { // If we've already set the environment, use the current value if (environment) { return resolve(environment); } - if (lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.DEV) { + if ((Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.DEV) { environment = CONST.ENVIRONMENT.DEV; return resolve(environment); } - if (lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.ADHOC) { + if ((Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.ADHOC) { environment = CONST.ENVIRONMENT.ADHOC; return resolve(environment); } diff --git a/src/libs/Environment/getEnvironment/index.js b/src/libs/Environment/getEnvironment/index.ts similarity index 53% rename from src/libs/Environment/getEnvironment/index.js rename to src/libs/Environment/getEnvironment/index.ts index a987678d6a6e..2c27be5fc471 100644 --- a/src/libs/Environment/getEnvironment/index.js +++ b/src/libs/Environment/getEnvironment/index.ts @@ -1,14 +1,11 @@ -import lodashGet from 'lodash/get'; import Config from 'react-native-config'; import CONST from '../../../CONST'; /** * Returns a promise that resolves with the current environment string value - * - * @returns {Promise} */ -function getEnvironment() { - return Promise.resolve(lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV)); +function getEnvironment(): Promise { + return Promise.resolve(Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV); } export default getEnvironment; From a27b365ec21320343fd4a9921e17ebcf543f9d29 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 2 Oct 2023 09:52:48 +0700 Subject: [PATCH 015/449] fix clicking back button bring back the workspace --- src/libs/actions/App.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index b8be35aa1919..a493dd9b74d9 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -325,9 +325,11 @@ function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = fal } if (shouldNavigateToAdminChat) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); + Navigation.dismissModal(adminsChatReportID); } - + }) + .then(() => Navigation.isNavigationReady()) + .then(() => { Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); }) .then(endSignOnTransition); From a803a71b9ecd22bb274020655a72bde5a76012d2 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 2 Oct 2023 18:55:58 +0700 Subject: [PATCH 016/449] fix refactor logic then --- src/libs/actions/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index a493dd9b74d9..5c9a0c2a8628 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -327,8 +327,8 @@ function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = fal if (shouldNavigateToAdminChat) { Navigation.dismissModal(adminsChatReportID); } + return Navigation.isNavigationReady(); }) - .then(() => Navigation.isNavigationReady()) .then(() => { Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); }) From 59a32831104a465c7386dfc1d5090af93f61570a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Mon, 2 Oct 2023 16:24:36 +0200 Subject: [PATCH 017/449] review changes --- src/libs/SelectionScraper/index.ts | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index 52fe69fcb75e..4b679734b3a0 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -14,9 +14,11 @@ const tagAttribute = 'data-testid'; */ const getHTMLOfSelection = (): string => { // If browser doesn't support Selection API, return an empty string. + if (!window.getSelection) { + return ''; + } const selection = window.getSelection(); - - if (!selection || !window.getSelection) { + if (!selection) { return ''; } @@ -66,8 +68,8 @@ const getHTMLOfSelection = (): string => { // and finally commonAncestorContainer.parentNode.closest('data-testid') is targeted dom. if (range.commonAncestorContainer instanceof HTMLElement) { parent = range.commonAncestorContainer.closest(`[${tagAttribute}]`); - } else if (range.commonAncestorContainer.parentNode) { - parent = (range.commonAncestorContainer.parentNode as HTMLElement).closest(`[${tagAttribute}]`); + } else { + parent = (range.commonAncestorContainer.parentNode as HTMLElement | null)?.closest(`[${tagAttribute}]`) ?? null; } // Keep traversing up to clone all parents with 'data-testid' attribute. @@ -76,7 +78,7 @@ const getHTMLOfSelection = (): string => { cloned.appendChild(child); child = cloned as DocumentFragment; - parent = (parent.parentNode as HTMLElement).closest(`[${tagAttribute}]`); + parent = (parent.parentNode as HTMLElement | null)?.closest(`[${tagAttribute}]`) ?? null; } div.appendChild(child); @@ -100,16 +102,21 @@ const getHTMLOfSelection = (): string => { * Clears all attributes from dom elements */ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { + // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. + const domDataNode = dom as DataNode; + let data = ''; + if (dom.type.toString() === 'text' && domDataNode.data) { + data = Str.htmlEncode(domDataNode.data); + return { + ...dom, + data, + } as DataNode; + } + const domElement = dom as Element; let domName = domElement.name; let domChildren: Node[] = []; const domAttribs: Element['attribs'] = {}; - let data = ''; - - // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. - if (dom.type.toString() === 'text') { - data = Str.htmlEncode((dom as DataNode).data); - } // We are skipping elements which has html and body in data-testid, since ExpensiMark can't parse it. Also this data // has no meaning for us. @@ -132,13 +139,6 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { domChildren = domElement.children.map((c) => replaceNodes(c, isChildOfEditorElement || !!domElement.attribs?.[tagAttribute])); } - if (data) { - return { - ...dom, - data, - } as DataNode; - } - return { ...dom, name: domName, From 4fe4ff020c80b21bcca6b00131fd2e553fb55075 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Tue, 3 Oct 2023 00:17:04 +0700 Subject: [PATCH 018/449] fix handle case create workspace without openning modal --- src/libs/actions/App.js | 17 +++++++++++++++-- .../FloatingActionButtonAndPopover.js | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 5c9a0c2a8628..fbea58b17efe 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -313,8 +313,17 @@ function endSignOnTransition() { * @param {String} [policyName] Optional, custom policy name we will use for created workspace * @param {Boolean} [transitionFromOldDot] Optional, if the user is transitioning from old dot * @param {Boolean} [shouldNavigateToAdminChat] Optional, navigate to the #admin room after creation + * @param {Boolean} [isThereModalToDismiss] Optional, if there is a modal to dismiss + */ -function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', transitionFromOldDot = false, shouldNavigateToAdminChat = true) { +function createWorkspaceAndNavigateToIt( + policyOwnerEmail = '', + makeMeAdmin = false, + policyName = '', + transitionFromOldDot = false, + shouldNavigateToAdminChat = true, + isThereModalToDismiss = true, +) { const policyID = Policy.generatePolicyID(); const adminsChatReportID = Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID); Navigation.isNavigationReady() @@ -325,7 +334,11 @@ function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = fal } if (shouldNavigateToAdminChat) { - Navigation.dismissModal(adminsChatReportID); + if (isThereModalToDismiss) { + Navigation.dismissModal(adminsChatReportID); + } else { + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); + } } return Navigation.isNavigationReady(); }) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index e9ede2c9a89a..bf703715848f 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -216,7 +216,7 @@ function FloatingActionButtonAndPopover(props) { iconHeight: 40, text: props.translate('workspace.new.newWorkspace'), description: props.translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => interceptAnonymousUser(() => App.createWorkspaceAndNavigateToIt('', false, '', false, !props.isSmallScreenWidth)), + onSelected: () => interceptAnonymousUser(() => App.createWorkspaceAndNavigateToIt('', false, '', false, !props.isSmallScreenWidth, false)), }, ] : []), From 23338eb4067c5420f6960657127223af6eed907b Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 3 Oct 2023 00:42:39 +0700 Subject: [PATCH 019/449] Fix receipt image is opened in transaction thread report --- src/components/Attachments/AttachmentCarousel/index.js | 7 +++++-- .../Attachments/AttachmentCarousel/index.native.js | 6 ++++-- .../ReportActionCompose/AttachmentPickerWithMenuItems.js | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.js b/src/components/Attachments/AttachmentCarousel/index.js index 00b603cdd7d9..df6c812fde4e 100644 --- a/src/components/Attachments/AttachmentCarousel/index.js +++ b/src/components/Attachments/AttachmentCarousel/index.js @@ -37,17 +37,18 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, setDownl const [attachments, setAttachments] = useState([]); const [activeSource, setActiveSource] = useState(source); const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); + const [isReceipt, setIsReceipt] = useState(false); const compareImage = useCallback( (attachment) => { - if (attachment.isReceipt) { + if (attachment.isReceipt && isReceipt) { const action = ReportActionsUtils.getParentReportAction(report); const transactionID = _.get(action, ['originalMessage', 'IOUTransactionID']); return attachment.transactionID === transactionID; } return attachment.source === source; }, - [source, report], + [source, report, isReceipt], ); useEffect(() => { @@ -86,10 +87,12 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, setDownl // to get the index of the current page const entry = _.first(viewableItems); if (!entry) { + setIsReceipt(false); setActiveSource(null); return; } + setIsReceipt(entry.item.isReceipt); setPage(entry.index); setActiveSource(entry.item.source); diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js index bd12020341be..f2cdc111c666 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.js +++ b/src/components/Attachments/AttachmentCarousel/index.native.js @@ -27,17 +27,18 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, onClose, const [activeSource, setActiveSource] = useState(source); const [isPinchGestureRunning, setIsPinchGestureRunning] = useState(true); const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); + const [isReceipt, setIsReceipt] = useState(false); const compareImage = useCallback( (attachment) => { - if (attachment.isReceipt) { + if (attachment.isReceipt && isReceipt) { const action = ReportActionsUtils.getParentReportAction(report); const transactionID = _.get(action, ['originalMessage', 'IOUTransactionID']); return attachment.transactionID === transactionID; } return attachment.source === source; }, - [source, report], + [source, report, isReceipt], ); useEffect(() => { @@ -76,6 +77,7 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, onClose, const item = attachments[newPageIndex]; setPage(newPageIndex); + setIsReceipt(item.isReceipt); setActiveSource(item.source); onNavigate(item); diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 6dd3355f4a53..10f29615c904 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -243,7 +243,7 @@ function AttachmentPickerWithMenuItems({ onAddActionPressed(); // Drop focus to avoid blue focus ring. - actionButtonRef.current.blur(); + // actionButtonRef.current.blur(); setMenuVisibility(!isMenuVisible); }} style={styles.composerSizeButton} From e48bd2f7a97333dbe779d49fa5f97d24a1fea76f Mon Sep 17 00:00:00 2001 From: April Bekkala Date: Mon, 2 Oct 2023 14:13:44 -0500 Subject: [PATCH 020/449] Rename Copilot to Copilot.md needed to add .md to the title --- .../expensify-classic/account-settings/{Copilot => Copilot.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/articles/expensify-classic/account-settings/{Copilot => Copilot.md} (100%) diff --git a/docs/articles/expensify-classic/account-settings/Copilot b/docs/articles/expensify-classic/account-settings/Copilot.md similarity index 100% rename from docs/articles/expensify-classic/account-settings/Copilot rename to docs/articles/expensify-classic/account-settings/Copilot.md From 09f3a5f73b343a93282da0ab747c4db7be3a5148 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 3 Oct 2023 10:40:30 +0700 Subject: [PATCH 021/449] restore hard code --- .../report/ReportActionCompose/AttachmentPickerWithMenuItems.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 10f29615c904..6dd3355f4a53 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -243,7 +243,7 @@ function AttachmentPickerWithMenuItems({ onAddActionPressed(); // Drop focus to avoid blue focus ring. - // actionButtonRef.current.blur(); + actionButtonRef.current.blur(); setMenuVisibility(!isMenuVisible); }} style={styles.composerSizeButton} From d21ae8d45a1a5563c6e959872633a0b961cbfac7 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 3 Oct 2023 17:49:39 +0800 Subject: [PATCH 022/449] Add accessibility translation for floating action button --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 81e1c22b0ccc..7b6cfc372c48 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -458,7 +458,7 @@ export default { buttonSearch: 'Buscar', buttonMySettings: 'Mi configuración', fabNewChat: 'Enviar mensaje', - fabNewChatExplained: 'Enviar mensaje', + fabNewChatExplained: 'Enviar mensaje (Acción flotante)', chatPinned: 'Chat fijado', draftedMessage: 'Mensaje borrador', listOfChatMessages: 'Lista de mensajes del chat', From a269b40cf59459fb5cce0edf5aba6c3f89c3d122 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 3 Oct 2023 07:44:02 -0400 Subject: [PATCH 023/449] update help site to use new https dev --- docs/_includes/CONST.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_includes/CONST.html b/docs/_includes/CONST.html index 4b87f87931d5..af6a14a78331 100644 --- a/docs/_includes/CONST.html +++ b/docs/_includes/CONST.html @@ -1,7 +1,7 @@ {% if jekyll.environment == "production" %} {% assign MAIN_SITE_URL = "https://new.expensify.com" %} {% else %} - {% assign MAIN_SITE_URL = "http://localhost:8082" %} + {% assign MAIN_SITE_URL = "https://new.expensify.com.dev:8082" %} {% endif %} {% capture CONCIERGE_CHAT_URL %}{{MAIN_SITE_URL}}/concierge{% endcapture %} From 94107850a394e68f2edf933013535fcdc6f3581e Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 3 Oct 2023 07:44:21 -0400 Subject: [PATCH 024/449] add https url to nav --- src/libs/Navigation/linkingConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 533dbf51633a..1e695fa9dcee 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -4,7 +4,7 @@ import CONST from '../../CONST'; import NAVIGATORS from '../../NAVIGATORS'; export default { - prefixes: ['new-expensify://', 'https://www.expensify.cash', 'https://staging.expensify.cash', 'http://localhost', CONST.NEW_EXPENSIFY_URL, CONST.STAGING_NEW_EXPENSIFY_URL], + prefixes: ['new-expensify://', 'https://www.expensify.cash', 'https://staging.expensify.cash', 'https://new.expensify.com.dev', CONST.NEW_EXPENSIFY_URL, CONST.STAGING_NEW_EXPENSIFY_URL], config: { initialRouteName: SCREENS.HOME, screens: { From e8ebe3bc9ffa24480e5417c7c817b47dcf94ee3a Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 3 Oct 2023 07:44:34 -0400 Subject: [PATCH 025/449] change additional refs to https dev --- contributingGuides/APPLE_GOOGLE_SIGNIN.md | 4 ++-- desktop/main.js | 4 ++-- desktop/start.js | 2 +- src/CONST.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contributingGuides/APPLE_GOOGLE_SIGNIN.md b/contributingGuides/APPLE_GOOGLE_SIGNIN.md index 9032a99dfbbd..3a1feac073db 100644 --- a/contributingGuides/APPLE_GOOGLE_SIGNIN.md +++ b/contributingGuides/APPLE_GOOGLE_SIGNIN.md @@ -161,10 +161,10 @@ function beginAppleSignIn(idToken) { You can use any SSH tunneling service that allows you to configure custom subdomains so that we have a consistent address to use. We'll use ngrok in these examples, but ngrok requires a paid account for this. If you need a free option, try serveo.net. -After you've set ngrok up to be able to run on your machine (requires configuring a key with the command line tool, instructions provided by the ngrok website after you create an account), test hosting the web app on a custom subdomain. This example assumes the development web app is running at `localhost:8082`: +After you've set ngrok up to be able to run on your machine (requires configuring a key with the command line tool, instructions provided by the ngrok website after you create an account), test hosting the web app on a custom subdomain. This example assumes the development web app is running at `new.expensify.com.dev:8082`: ``` -ngrok http 8082 --host-header="localhost:8082" --subdomain=mysubdomain +ngrok http 8082 --host-header="new.expensify.com.dev:8082" --subdomain=mysubdomain ``` The `--host-header` flag is there to avoid webpack errors with header validation. In addition, add `allowedHosts: 'all'` to the dev server config in `webpack.dev.js`: diff --git a/desktop/main.js b/desktop/main.js index 5e184d529afd..4b43ff128f65 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -85,7 +85,7 @@ _.assign(console, log.functions); // until it detects that it has been upgraded to the correct version. const EXPECTED_UPDATE_VERSION_FLAG = '--expected-update-version'; -const APP_DOMAIN = __DEV__ ? `http://localhost:${port}` : 'app://-'; +const APP_DOMAIN = __DEV__ ? `https://new.expensify.com.dev:${port}` : 'app://-'; let expectedUpdateVersion; for (let i = 0; i < process.argv.length; i++) { @@ -221,7 +221,7 @@ const mainWindow = () => { let deeplinkUrl; let browserWindow; - const loadURL = __DEV__ ? (win) => win.loadURL(`http://localhost:${port}`) : serve({directory: `${__dirname}/www`}); + const loadURL = __DEV__ ? (win) => win.loadURL(`https://new.expensify.com.dev:${port}`) : serve({directory: `${__dirname}/www`}); // Prod and staging set the icon in the electron-builder config, so only update it here for dev if (__DEV__) { diff --git a/desktop/start.js b/desktop/start.js index d9ec59b71c83..a9bb5a2d588d 100644 --- a/desktop/start.js +++ b/desktop/start.js @@ -32,7 +32,7 @@ portfinder env, }, { - command: `wait-port localhost:${port} && npx electronmon ./desktop/dev.js`, + command: `wait-port new.expensify.com.dev:${port} && npx electronmon ./desktop/dev.js`, name: 'Electron', prefixColor: 'cyan.dim', env, diff --git a/src/CONST.ts b/src/CONST.ts index dbe47c6ed1a7..c8e6fcc65e20 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -448,7 +448,7 @@ const CONST = { ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/', // Use Environment.getEnvironmentURL to get the complete URL with port number - DEV_NEW_EXPENSIFY_URL: 'http://localhost:', + DEV_NEW_EXPENSIFY_URL: 'https://new.expensify.com.dev:', SIGN_IN_FORM_WIDTH: 300, From 89173ee79f3c10f48de8d5449e45b843bf2019aa Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 3 Oct 2023 20:36:35 +0200 Subject: [PATCH 026/449] Create types for environment urls keys --- src/libs/Environment/Environment.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/Environment/Environment.ts b/src/libs/Environment/Environment.ts index f14e21eac90d..b201fd5be439 100644 --- a/src/libs/Environment/Environment.ts +++ b/src/libs/Environment/Environment.ts @@ -17,6 +17,9 @@ const OLDDOT_ENVIRONMENT_URLS = { [CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_EXPENSIFY_URL, }; +type EnvironmentUrlsKeys = keyof typeof ENVIRONMENT_URLS; +type OldDotEnvironmentUrlsKeys = keyof typeof OLDDOT_ENVIRONMENT_URLS; + /** * Are we running the app in development? */ @@ -36,7 +39,7 @@ function isInternalTestBuild(): boolean { */ function getEnvironmentURL(): Promise { return new Promise((resolve) => { - getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment as keyof typeof ENVIRONMENT_URLS])); + getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment as EnvironmentUrlsKeys])); }); } @@ -44,7 +47,7 @@ function getEnvironmentURL(): Promise { * Get the corresponding oldDot URL based on the environment we are in */ function getOldDotEnvironmentURL(): Promise { - return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment as keyof typeof OLDDOT_ENVIRONMENT_URLS]); + return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment as OldDotEnvironmentUrlsKeys]); } export {getEnvironment, isInternalTestBuild, isDevelopment, getEnvironmentURL, getOldDotEnvironmentURL}; From 0be46040ab60b2cd4b3ed58ff106e0dba98d3a4f Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 3 Oct 2023 21:52:50 -0400 Subject: [PATCH 027/449] fix unit test --- tests/unit/ReportUtilsTest.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index 24397a04a0e9..9ddf9b93f8d0 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -503,8 +503,7 @@ describe('ReportUtils', () => { expect(ReportUtils.getReportIDFromLink('new-expensify://r/75431276')).toBe('75431276'); expect(ReportUtils.getReportIDFromLink('https://www.expensify.cash/r/75431276')).toBe('75431276'); expect(ReportUtils.getReportIDFromLink('https://staging.new.expensify.com/r/75431276')).toBe('75431276'); - expect(ReportUtils.getReportIDFromLink('http://localhost/r/75431276')).toBe('75431276'); - expect(ReportUtils.getReportIDFromLink('http://localhost:8080/r/75431276')).toBe('75431276'); + expect(ReportUtils.getReportIDFromLink('https://new.expensify.com.dev/r/75431276')).toBe('75431276'); expect(ReportUtils.getReportIDFromLink('https://staging.expensify.cash/r/75431276')).toBe('75431276'); expect(ReportUtils.getReportIDFromLink('https://new.expensify.com/r/75431276')).toBe('75431276'); }); From 9b266fae39474f474ebd21dd08bc38fb342999a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 4 Oct 2023 10:47:09 +0200 Subject: [PATCH 028/449] fix replaceNodes function --- src/libs/SelectionScraper/index.ts | 59 +++++++++++++++--------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index 4b679734b3a0..d865cd75e850 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -100,51 +100,50 @@ const getHTMLOfSelection = (): string => { /** * Clears all attributes from dom elements + * @param dom - dom htmlparser2 dom representation */ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { - // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. - const domDataNode = dom as DataNode; - let data = ''; - if (dom.type.toString() === 'text' && domDataNode.data) { - data = Str.htmlEncode(domDataNode.data); - return { - ...dom, - data, - } as DataNode; - } - - const domElement = dom as Element; - let domName = domElement.name; + let domName; let domChildren: Node[] = []; const domAttribs: Element['attribs'] = {}; + let data = ''; - // We are skipping elements which has html and body in data-testid, since ExpensiMark can't parse it. Also this data - // has no meaning for us. - if (domElement.attribs?.[tagAttribute]) { - if (!elementsWillBeSkipped.includes(domElement.attribs[tagAttribute])) { - domName = domElement.attribs[tagAttribute]; - } - } else if (domElement.name === 'div' && domElement.children.length === 1 && isChildOfEditorElement) { - // We are excluding divs that are children of our editor element and have only one child to prevent - // additional newlines from being added in the HTML to Markdown conversion process. - return replaceNodes(domElement.children[0], isChildOfEditorElement); + // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. + if (dom.type.toString() === 'text' && dom instanceof DataNode) { + data = Str.htmlEncode(dom.data); } - // We need to preserve href attribute in order to copy links. - if (domElement.attribs?.href) { - domAttribs.href = domElement.attribs.href; - } + if (dom instanceof Element) { + domName = dom.name; + // We are skipping elements which has html and body in data-testid, since ExpensiMark can't parse it. Also this data + // has no meaning for us. + if (dom.attribs?.[tagAttribute]) { + if (!elementsWillBeSkipped.includes(dom.attribs[tagAttribute])) { + domName = dom.attribs[tagAttribute]; + } + } else if (dom.name === 'div' && dom.children.length === 1 && isChildOfEditorElement) { + // We are excluding divs that are children of our editor element and have only one child to prevent + // additional newlines from being added in the HTML to Markdown conversion process. + return replaceNodes(dom.children[0], isChildOfEditorElement); + } - if (domElement.children) { - domChildren = domElement.children.map((c) => replaceNodes(c, isChildOfEditorElement || !!domElement.attribs?.[tagAttribute])); + // We need to preserve href attribute in order to copy links. + if (dom.attribs?.href) { + domAttribs.href = dom.attribs.href; + } + + if (dom.children) { + domChildren = dom.children.map((c) => replaceNodes(c, isChildOfEditorElement || !!dom.attribs?.[tagAttribute])); + } } return { ...dom, + data, name: domName, attribs: domAttribs, children: domChildren, - } as Element; + } as unknown as Node; }; /** From 772975082c0725c5ac88f20065adb437944ca3b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 4 Oct 2023 16:21:49 +0200 Subject: [PATCH 029/449] change replaceNodes return object type --- src/libs/SelectionScraper/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index d865cd75e850..0368087b5f38 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -143,7 +143,7 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { name: domName, attribs: domAttribs, children: domChildren, - } as unknown as Node; + } as Element & DataNode; }; /** From b0fa108a9ef097ebc5358d8576c6173bc9cc16a0 Mon Sep 17 00:00:00 2001 From: April Bekkala Date: Wed, 4 Oct 2023 11:03:02 -0500 Subject: [PATCH 030/449] Update Copilot.md https://github.com/Expensify/App/pull/28361#pullrequestreview-1656620957 made necessary edit noted in above comment --- .../expensify-classic/account-settings/Copilot.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/articles/expensify-classic/account-settings/Copilot.md b/docs/articles/expensify-classic/account-settings/Copilot.md index dbd26af12d88..4fac402b7ced 100644 --- a/docs/articles/expensify-classic/account-settings/Copilot.md +++ b/docs/articles/expensify-classic/account-settings/Copilot.md @@ -2,15 +2,11 @@ title: Copilot description: Safely delegate tasks without sharing login information. --- - # About - # How-to - # Deep Dive - # FAQ - From 67125d17037f34d45c35d77862148cfd08995638 Mon Sep 17 00:00:00 2001 From: Pierre Michel Date: Wed, 4 Oct 2023 10:08:53 -0600 Subject: [PATCH 031/449] The merge was wrong Signed-off-by: Pierre Michel --- src/components/ReportActionItem/TaskPreview.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 79d016fa297f..6fbe2c175799 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -57,16 +57,17 @@ const propTypes = { ownerAccountID: PropTypes.number, }), - /* Onyx Props */ /** chatReport associated with taskReport */ chatReport: reportPropTypes, - + /** Popover context menu anchor, used for showing context menu */ contextMenuAnchor: refPropTypes, /** Callback for updating context menu active state, used for showing context menu */ checkIfContextMenuActive: PropTypes.func, - ...withLocalizePropTypes, + + /* Onyx Props */ + ...withLocalizePropTypes, ...withCurrentUserPersonalDetailsPropTypes, }; @@ -87,8 +88,7 @@ function TaskPreview(props) { : props.action.childStateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.action.childStatusNum === CONST.REPORT.STATUS.APPROVED; const taskTitle = props.taskReport.reportName || props.action.childReportName; const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(props.taskReport) || props.action.childManagerAccountID; - const taskAssignee = lodashGet(props.personalDetailsList, [taskAssigneeAccountID, 'login'], lodashGet(props.personalDetailsList, [taskAssigneeAccountID, 'displayName'], '')); - const htmlForTaskPreview = taskAssignee ? `@${taskAssignee} ${taskTitle}` : `${taskTitle}`; + const assigneeLogin = lodashGet(props.personalDetailsList, [taskAssigneeAccountID, 'login'], ''); const assigneeDisplayName = lodashGet(props.personalDetailsList, [taskAssigneeAccountID, 'displayName'], ''); const taskAssignee = assigneeDisplayName || LocalePhoneNumber.formatPhoneNumber(assigneeLogin); const htmlForTaskPreview = From eb4fe936253d86232d0e096ca4c6328471d95e96 Mon Sep 17 00:00:00 2001 From: Pierre Michel Date: Wed, 4 Oct 2023 11:09:07 -0600 Subject: [PATCH 032/449] We use chatReportID not chatReport Signed-off-by: Pierre Michel --- src/components/ReportActionItem/TaskPreview.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 6fbe2c175799..3635e8dbb24d 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -57,8 +57,8 @@ const propTypes = { ownerAccountID: PropTypes.number, }), - /** chatReport associated with taskReport */ - chatReport: reportPropTypes, + /** The chat report associated with taskReport */ + chatReportID: PropTypes.string.isRequired, /** Popover context menu anchor, used for showing context menu */ contextMenuAnchor: refPropTypes, From eb985defedccd75ff4d347cff2c7adb433e068d6 Mon Sep 17 00:00:00 2001 From: Pierre Michel Date: Wed, 4 Oct 2023 11:16:09 -0600 Subject: [PATCH 033/449] reportPropTypes is not used Signed-off-by: Pierre Michel --- src/components/ReportActionItem/TaskPreview.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 3635e8dbb24d..8c757b51790c 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -26,7 +26,6 @@ import personalDetailsPropType from '../../pages/personalDetailsPropType'; import * as Session from '../../libs/actions/Session'; import * as LocalePhoneNumber from '../../libs/LocalePhoneNumber'; import {showContextMenuForReport} from '../ShowContextMenuContext'; -import reportPropTypes from '../../pages/reportPropTypes'; import refPropTypes from '../refPropTypes'; import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; import ControlSelection from '../../libs/ControlSelection'; From 392e320554727161916e4547eb24afd7567a5641 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 6 Oct 2023 22:32:14 +0700 Subject: [PATCH 034/449] fix wait for browser history load url after navigate --- src/libs/actions/App.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index fbea58b17efe..753e73947c4e 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -336,14 +336,24 @@ function createWorkspaceAndNavigateToIt( if (shouldNavigateToAdminChat) { if (isThereModalToDismiss) { Navigation.dismissModal(adminsChatReportID); + setTimeout(() => { + Navigation.navigate(ROUTES.SETTINGS); + setTimeout(() => { + Navigation.navigate(ROUTES.SETTINGS_WORKSPACES); + setTimeout(() => { + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); + }, 50); + }, 50); + }, 50); } else { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); + setTimeout(() => { + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); + }, 50); } + } else { + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); } - return Navigation.isNavigationReady(); - }) - .then(() => { - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); }) .then(endSignOnTransition); } From f9717fd85f6239df5df5c5b4d446293eb5be0899 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 7 Oct 2023 11:43:03 +0530 Subject: [PATCH 035/449] fixed offline jumping behaviour --- src/components/AddressSearch/index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index fe220d442674..84e009fdc081 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -294,12 +294,14 @@ function AddressSearch(props) { ) } listLoaderComponent={ - - - + props.network.isOffline ? null : ( + + + + ) } renderHeaderComponent={() => !props.value && From 3ebd5f97bdd31d5689ac8a9f75a577c70d1808a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Mon, 9 Oct 2023 11:40:09 +0200 Subject: [PATCH 036/449] fix replaceNodes condition --- src/libs/SelectionScraper/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index 0368087b5f38..abe9a235fca8 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -111,9 +111,7 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. if (dom.type.toString() === 'text' && dom instanceof DataNode) { data = Str.htmlEncode(dom.data); - } - - if (dom instanceof Element) { + } else if (dom instanceof Element) { domName = dom.name; // We are skipping elements which has html and body in data-testid, since ExpensiMark can't parse it. Also this data // has no meaning for us. @@ -135,6 +133,8 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { if (dom.children) { domChildren = dom.children.map((c) => replaceNodes(c, isChildOfEditorElement || !!dom.attribs?.[tagAttribute])); } + } else { + throw new Error(`Unknown dom type: ${dom.type}`); } return { From 16608759411ace3d75f90f7f19ccfb5cca1282bc Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 9 Oct 2023 22:00:50 +0530 Subject: [PATCH 037/449] fix package upstream --- package-lock.json | 14 +++++++------- package.json | 2 +- src/components/AddressSearch/index.js | 14 ++++++-------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index ddebbe8a3832..9539536b7891 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,7 +85,7 @@ "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.12.0", - "react-native-google-places-autocomplete": "2.5.5", + "react-native-google-places-autocomplete": "2.5.6", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", @@ -44589,9 +44589,9 @@ } }, "node_modules/react-native-google-places-autocomplete": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-native-google-places-autocomplete/-/react-native-google-places-autocomplete-2.5.5.tgz", - "integrity": "sha512-ypqaHYRifcY9q28HkZYExzHMF4Eul+mf3y4dlIlvBj3SgLI2FyrD1mdQoF8A7xwhOWctYs6PsVj3Mg71IVJTTw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/react-native-google-places-autocomplete/-/react-native-google-places-autocomplete-2.5.6.tgz", + "integrity": "sha512-Dy7mFKyEoiNeWPLd7HUkrI/SzJYe7GST55FGtiXzXDoPs05LYHIOCPrT9qFE51COh5X8kgDKm+f7D5aMY/aMbg==", "dependencies": { "lodash.debounce": "^4.0.8", "prop-types": "^15.7.2", @@ -85211,9 +85211,9 @@ } }, "react-native-google-places-autocomplete": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-native-google-places-autocomplete/-/react-native-google-places-autocomplete-2.5.5.tgz", - "integrity": "sha512-ypqaHYRifcY9q28HkZYExzHMF4Eul+mf3y4dlIlvBj3SgLI2FyrD1mdQoF8A7xwhOWctYs6PsVj3Mg71IVJTTw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/react-native-google-places-autocomplete/-/react-native-google-places-autocomplete-2.5.6.tgz", + "integrity": "sha512-Dy7mFKyEoiNeWPLd7HUkrI/SzJYe7GST55FGtiXzXDoPs05LYHIOCPrT9qFE51COh5X8kgDKm+f7D5aMY/aMbg==", "requires": { "lodash.debounce": "^4.0.8", "prop-types": "^15.7.2", diff --git a/package.json b/package.json index 9a3b9ed3af86..2b97739bc050 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.12.0", - "react-native-google-places-autocomplete": "2.5.5", + "react-native-google-places-autocomplete": "2.5.6", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 84e009fdc081..fe220d442674 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -294,14 +294,12 @@ function AddressSearch(props) { ) } listLoaderComponent={ - props.network.isOffline ? null : ( - - - - ) + + + } renderHeaderComponent={() => !props.value && From 9e54973035bd998503c3bfaad497b1412f729a52 Mon Sep 17 00:00:00 2001 From: Sofie de Vreese <40040992+SofiedeVreese@users.noreply.github.com> Date: Tue, 10 Oct 2023 01:38:34 +0000 Subject: [PATCH 038/449] Update Auto-Reconciliation.md Updating article to add image placeholder per https://stackoverflowteams.com/c/expensify/questions/17454/17455 --- .../expensify-classic/expensify-card/Auto-Reconciliation.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md b/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md index 9de47d6e5beb..059877a18075 100644 --- a/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md +++ b/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md @@ -44,10 +44,14 @@ Once Auto-Reconciliation is enabled, there are a few things that happen. Let’s **What happens**: When an Expensify Card is used to make purchases, the amount spent is automatically deducted from your company’s 'Settlement Account' (your business checking account). This deduction happens on a daily or monthly basis, depending on your chosen settlement frequency. Don't worry; this settlement account is pre-defined when you apply for the Expensify Card, and you can't accidentally change it. **Accounting treatment**: After your card balance is settled each day, we update your accounting system with a journal entry. This entry credits your bank account (referred to as the GL account) and debits the Expensify Card Clearing Account. To ensure accuracy, please make sure that the 'bank account' in your Expensify Card settings matches your real-life settlement account. You can easily verify this by navigating to **Settings > Account > Payments**, where you'll see 'Settlement Account' next to your business bank account. To keep track of settlement figures by date, use the Company Card Reconciliation Dashboard's Settlements tab: +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} + ### Submitting, Approving, and Exporting Expenses **What happens**: Users submit their expenses on a report, which might occur after some time has passed since the initial purchase. Once the report is approved, it's then exported to your accounting software. **Accounting treatment**: When the report is exported, we create a journal entry in your accounting system. This entry credits the Clearing Account and debits the Liability Account for the purchase amount. The Liability Account functions as a bank account in your ledger, specifically for Expensify Card expenses: +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} + # Deep Dive ## QuickBooks Online From c92ab2ef56ad8e714cdc0f67b73e1cb09829197d Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 11 Oct 2023 09:01:05 +0700 Subject: [PATCH 039/449] fix anonymous user can edit profile --- src/libs/Navigation/NavigationRoot.js | 6 ++++++ src/libs/actions/Session/index.js | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/libs/Navigation/NavigationRoot.js b/src/libs/Navigation/NavigationRoot.js index 34a52adfeca9..d8f083b9c59b 100644 --- a/src/libs/Navigation/NavigationRoot.js +++ b/src/libs/Navigation/NavigationRoot.js @@ -12,6 +12,7 @@ import StatusBar from '../StatusBar'; import useCurrentReportID from '../../hooks/useCurrentReportID'; import useWindowDimensions from '../../hooks/useWindowDimensions'; import {SidebarNavigationContext} from '../../pages/home/sidebar/SidebarNavigationContext'; +import * as Session from '../actions/Session'; // https://reactnavigation.org/docs/themes const navigationTheme = { @@ -133,6 +134,11 @@ function NavigationRoot(props) { // Update the global navigation to show the correct selected menu items. globalNavigation.updateFromNavigationState(state); + + const route = Navigation.getActiveRoute(); + if (Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(route)) { + Session.signOutAndRedirectToSignIn(); + } }; return ( diff --git a/src/libs/actions/Session/index.js b/src/libs/actions/Session/index.js index 117a092c3875..30c5f3320e08 100644 --- a/src/libs/actions/Session/index.js +++ b/src/libs/actions/Session/index.js @@ -787,6 +787,20 @@ function waitForUserSignIn() { }); } +/** + * check if the route can be accessed by anonymous user + * + * @param {string} route + */ + +const canAccessRouteByAnonymousUser = (route) => { + const reportID = ReportUtils.getReportIDFromLink(route); + if (reportID) { + return true; + } + return false; +}; + export { beginSignIn, beginAppleSignIn, @@ -815,4 +829,5 @@ export { toggleTwoFactorAuth, validateTwoFactorAuth, waitForUserSignIn, + canAccessRouteByAnonymousUser, }; From fa0fc433829dc7e5749a7047ede3851cc3315ab9 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 11 Oct 2023 13:25:01 +0700 Subject: [PATCH 040/449] fix update util function --- src/libs/ReportUtils.js | 1 + src/libs/actions/Session/index.js | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index b5fc0bff6ec7..ffb34355845f 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3959,4 +3959,5 @@ export { getIOUReportActionDisplayMessage, isWaitingForTaskCompleteFromAssignee, isReportDraft, + parseReportRouteParams }; diff --git a/src/libs/actions/Session/index.js b/src/libs/actions/Session/index.js index 30c5f3320e08..b52f172f2efa 100644 --- a/src/libs/actions/Session/index.js +++ b/src/libs/actions/Session/index.js @@ -798,6 +798,19 @@ const canAccessRouteByAnonymousUser = (route) => { if (reportID) { return true; } + const parsedReportRouteParams = ReportUtils.parseReportRouteParams(route); + let routeRemovedReportId = route; + if (parsedReportRouteParams.reportID) { + routeRemovedReportId = route.replace(lodashGet(parsedReportRouteParams, 'reportID', ''), ':reportID'); + } + if (route.startsWith('/')) { + routeRemovedReportId = routeRemovedReportId.slice(1); + } + const routesCanAccessByAnonymousUser = [ROUTES.SIGN_IN_MODAL, ROUTES.REPORT_WITH_ID_DETAILS.route, ROUTES.REPORT_WITH_ID_DETAILS_SHARE_CODE.route]; + + if (_.contains(routesCanAccessByAnonymousUser, routeRemovedReportId)) { + return true; + } return false; }; From 78ba2a46d5bfb9f2fce3a22f2c786d9810cd3565 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 11 Oct 2023 14:26:11 +0700 Subject: [PATCH 041/449] fix lint issue --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index ffb34355845f..50df076ba043 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3959,5 +3959,5 @@ export { getIOUReportActionDisplayMessage, isWaitingForTaskCompleteFromAssignee, isReportDraft, - parseReportRouteParams + parseReportRouteParams, }; From e2738d30b5e767e3568e4476fbc6438870c19607 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 11 Oct 2023 12:06:24 +0200 Subject: [PATCH 042/449] Remove assertion --- src/libs/Environment/Environment.ts | 7 ++----- src/libs/Environment/getEnvironment/index.native.ts | 5 +++-- src/libs/Environment/getEnvironment/index.ts | 5 +++-- src/libs/Environment/getEnvironment/types.ts | 6 ++++++ 4 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 src/libs/Environment/getEnvironment/types.ts diff --git a/src/libs/Environment/Environment.ts b/src/libs/Environment/Environment.ts index b201fd5be439..60b83e0a1dfe 100644 --- a/src/libs/Environment/Environment.ts +++ b/src/libs/Environment/Environment.ts @@ -17,9 +17,6 @@ const OLDDOT_ENVIRONMENT_URLS = { [CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_EXPENSIFY_URL, }; -type EnvironmentUrlsKeys = keyof typeof ENVIRONMENT_URLS; -type OldDotEnvironmentUrlsKeys = keyof typeof OLDDOT_ENVIRONMENT_URLS; - /** * Are we running the app in development? */ @@ -39,7 +36,7 @@ function isInternalTestBuild(): boolean { */ function getEnvironmentURL(): Promise { return new Promise((resolve) => { - getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment as EnvironmentUrlsKeys])); + getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment])); }); } @@ -47,7 +44,7 @@ function getEnvironmentURL(): Promise { * Get the corresponding oldDot URL based on the environment we are in */ function getOldDotEnvironmentURL(): Promise { - return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment as OldDotEnvironmentUrlsKeys]); + return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment]); } export {getEnvironment, isInternalTestBuild, isDevelopment, getEnvironmentURL, getOldDotEnvironmentURL}; diff --git a/src/libs/Environment/getEnvironment/index.native.ts b/src/libs/Environment/getEnvironment/index.native.ts index 1e3333c6a881..0e3b943b1bd5 100644 --- a/src/libs/Environment/getEnvironment/index.native.ts +++ b/src/libs/Environment/getEnvironment/index.native.ts @@ -1,13 +1,14 @@ import Config from 'react-native-config'; import betaChecker from '../betaChecker'; import CONST from '../../../CONST'; +import Environment from './types'; -let environment: string | null = null; +let environment: Environment | null = null; /** * Returns a promise that resolves with the current environment string value */ -function getEnvironment(): Promise { +function getEnvironment(): Promise { return new Promise((resolve) => { // If we've already set the environment, use the current value if (environment) { diff --git a/src/libs/Environment/getEnvironment/index.ts b/src/libs/Environment/getEnvironment/index.ts index 2c27be5fc471..9a18a997eec6 100644 --- a/src/libs/Environment/getEnvironment/index.ts +++ b/src/libs/Environment/getEnvironment/index.ts @@ -1,11 +1,12 @@ import Config from 'react-native-config'; import CONST from '../../../CONST'; +import Environment from './types'; /** * Returns a promise that resolves with the current environment string value */ -function getEnvironment(): Promise { - return Promise.resolve(Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV); +function getEnvironment(): Promise { + return Promise.resolve((Config?.ENVIRONMENT as Environment) ?? CONST.ENVIRONMENT.DEV); } export default getEnvironment; diff --git a/src/libs/Environment/getEnvironment/types.ts b/src/libs/Environment/getEnvironment/types.ts new file mode 100644 index 000000000000..7cf17af3e0d8 --- /dev/null +++ b/src/libs/Environment/getEnvironment/types.ts @@ -0,0 +1,6 @@ +import {ValueOf} from 'type-fest'; +import CONST from '../../../CONST'; + +type Environment = ValueOf; + +export default Environment; From 598f97f5471915b03bc9e62a94a4ad728a5f8b27 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 11 Oct 2023 17:01:03 +0200 Subject: [PATCH 043/449] ref: moved useArrowKeyFocusManager to TS --- ...sManager.js => useArrowKeyFocusManager.ts} | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) rename src/hooks/{useArrowKeyFocusManager.js => useArrowKeyFocusManager.ts} (77%) diff --git a/src/hooks/useArrowKeyFocusManager.js b/src/hooks/useArrowKeyFocusManager.ts similarity index 77% rename from src/hooks/useArrowKeyFocusManager.js rename to src/hooks/useArrowKeyFocusManager.ts index 58cecb169249..4f06111df09b 100644 --- a/src/hooks/useArrowKeyFocusManager.js +++ b/src/hooks/useArrowKeyFocusManager.ts @@ -2,19 +2,27 @@ import {useState, useEffect, useCallback, useMemo} from 'react'; import useKeyboardShortcut from './useKeyboardShortcut'; import CONST from '../CONST'; +type Config = { + maxIndex: number; + onFocusedIndexChange?: (index: number) => void; + initialFocusedIndex?: number; + disabledIndexes?: readonly number[]; + shouldExcludeTextAreaNodes?: boolean; + isActive?: boolean; +}; + /** * A hook that makes it easy to use the arrow keys to manage focus of items in a list * * Recommendation: To ensure stability, wrap the `onFocusedIndexChange` function with the useCallback hook before using it with this hook. * - * @param {Object} config - * @param {Number} config.maxIndex – typically the number of items in your list - * @param {Function} [config.onFocusedIndexChange] – optional callback to execute when focusedIndex changes - * @param {Number} [config.initialFocusedIndex] – where to start in the list - * @param {Array} [config.disabledIndexes] – An array of indexes to disable + skip over - * @param {Boolean} [config.shouldExcludeTextAreaNodes] – Whether arrow keys should have any effect when a TextArea node is focused - * @param {Boolean} [config.isActive] – Whether the component is ready and should subscribe to KeyboardShortcut - * @returns {Array} + * @param config + * @param config.maxIndex – typically the number of items in your list + * @param [config.onFocusedIndexChange] – optional callback to execute when focusedIndex changes + * @param [config.initialFocusedIndex] – where to start in the list + * @param [config.disabledIndexes] – An array of indexes to disable + skip over + * @param [config.shouldExcludeTextAreaNodes] – Whether arrow keys should have any effect when a TextArea node is focused + * @param [config.isActive] – Whether the component is ready and should subscribe to KeyboardShortcut */ export default function useArrowKeyFocusManager({ maxIndex, @@ -26,7 +34,7 @@ export default function useArrowKeyFocusManager({ disabledIndexes = CONST.EMPTY_ARRAY, shouldExcludeTextAreaNodes = true, isActive, -}) { +}: Config): [number, (index: number) => void] { const [focusedIndex, setFocusedIndex] = useState(initialFocusedIndex); const arrowConfig = useMemo( () => ({ From cb7efb9b6c9215e96170476572c7203845681683 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 11 Oct 2023 22:32:34 +0200 Subject: [PATCH 044/449] Add IsBetaBuild type --- src/libs/Environment/betaChecker/index.android.ts | 3 ++- src/libs/Environment/betaChecker/index.ios.ts | 3 ++- src/libs/Environment/betaChecker/index.ts | 4 +++- src/libs/Environment/betaChecker/types.ts | 3 +++ 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 src/libs/Environment/betaChecker/types.ts diff --git a/src/libs/Environment/betaChecker/index.android.ts b/src/libs/Environment/betaChecker/index.android.ts index 8b3afed3b2f3..e33b9c7feb6e 100644 --- a/src/libs/Environment/betaChecker/index.android.ts +++ b/src/libs/Environment/betaChecker/index.android.ts @@ -4,6 +4,7 @@ import CONST from '../../../CONST'; import pkg from '../../../../package.json'; import ONYXKEYS from '../../../ONYXKEYS'; import * as AppUpdate from '../../actions/AppUpdate'; +import IsBetaBuild from './types'; let isLastSavedBeta = false; Onyx.connect({ @@ -19,7 +20,7 @@ Onyx.connect({ /** * Check the GitHub releases to see if the current build is a beta build or production build */ -function isBetaBuild(): Promise { +function isBetaBuild(): IsBetaBuild { return new Promise((resolve) => { fetch(CONST.GITHUB_RELEASE_URL) .then((res) => res.json()) diff --git a/src/libs/Environment/betaChecker/index.ios.ts b/src/libs/Environment/betaChecker/index.ios.ts index 2d6079e30a1c..0d901fc4b003 100644 --- a/src/libs/Environment/betaChecker/index.ios.ts +++ b/src/libs/Environment/betaChecker/index.ios.ts @@ -1,9 +1,10 @@ import {NativeModules} from 'react-native'; +import IsBetaBuild from './types'; /** * Check to see if the build is staging (TestFlight) or production */ -function isBetaBuild(): Promise { +function isBetaBuild(): IsBetaBuild { return new Promise((resolve) => { NativeModules.EnvironmentChecker.isBeta().then((isBeta: boolean) => { resolve(isBeta); diff --git a/src/libs/Environment/betaChecker/index.ts b/src/libs/Environment/betaChecker/index.ts index cda7c297624f..541a3120ccce 100644 --- a/src/libs/Environment/betaChecker/index.ts +++ b/src/libs/Environment/betaChecker/index.ts @@ -1,7 +1,9 @@ +import IsBetaBuild from './types'; + /** * There's no beta build in non native */ -function isBetaBuild(): Promise { +function isBetaBuild(): IsBetaBuild { return Promise.resolve(false); } diff --git a/src/libs/Environment/betaChecker/types.ts b/src/libs/Environment/betaChecker/types.ts new file mode 100644 index 000000000000..61ce4bc9cec4 --- /dev/null +++ b/src/libs/Environment/betaChecker/types.ts @@ -0,0 +1,3 @@ +type IsBetaBuild = Promise; + +export default IsBetaBuild; From 1d6bb1d14cff3dd029868a0a7c8ee14ae78c527b Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 12 Oct 2023 11:18:33 -0400 Subject: [PATCH 045/449] use server instead of https config --- config/webpack/webpack.dev.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index 1e7074b16277..dcca84ce5e9d 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -45,9 +45,12 @@ module.exports = (env = {}) => historyApiFallback: true, port, host: 'new.expensify.com.dev', - https: { - key: path.join(__dirname, 'key.pem'), - cert: path.join(__dirname, 'certificate.pem'), + server: { + type: 'https', + options: { + key: path.join(__dirname, 'key.pem'), + cert: path.join(__dirname, 'certificate.pem'), + }, }, }, plugins: [ From 709d48fbab1fce7dfc3936ad3b3a81c8b5a0e1d6 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 13 Oct 2023 13:42:14 +0700 Subject: [PATCH 046/449] fix dissmiss modal login when clicking on back button --- src/pages/signin/SignInModal.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/signin/SignInModal.js b/src/pages/signin/SignInModal.js index f1ce09def084..0ca8fa6838b2 100644 --- a/src/pages/signin/SignInModal.js +++ b/src/pages/signin/SignInModal.js @@ -24,7 +24,11 @@ function SignInModal() { shouldEnableMaxHeight testID={SignInModal.displayName} > - + { + Navigation.dismissModal(); + }} + /> ); From 0d08b325058d0a96f2bb543150d268f786f1a9e4 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 13 Oct 2023 13:45:17 +0700 Subject: [PATCH 047/449] fix refactor code --- src/pages/signin/SignInModal.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/signin/SignInModal.js b/src/pages/signin/SignInModal.js index 0ca8fa6838b2..98bd0692298c 100644 --- a/src/pages/signin/SignInModal.js +++ b/src/pages/signin/SignInModal.js @@ -24,11 +24,7 @@ function SignInModal() { shouldEnableMaxHeight testID={SignInModal.displayName} > - { - Navigation.dismissModal(); - }} - /> + ); From 2cadcf65de4bb5d60e9c304ebddee04effb87978 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 13 Oct 2023 14:36:51 +0200 Subject: [PATCH 048/449] [TS migration] Migrate 'withPolicy.js' HOC to TypeScript --- .eslintrc.js | 2 +- src/libs/getComponentDisplayName.ts | 2 +- .../{withPolicy.js => withPolicy.tsx} | 87 ++++++++----------- 3 files changed, 40 insertions(+), 51 deletions(-) rename src/pages/workspace/{withPolicy.js => withPolicy.tsx} (58%) diff --git a/.eslintrc.js b/.eslintrc.js index 75a74ed371c4..83e9479ce0c4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -116,7 +116,7 @@ module.exports = { }, { selector: ['parameter', 'method'], - format: ['camelCase'], + format: ['camelCase', 'PascalCase'], }, ], '@typescript-eslint/ban-types': [ diff --git a/src/libs/getComponentDisplayName.ts b/src/libs/getComponentDisplayName.ts index fd1bbcaea521..0bf52d543a84 100644 --- a/src/libs/getComponentDisplayName.ts +++ b/src/libs/getComponentDisplayName.ts @@ -1,6 +1,6 @@ import {ComponentType} from 'react'; /** Returns the display name of a component */ -export default function getComponentDisplayName(component: ComponentType): string { +export default function getComponentDisplayName(component: ComponentType): string { return component.displayName ?? component.name ?? 'Component'; } diff --git a/src/pages/workspace/withPolicy.js b/src/pages/workspace/withPolicy.tsx similarity index 58% rename from src/pages/workspace/withPolicy.js rename to src/pages/workspace/withPolicy.tsx index b1659ea2b7a6..94731b2dc650 100644 --- a/src/pages/workspace/withPolicy.js +++ b/src/pages/workspace/withPolicy.tsx @@ -1,21 +1,22 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; -import React from 'react'; +import React, {ComponentType, ForwardedRef, RefAttributes, forwardRef} from 'react'; import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import {useNavigationState} from '@react-navigation/native'; import CONST from '../../CONST'; import getComponentDisplayName from '../../libs/getComponentDisplayName'; import * as Policy from '../../libs/actions/Policy'; import ONYXKEYS from '../../ONYXKEYS'; import policyMemberPropType from '../policyMemberPropType'; +import * as OnyxTypes from '../../types/onyx'; -/** - * @param {Object} route - * @returns {String} - */ -function getPolicyIDFromRoute(route) { - return lodashGet(route, 'params.policyID', ''); +type PolicyRoute = { + params?: { + policyID: string; + }; +}; + +function getPolicyIDFromRoute(route: PolicyRoute): string { + return route?.params?.policyID ?? 'N/A'; } const policyPropTypes = { @@ -28,10 +29,10 @@ const policyPropTypes = { name: PropTypes.string, /** The current user's role in the policy */ - role: PropTypes.oneOf(_.values(CONST.POLICY.ROLE)), + role: PropTypes.oneOf(Object.values(CONST.POLICY.ROLE)), /** The policy type */ - type: PropTypes.oneOf(_.values(CONST.POLICY.TYPE)), + type: PropTypes.oneOf(Object.values(CONST.POLICY.TYPE)), /** The email of the policy owner */ owner: PropTypes.string, @@ -61,66 +62,54 @@ const policyPropTypes = { policyMembers: PropTypes.objectOf(policyMemberPropType), }; -const policyDefaultProps = { - policy: {}, +const policyDefaultProps: WithPolicyOnyxProps = { + policy: {} as OnyxTypes.Policy, policyMembers: {}, }; +type WithPolicyOnyxProps = { + policy: OnyxEntry; + policyMembers: OnyxEntry; +}; + +type WithPolicyProps = WithPolicyOnyxProps & { + route: PolicyRoute; +}; /* * HOC for connecting a policy in Onyx corresponding to the policyID in route params */ -export default function (WrappedComponent) { - const propTypes = { - /** The HOC takes an optional ref as a prop and passes it as a ref to the wrapped component. - * That way, if a ref is passed to a component wrapped in the HOC, the ref is a reference to the wrapped component, not the HOC. */ - forwardedRef: PropTypes.func, - - ...policyPropTypes, - }; - - const defaultProps = { - forwardedRef: () => {}, - - ...policyDefaultProps, - }; - - function WithPolicy(props) { - const currentRoute = _.last(useNavigationState((state) => state.routes || [])); - const policyID = getPolicyIDFromRoute(currentRoute); - - if (_.isString(policyID) && !_.isEmpty(policyID)) { +export default function withPolicy( + WrappedComponent: ComponentType>, +): React.ComponentType> { + function WithPolicy(props: TProps, ref: ForwardedRef) { + const routes = useNavigationState((state) => state.routes || []); + const currentRoute = routes?.[routes.length - 1]; + const policyID = getPolicyIDFromRoute(currentRoute as PolicyRoute); + + if (typeof policyID === 'string' && policyID.length > 0) { Policy.updateLastAccessedWorkspace(policyID); } - const rest = _.omit(props, ['forwardedRef']); return ( ); } - WithPolicy.propTypes = propTypes; - WithPolicy.defaultProps = defaultProps; + WithPolicy.defaultProps = policyDefaultProps; WithPolicy.displayName = `withPolicy(${getComponentDisplayName(WrappedComponent)})`; - const withPolicy = React.forwardRef((props, ref) => ( - - )); - - return withOnyx({ + + return withOnyx({ policy: { key: (props) => `${ONYXKEYS.COLLECTION.POLICY}${getPolicyIDFromRoute(props.route)}`, }, policyMembers: { key: (props) => `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${getPolicyIDFromRoute(props.route)}`, }, - })(withPolicy); + })(forwardRef(WithPolicy)); } export {policyPropTypes, policyDefaultProps}; From c102c52abc79a22646509e3486a80da647645340 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 13 Oct 2023 15:03:43 +0200 Subject: [PATCH 049/449] fix: resolve comment --- src/hooks/useArrowKeyFocusManager.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/useArrowKeyFocusManager.ts b/src/hooks/useArrowKeyFocusManager.ts index 4f06111df09b..5746700b3c64 100644 --- a/src/hooks/useArrowKeyFocusManager.ts +++ b/src/hooks/useArrowKeyFocusManager.ts @@ -11,6 +11,8 @@ type Config = { isActive?: boolean; }; +type UseArrowKeyFocusManager = [number, (index: number) => void]; + /** * A hook that makes it easy to use the arrow keys to manage focus of items in a list * @@ -34,7 +36,7 @@ export default function useArrowKeyFocusManager({ disabledIndexes = CONST.EMPTY_ARRAY, shouldExcludeTextAreaNodes = true, isActive, -}: Config): [number, (index: number) => void] { +}: Config): UseArrowKeyFocusManager { const [focusedIndex, setFocusedIndex] = useState(initialFocusedIndex); const arrowConfig = useMemo( () => ({ From 86e1ef176839afdd8df5c625164b9e9c049266d9 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 13 Oct 2023 15:16:02 +0200 Subject: [PATCH 050/449] Resolve typecheck errors --- src/libs/ApiUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ApiUtils.ts b/src/libs/ApiUtils.ts index 87a251ccb086..54c7689dc4fb 100644 --- a/src/libs/ApiUtils.ts +++ b/src/libs/ApiUtils.ts @@ -1,3 +1,4 @@ +import {ValueOf} from 'type-fest'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '../ONYXKEYS'; import CONFIG from '../CONFIG'; @@ -8,7 +9,7 @@ import {Request} from '../types/onyx'; // To avoid rebuilding native apps, native apps use production config for both staging and prod // We use the async environment check because it works on all platforms -let ENV_NAME = CONST.ENVIRONMENT.PRODUCTION; +let ENV_NAME: ValueOf = CONST.ENVIRONMENT.PRODUCTION; let shouldUseStagingServer = false; Environment.getEnvironment().then((envName) => { ENV_NAME = envName; From e48b32e09b52d18d944c5d77a86cc12c2dc658f2 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 16 Oct 2023 12:32:43 +0200 Subject: [PATCH 051/449] fix: resolved comment --- src/hooks/useArrowKeyFocusManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/useArrowKeyFocusManager.ts b/src/hooks/useArrowKeyFocusManager.ts index 5746700b3c64..b2d1af1d659d 100644 --- a/src/hooks/useArrowKeyFocusManager.ts +++ b/src/hooks/useArrowKeyFocusManager.ts @@ -18,7 +18,6 @@ type UseArrowKeyFocusManager = [number, (index: number) => void]; * * Recommendation: To ensure stability, wrap the `onFocusedIndexChange` function with the useCallback hook before using it with this hook. * - * @param config * @param config.maxIndex – typically the number of items in your list * @param [config.onFocusedIndexChange] – optional callback to execute when focusedIndex changes * @param [config.initialFocusedIndex] – where to start in the list From e733f372f4d63779b349439d821fd843317d743a Mon Sep 17 00:00:00 2001 From: Hayata Suenaga Date: Tue, 17 Oct 2023 20:59:42 +0900 Subject: [PATCH 052/449] docs: add instructions for moving certificates --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index 6b5f96de6b72..17c4f83e2113 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ #### Table of Contents * [Local Development](#local-development) +* [Testing on browsers on simulators and emulators](#testing-on-browsers-on-simulators-and-emulators) * [Running The Tests](#running-the-tests) * [Debugging](#debugging) * [App Structure and Conventions](#app-structure-and-conventions) @@ -113,6 +114,47 @@ variables referenced here get updated since your local `.env` file is ignored. ---- +# Testing on browsers on simulators and emulators + +The development server is reached through the HTTPS protocol, and any client that access the development server needs a certificate. + +You create this certificate by following the instructions in [`Configuring HTTPS`](#configuring-https) of this readme. When accessing the website served from the development server on browsers on iOS simulator or Android emulator, these virtual devices need to have the same certificate installed. Follow the steps below to install to do so. + +#### Pre-requisite for Android flow +1. Open any emulator using Android Studio +2. Use `adb push "$(mkcert -CAROOT)/rootCA.pem" /storage/emulated/0/Download/` to push certificate to install in Download folder. +3. Install the certificate as CA certificate from the settings. +4. Close the emulator. + +Note - If you want to run app on `https://127.0.0.1:8082`, then just install the certificate and use `adb reverse tcp:8082 tcp:8082` on every startup. + +#### Android Flow +1. Run `npm run setupNewDotWebForEmulators android` +2. Select the emulator you want to run if prompted. (If single emulator is available, then it will open automatically) +3. Let the script execute till the message `🎉 Done!`. +4. Check the emulator is closed. + +Note - If you want to run app on `https://new.expensify.com.dev:8082`, then just do the Android flow and use `npm run startAndroidEmulator` to start the Android Emulator every time (It will configure the emulator). + + +Possible Scenario: +1. It may run the second flow on a new device, then to check first flow just run the flow again and select the same device. +2. It may fail to root with error `adbd cannot run as root in production builds`, then it will point to https://stackoverflow.com/a/45668555 in the console. + +#### iOS Flow +1. Run `npm run setupNewDotWebForEmulators ios` +2. Select the emulator you want to run if prompted. (If single emulator is available, then it will open automatically) +3. Let the script execute till the message `🎉 Done!`. +4. Check the emulator is closed. + +#### All Flow +1. Run `npm run setupNewDotWebForEmulators all` or `npm run setupNewDotWebForEmulators` +2. Check if the iOS flow runs first and then Android flow runs. +3. Let the script execute till the message `🎉 Done!`. +4. Check the emulator is closed. + +---- + # Running the tests ## Unit tests Unit tests are valuable when you want to test one component. They should be short, fast, and ideally only test one thing. From 98f89ada062eec261414497e82626cd79bb8f783 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Oct 2023 14:02:53 +0200 Subject: [PATCH 053/449] [TS migration] Migrate 'SafeAreaConsumer.js' component to TypeScript --- ...feAreaConsumer.js => SafeAreaConsumer.tsx} | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) rename src/components/{SafeAreaConsumer.js => SafeAreaConsumer.tsx} (55%) diff --git a/src/components/SafeAreaConsumer.js b/src/components/SafeAreaConsumer.tsx similarity index 55% rename from src/components/SafeAreaConsumer.js rename to src/components/SafeAreaConsumer.tsx index 78d7426ba380..c2439e25ecd3 100644 --- a/src/components/SafeAreaConsumer.js +++ b/src/components/SafeAreaConsumer.tsx @@ -1,29 +1,34 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import {SafeAreaInsetsContext} from 'react-native-safe-area-context'; +import {EdgeInsets, SafeAreaInsetsContext} from 'react-native-safe-area-context'; +import {DimensionValue} from 'react-native'; import * as StyleUtils from '../styles/StyleUtils'; -const propTypes = { - /** Children to render. */ - children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired, +type ChildrenProps = { + paddingTop?: DimensionValue; + paddingBottom?: DimensionValue; + insets?: EdgeInsets; + safeAreaPaddingBottomStyle: { + paddingBottom?: DimensionValue; + }; +}; + +type SafeAreaConsumerProps = { + children: (props: ChildrenProps) => React.ReactNode; }; /** * This component is a light wrapper around the SafeAreaInsetsContext.Consumer. There are several places where we * may need not just the insets, but the computed styles so we save a few lines of code with this. - * - * @param {Object} props - * @returns {React.Component} */ -function SafeAreaConsumer(props) { +function SafeAreaConsumer({children}: SafeAreaConsumerProps) { return ( {(insets) => { - const {paddingTop, paddingBottom} = StyleUtils.getSafeAreaPadding(insets); - return props.children({ + const {paddingTop, paddingBottom} = StyleUtils.getSafeAreaPadding(insets ?? undefined); + return children({ paddingTop, paddingBottom, - insets, + insets: insets ?? undefined, safeAreaPaddingBottomStyle: {paddingBottom}, }); }} @@ -32,5 +37,5 @@ function SafeAreaConsumer(props) { } SafeAreaConsumer.displayName = 'SafeAreaConsumer'; -SafeAreaConsumer.propTypes = propTypes; + export default SafeAreaConsumer; From 7b277a636b6e04c76926d276100a0344e00ddaef Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Oct 2023 14:05:24 +0200 Subject: [PATCH 054/449] [TS migration] Migrate 'RenderHTML.js' component to TypeScript --- src/components/{RenderHTML.js => RenderHTML.tsx} | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) rename src/components/{RenderHTML.js => RenderHTML.tsx} (75%) diff --git a/src/components/RenderHTML.js b/src/components/RenderHTML.tsx similarity index 75% rename from src/components/RenderHTML.js rename to src/components/RenderHTML.tsx index d2d4f0b58e71..14423d5e9d26 100644 --- a/src/components/RenderHTML.js +++ b/src/components/RenderHTML.tsx @@ -1,29 +1,26 @@ import React from 'react'; -import PropTypes from 'prop-types'; import {RenderHTMLSource} from 'react-native-render-html'; import useWindowDimensions from '../hooks/useWindowDimensions'; -const propTypes = { +type RenderHTMLProps = { /** HTML string to render */ - html: PropTypes.string.isRequired, + html: string; }; // We are using the explicit composite architecture for performance gains. // Configuration for RenderHTML is handled in a top-level component providing // context to RenderHTMLSource components. See https://git.io/JRcZb // The provider is available at src/components/HTMLEngineProvider/ -function RenderHTML(props) { +function RenderHTML({html}: RenderHTMLProps) { const {windowWidth} = useWindowDimensions(); return ( ); } RenderHTML.displayName = 'RenderHTML'; -RenderHTML.propTypes = propTypes; -RenderHTML.defaultProps = {}; export default RenderHTML; From abad73ff87fd544d75c4b4def30722d9e454766b Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Oct 2023 15:05:58 +0200 Subject: [PATCH 055/449] fix TS check --- src/hooks/useWindowDimensions/index.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/hooks/useWindowDimensions/index.js b/src/hooks/useWindowDimensions/index.js index 1a1f7eed5a67..404bbd161a8c 100644 --- a/src/hooks/useWindowDimensions/index.js +++ b/src/hooks/useWindowDimensions/index.js @@ -2,9 +2,19 @@ import {Dimensions, useWindowDimensions} from 'react-native'; import variables from '../../styles/variables'; +/** + * @typedef {Object} WindowDimensions + * @property {number} windowWidth + * @property {number} windowHeight + * @property {boolean} isExtraSmallScreenHeight + * @property {boolean} isSmallScreenWidth + * @property {boolean} isMediumScreenWidth + * @property {boolean} isLargeScreenWidth + */ + /** * A convenience wrapper around React Native's useWindowDimensions hook that also provides booleans for our breakpoints. - * @returns {Object} + * @returns {WindowDimensions} */ export default function () { const {width: windowWidth, height: windowHeight} = useWindowDimensions(); From cb3f3d0bb0011e7531c90eee29dd4c0e5d05bc49 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Tue, 17 Oct 2023 16:17:44 +0200 Subject: [PATCH 056/449] add hasOutstandingChildRequest verification --- src/libs/ReportUtils.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 011907c2c88b..c97de623ad3a 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1253,6 +1253,11 @@ function isWaitingForIOUActionFromCurrentUser(report) { return true; } + // Child report that is awaiting for current user to Pay + if (report.hasOutstandingChildRequest && report.ownerAccountID === currentUserAccountID) { + return true; + } + return false; } From 872645c276342312772f7c73029e7faa95fd533f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 18 Oct 2023 11:55:47 +0800 Subject: [PATCH 057/449] update number of lines when window width changes --- src/components/Composer/index.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index ad7a84cc1828..14974f217aab 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -11,7 +11,6 @@ import updateIsFullComposerAvailable from '../../libs/ComposerUtils/updateIsFull import * as ComposerUtils from '../../libs/ComposerUtils'; import * as Browser from '../../libs/Browser'; import * as StyleUtils from '../../styles/StyleUtils'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions'; import compose from '../../libs/compose'; import styles from '../../styles/styles'; import Text from '../Text'; @@ -19,6 +18,7 @@ import isEnterWhileComposition from '../../libs/KeyboardShortcut/isEnterWhileCom import CONST from '../../CONST'; import withNavigation from '../withNavigation'; import ReportActionComposeFocusManager from '../../libs/ReportActionComposeFocusManager'; +import useWindowDimensions from '../../hooks/useWindowDimensions'; const propTypes = { /** Maximum number of lines in the text input */ @@ -87,8 +87,6 @@ const propTypes = { isComposerFullSize: PropTypes.bool, ...withLocalizePropTypes, - - ...windowDimensionsPropTypes, }; const defaultProps = { @@ -168,6 +166,7 @@ function Composer({ isComposerFullSize, ...props }) { + const {windowWidth} = useWindowDimensions(); const textRef = useRef(null); const textInput = useRef(null); const initialValue = defaultValue ? `${defaultValue}` : `${value || ''}`; @@ -366,7 +365,7 @@ function Composer({ setNumberOfLines(generalNumberOfLines); textInput.current.style.height = 'auto'; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [value, maxLines, numberOfLinesProp, onNumberOfLinesChange, isFullComposerAvailable, setIsFullComposerAvailable]); + }, [value, maxLines, numberOfLinesProp, onNumberOfLinesChange, isFullComposerAvailable, setIsFullComposerAvailable, windowWidth]); useEffect(() => { updateNumberOfLines(); @@ -491,7 +490,6 @@ Composer.defaultProps = defaultProps; export default compose( withLocalize, - withWindowDimensions, withNavigation, )( React.forwardRef((props, ref) => ( From eab87d28d3a3747f68f1096e2b68f03583799a64 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 18 Oct 2023 13:05:00 +0200 Subject: [PATCH 058/449] Adjust after internal review --- src/components/SafeAreaConsumer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SafeAreaConsumer.tsx b/src/components/SafeAreaConsumer.tsx index c2439e25ecd3..dec0964b34a9 100644 --- a/src/components/SafeAreaConsumer.tsx +++ b/src/components/SafeAreaConsumer.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {EdgeInsets, SafeAreaInsetsContext} from 'react-native-safe-area-context'; -import {DimensionValue} from 'react-native'; +import type {DimensionValue} from 'react-native'; import * as StyleUtils from '../styles/StyleUtils'; type ChildrenProps = { @@ -13,7 +13,7 @@ type ChildrenProps = { }; type SafeAreaConsumerProps = { - children: (props: ChildrenProps) => React.ReactNode; + children: React.FC; }; /** From 6aeab2f7b1c996634fc7c20bc24b661dee8bd0f4 Mon Sep 17 00:00:00 2001 From: Hayata Suenaga Date: Wed, 18 Oct 2023 21:53:59 +0900 Subject: [PATCH 059/449] docs: update readme --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 17c4f83e2113..d96bafd85061 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ You create this certificate by following the instructions in [`Configuring HTTPS #### Pre-requisite for Android flow 1. Open any emulator using Android Studio 2. Use `adb push "$(mkcert -CAROOT)/rootCA.pem" /storage/emulated/0/Download/` to push certificate to install in Download folder. -3. Install the certificate as CA certificate from the settings. +3. Install the certificate as CA certificate from the settings. On the Android emulator, this option can be found in Settings > Security > Encryption & Credentials > Install a certificate > CA certificate. 4. Close the emulator. Note - If you want to run app on `https://127.0.0.1:8082`, then just install the certificate and use `adb reverse tcp:8082 tcp:8082` on every startup. @@ -132,26 +132,22 @@ Note - If you want to run app on `https://127.0.0.1:8082`, then just install the 1. Run `npm run setupNewDotWebForEmulators android` 2. Select the emulator you want to run if prompted. (If single emulator is available, then it will open automatically) 3. Let the script execute till the message `🎉 Done!`. -4. Check the emulator is closed. Note - If you want to run app on `https://new.expensify.com.dev:8082`, then just do the Android flow and use `npm run startAndroidEmulator` to start the Android Emulator every time (It will configure the emulator). Possible Scenario: -1. It may run the second flow on a new device, then to check first flow just run the flow again and select the same device. -2. It may fail to root with error `adbd cannot run as root in production builds`, then it will point to https://stackoverflow.com/a/45668555 in the console. +The flow may fail to root with error `adbd cannot run as root in production builds`. In this case, please refer to https://stackoverflow.com/a/45668555. Or use `https://127.0.0.1:8082` for less hassle. #### iOS Flow 1. Run `npm run setupNewDotWebForEmulators ios` 2. Select the emulator you want to run if prompted. (If single emulator is available, then it will open automatically) 3. Let the script execute till the message `🎉 Done!`. -4. Check the emulator is closed. #### All Flow 1. Run `npm run setupNewDotWebForEmulators all` or `npm run setupNewDotWebForEmulators` 2. Check if the iOS flow runs first and then Android flow runs. 3. Let the script execute till the message `🎉 Done!`. -4. Check the emulator is closed. ---- From 1ea93f7b779943e8a2dc3dd384f305d7b7a0d5c9 Mon Sep 17 00:00:00 2001 From: Roksana Zawilowska Date: Wed, 18 Oct 2023 15:13:41 +0200 Subject: [PATCH 060/449] Fix: 27456 green line --- src/pages/home/report/ReportActionsList.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index c673c06470f8..93668970edb7 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -288,9 +288,13 @@ function ReportActionsList({ if (!currentUnreadMarker) { const nextMessage = sortedReportActions[index + 1]; const isCurrentMessageUnread = isMessageUnread(reportAction, report.lastReadTime); - shouldDisplay = isCurrentMessageUnread && !isMessageUnread(nextMessage, report.lastReadTime); - if (!messageManuallyMarkedUnread) { - shouldDisplay = shouldDisplay && reportAction.actorAccountID !== Report.getCurrentUserAccountID(); + const isNextMessageRead = !isMessageUnread(nextMessage, report.lastReadTime); + const isWithinVisibleThreshold = scrollingVerticalOffset.current < MSG_VISIBLE_THRESHOLD ? reportAction.created < userActiveSince.current : true; + + shouldDisplay = isCurrentMessageUnread && isNextMessageRead && isWithinVisibleThreshold; + + if (shouldDisplay && !messageManuallyMarkedUnread) { + shouldDisplay = reportAction.actorAccountID !== Report.getCurrentUserAccountID(); } } else { shouldDisplay = reportAction.reportActionID === currentUnreadMarker; From a2a2fda4523474244b7b8dbfe351eed459973e23 Mon Sep 17 00:00:00 2001 From: Hayata Suenaga Date: Wed, 18 Oct 2023 07:10:35 -0700 Subject: [PATCH 061/449] fix typo Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d96bafd85061..b217690f37ed 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ variables referenced here get updated since your local `.env` file is ignored. ---- -# Testing on browsers on simulators and emulators +# Testing on browsers in simulators and emulators The development server is reached through the HTTPS protocol, and any client that access the development server needs a certificate. From 512d08434510ce8f6a4978e94796528b330d84bf Mon Sep 17 00:00:00 2001 From: Hayata Suenaga Date: Wed, 18 Oct 2023 07:11:19 -0700 Subject: [PATCH 062/449] fix typo Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b217690f37ed..48c4cb03baf0 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ variables referenced here get updated since your local `.env` file is ignored. The development server is reached through the HTTPS protocol, and any client that access the development server needs a certificate. -You create this certificate by following the instructions in [`Configuring HTTPS`](#configuring-https) of this readme. When accessing the website served from the development server on browsers on iOS simulator or Android emulator, these virtual devices need to have the same certificate installed. Follow the steps below to install to do so. +You create this certificate by following the instructions in [`Configuring HTTPS`](#configuring-https) of this readme. When accessing the website served from the development server on browsers in iOS simulator or Android emulator, these virtual devices need to have the same certificate installed. Follow the steps below to install them. #### Pre-requisite for Android flow 1. Open any emulator using Android Studio From 3d0b045c01d35b7759d2dcb72ea96a791c07fa6c Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 18 Oct 2023 10:53:51 -0600 Subject: [PATCH 063/449] Rename shouldDisableWriteActions to canUserPerformWriteAction --- src/libs/ReportUtils.js | 7 ++++--- src/libs/SidebarUtils.js | 2 +- src/pages/home/report/ReportActionsList.js | 2 +- src/pages/home/report/ReportFooter.js | 2 +- src/pages/iou/steps/NewRequestAmountPage.js | 2 +- src/pages/tasks/TaskShareDestinationSelectorModal.js | 2 +- tests/unit/OptionsListUtilsTest.js | 4 ++-- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 4e351d2dc5e3..7a376c59e6a3 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3386,7 +3386,7 @@ function parseReportRouteParams(route) { parsingRoute = parsingRoute.slice(1); } - if (!parsingRoute.startsWith(Url.addTrailingForwardSlash('r'))) { + if (!parsingRoute.startsWith(Url.addTrailingForwardSlash(ROUTES.REPORT))) { return {reportID: '', isSubReportPageRoute: false}; } @@ -3662,9 +3662,10 @@ function getAddWorkspaceRoomOrChatReportErrors(report) { * @param {Object} report * @returns {Boolean} */ -function shouldDisableWriteActions(report) { +function canUserPerformWriteAction(report) { const reportErrors = getAddWorkspaceRoomOrChatReportErrors(report); return isArchivedRoom(report) || !_.isEmpty(reportErrors) || !isAllowedToComment(report) || isAnonymousUser; + return !isArchivedRoom(report) && _.isEmpty(reportErrors) && isAllowedToComment(report) && !isAnonymousUser; } /** @@ -4057,7 +4058,7 @@ export { getRootParentReport, getReportPreviewMessage, getModifiedExpenseMessage, - shouldDisableWriteActions, + canUserPerformWriteAction, getOriginalReportID, canAccessReport, getAddWorkspaceRoomOrChatReportErrors, diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index dd6db33902fb..9018ed1ddd80 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -305,7 +305,7 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, result.parentReportID = report.parentReportID || null; result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; result.notificationPreference = report.notificationPreference || null; - result.isAllowedToComment = !ReportUtils.shouldDisableWriteActions(report); + result.isAllowedToComment = !ReportUtils.canUserPerformWriteAction(report); const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; const subtitle = ReportUtils.getChatRoomSubtitle(report); diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index c673c06470f8..e986c7b53528 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -334,7 +334,7 @@ function ReportActionsList({ // Native mobile does not render updates flatlist the changes even though component did update called. // To notify there something changes we can use extraData prop to flatlist const extraData = [isSmallScreenWidth ? currentUnreadMarker : undefined, ReportUtils.isArchivedRoom(report)]; - const hideComposer = ReportUtils.shouldDisableWriteActions(report); + const hideComposer = ReportUtils.canUserPerformWriteAction(report); const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(personalDetailsList, report, currentUserPersonalDetails.accountID) && !isComposerFullSize; const renderFooter = useCallback(() => { diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index 2237e6448504..c96824770f07 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -70,7 +70,7 @@ function ReportFooter(props) { const isAnonymousUser = Session.isAnonymousUser(); const isSmallSizeLayout = props.windowWidth - (props.isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; - const hideComposer = ReportUtils.shouldDisableWriteActions(props.report); + const hideComposer = ReportUtils.canUserPerformWriteAction(props.report); return ( <> diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index ae319f5a73bb..dda52352e015 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -85,7 +85,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { // Check and dismiss modal useEffect(() => { - if (!ReportUtils.shouldDisableWriteActions(report)) { + if (!ReportUtils.canUserPerformWriteAction(report)) { return; } Navigation.dismissModal(reportID); diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index 2fc8f0eab014..f1cd009d197d 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -52,7 +52,7 @@ function TaskShareDestinationSelectorModal(props) { const reports = {}; _.keys(props.reports).forEach((reportKey) => { if ( - ReportUtils.shouldDisableWriteActions(props.reports[reportKey]) || + ReportUtils.canUserPerformWriteAction(props.reports[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(props.reports[reportKey]) || ReportUtils.isCanceledTaskReport(props.reports[reportKey]) ) { diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index eda743f85aa2..0032f6a1ad42 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -590,7 +590,7 @@ describe('OptionsListUtils', () => { // Filter current REPORTS as we do in the component, before getting share destination options const filteredReports = {}; _.keys(REPORTS).forEach((reportKey) => { - if (ReportUtils.shouldDisableWriteActions(REPORTS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS[reportKey])) { + if (ReportUtils.canUserPerformWriteAction(REPORTS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS[reportKey])) { return; } filteredReports[reportKey] = REPORTS[reportKey]; @@ -617,7 +617,7 @@ describe('OptionsListUtils', () => { // Filter current REPORTS_WITH_WORKSPACE_ROOMS as we do in the component, before getting share destination options const filteredReportsWithWorkspaceRooms = {}; _.keys(REPORTS_WITH_WORKSPACE_ROOMS).forEach((reportKey) => { - if (ReportUtils.shouldDisableWriteActions(REPORTS_WITH_WORKSPACE_ROOMS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS_WITH_WORKSPACE_ROOMS[reportKey])) { + if (ReportUtils.canUserPerformWriteAction(REPORTS_WITH_WORKSPACE_ROOMS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS_WITH_WORKSPACE_ROOMS[reportKey])) { return; } filteredReportsWithWorkspaceRooms[reportKey] = REPORTS_WITH_WORKSPACE_ROOMS[reportKey]; From ddba0ece2d3b77764cc3af4b3e86635ebbe85876 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 18 Oct 2023 19:07:30 +0200 Subject: [PATCH 064/449] adjusted and removed unused parts --- src/components/LHNOptionsList/OptionRowLHN.js | 3 +- src/libs/ReportUtils.js | 31 +++---------------- src/libs/SidebarUtils.js | 2 +- tests/unit/ReportUtilsTest.js | 20 ++++++------ 4 files changed, 17 insertions(+), 39 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index ba035c8b3baf..064c1c146163 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -116,8 +116,7 @@ function OptionRowLHN(props) { const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; const defaultSubscriptSize = optionItem.isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; - const shouldShowGreenDotIndicator = - !hasBrickError && (optionItem.isUnreadWithMention || optionItem.isWaitingForTaskCompleteFromAssignee || ReportUtils.isWaitingForIOUActionFromCurrentUser(optionItem)); + const shouldShowGreenDotIndicator = !hasBrickError && (optionItem.isUnreadWithMention || optionItem.isWaitingForTaskCompleteFromAssignee || ReportUtils.shouldShowGBR(optionItem)); /** * Show the ReportActionContextMenu modal popover. diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index c97de623ad3a..c8339810cb1f 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1213,12 +1213,12 @@ function getDisplayNamesWithTooltips(personalDetailsList, isMultipleParticipantR } /** - * Determines if a report has an IOU that is waiting for an action from the current user (either Pay or Add a credit bank account) + * Determines if a report child has an outstanding request that is waiting for an action from the current user (either Pay or Add a credit bank account) * * @param {Object} report (chatReport or iouReport) * @returns {boolean} */ -function isWaitingForIOUActionFromCurrentUser(report) { +function shouldShowGBR(report) { if (!report) { return false; } @@ -1227,34 +1227,13 @@ function isWaitingForIOUActionFromCurrentUser(report) { return false; } - const policy = getPolicy(report.policyID); - if (policy.type === CONST.POLICY.TYPE.CORPORATE) { - // If the report is already settled, there's no action required from any user. - if (isSettled(report.reportID)) { - return false; - } - - // Report is pending approval and the current user is the manager - if (isReportManager(report) && !isReportApproved(report)) { - return true; - } - - // Current user is an admin and the report has been approved but not settled yet - return policy.role === CONST.POLICY.ROLE.ADMIN && isReportApproved(report); - } - // Money request waiting for current user to add their credit bank account if (report.hasOutstandingIOU && report.ownerAccountID === currentUserAccountID && report.isWaitingOnBankAccount) { return true; } - // Money request waiting for current user to Pay (from expense or iou report) - if (report.hasOutstandingIOU && report.ownerAccountID && (report.ownerAccountID !== currentUserAccountID || currentUserAccountID === report.managerID)) { - return true; - } - // Child report that is awaiting for current user to Pay - if (report.hasOutstandingChildRequest && report.ownerAccountID === currentUserAccountID) { + if (report.hasOutstandingChildRequest) { return true; } @@ -3141,7 +3120,7 @@ function shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, } // Include reports that are relevant to the user in any view mode. Criteria include having a draft, having an outstanding IOU, or being assigned to an open task. - if (report.hasDraft || isWaitingForIOUActionFromCurrentUser(report) || isWaitingForTaskCompleteFromAssignee(report)) { + if (report.hasDraft || shouldShowGBR(report) || isWaitingForTaskCompleteFromAssignee(report)) { return true; } const lastVisibleMessage = ReportActionsUtils.getLastVisibleMessage(report.reportID); @@ -3953,7 +3932,7 @@ export { isCurrentUserTheOnlyParticipant, hasAutomatedExpensifyAccountIDs, hasExpensifyGuidesEmails, - isWaitingForIOUActionFromCurrentUser, + shouldShowGBR, isIOUOwnedByCurrentUser, getMoneyRequestReimbursableTotal, getMoneyRequestSpendBreakdown, diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index dd6db33902fb..bc915c4b16fa 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -179,7 +179,7 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p reportsToDisplay.forEach((report) => { if (report.isPinned) { pinnedReports.push(report); - } else if (ReportUtils.isWaitingForIOUActionFromCurrentUser(report)) { + } else if (ReportUtils.shouldShowGBR(report)) { outstandingIOUReports.push(report); } else if (report.hasDraft) { draftReports.push(report); diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index c6afde7d9161..10a1389d6cf9 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -246,9 +246,9 @@ describe('ReportUtils', () => { }); }); - describe('isWaitingForIOUActionFromCurrentUser', () => { + describe('shouldShowGBR', () => { it('returns false when there is no report', () => { - expect(ReportUtils.isWaitingForIOUActionFromCurrentUser()).toBe(false); + expect(ReportUtils.shouldShowGBR()).toBe(false); }); it('returns false when the matched IOU report does not have an owner accountID', () => { const report = { @@ -256,7 +256,7 @@ describe('ReportUtils', () => { ownerAccountID: undefined, hasOutstandingIOU: true, }; - expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false); + expect(ReportUtils.shouldShowGBR(report)).toBe(false); }); it('returns false when the linked iou report has an oustanding IOU', () => { const report = { @@ -268,7 +268,7 @@ describe('ReportUtils', () => { ownerAccountID: 99, hasOutstandingIOU: true, }).then(() => { - expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false); + expect(ReportUtils.shouldShowGBR(report)).toBe(false); }); }); it('returns false when the report has no oustanding IOU but is waiting for a bank account and the logged user is the report owner', () => { @@ -278,7 +278,7 @@ describe('ReportUtils', () => { ownerAccountID: currentUserAccountID, isWaitingOnBankAccount: true, }; - expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false); + expect(ReportUtils.shouldShowGBR(report)).toBe(false); }); it('returns true when the report has oustanding IOU and is waiting for a bank account and the logged user is the report owner', () => { const report = { @@ -287,7 +287,7 @@ describe('ReportUtils', () => { ownerAccountID: currentUserAccountID, isWaitingOnBankAccount: true, }; - expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(true); + expect(ReportUtils.shouldShowGBR(report)).toBe(true); }); it('returns false when the report has no oustanding IOU but is waiting for a bank account and the logged user is not the report owner', () => { const report = { @@ -296,16 +296,16 @@ describe('ReportUtils', () => { ownerAccountID: 97, isWaitingOnBankAccount: true, }; - expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false); + expect(ReportUtils.shouldShowGBR(report)).toBe(false); }); - it('returns true when the report has oustanding IOU', () => { + it('returns true when the report has oustanding child request', () => { const report = { ...LHNTestUtils.getFakeReport(), ownerAccountID: 99, - hasOutstandingIOU: true, + hasOutstandingChildRequest: true, isWaitingOnBankAccount: false, }; - expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(true); + expect(ReportUtils.shouldShowGBR(report)).toBe(true); }); }); From 03af64861ae3e64fc41cfc4c19274b13f51f68af Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Thu, 19 Oct 2023 00:56:31 +0700 Subject: [PATCH 065/449] fix sign in modal appear for a second --- src/libs/Navigation/NavigationRoot.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/NavigationRoot.js b/src/libs/Navigation/NavigationRoot.js index a920bfeeca9e..b39e5bbb0d5f 100644 --- a/src/libs/Navigation/NavigationRoot.js +++ b/src/libs/Navigation/NavigationRoot.js @@ -13,6 +13,8 @@ import useCurrentReportID from '../../hooks/useCurrentReportID'; import useWindowDimensions from '../../hooks/useWindowDimensions'; import {SidebarNavigationContext} from '../../pages/home/sidebar/SidebarNavigationContext'; import * as Session from '../actions/Session'; +import getCurrentUrl from './currentUrl'; +import ROUTES from '../../ROUTES'; // https://reactnavigation.org/docs/themes const navigationTheme = { @@ -102,7 +104,7 @@ function NavigationRoot(props) { const animateStatusBarBackgroundColor = () => { const currentRoute = navigationRef.getCurrentRoute(); - const currentScreenBackgroundColor = (currentRoute.params && currentRoute.params.backgroundColor) || themeColors.PAGE_BACKGROUND_COLORS[currentRoute.name] || themeColors.appBG; + const currentScreenBackgroundColor = themeColors.PAGE_BACKGROUND_COLORS[currentRoute.name] || themeColors.appBG; prevStatusBarBackgroundColor.current = statusBarBackgroundColor.current; statusBarBackgroundColor.current = currentScreenBackgroundColor; @@ -136,7 +138,7 @@ function NavigationRoot(props) { globalNavigation.updateFromNavigationState(state); const route = Navigation.getActiveRoute(); - if (Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(route)) { + if (Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(route) && !getCurrentUrl().includes(ROUTES.SIGN_IN_MODAL)) { Session.signOutAndRedirectToSignIn(); } }; From 0ac3e98f814025b0df91c793c1352e94a218f190 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Thu, 19 Oct 2023 01:02:12 +0700 Subject: [PATCH 066/449] fix revert unrelated change --- src/libs/Navigation/NavigationRoot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/NavigationRoot.js b/src/libs/Navigation/NavigationRoot.js index b39e5bbb0d5f..a22b6714a306 100644 --- a/src/libs/Navigation/NavigationRoot.js +++ b/src/libs/Navigation/NavigationRoot.js @@ -104,7 +104,7 @@ function NavigationRoot(props) { const animateStatusBarBackgroundColor = () => { const currentRoute = navigationRef.getCurrentRoute(); - const currentScreenBackgroundColor = themeColors.PAGE_BACKGROUND_COLORS[currentRoute.name] || themeColors.appBG; + const currentScreenBackgroundColor = (currentRoute.params && currentRoute.params.backgroundColor) || themeColors.PAGE_BACKGROUND_COLORS[currentRoute.name] || themeColors.appBG; prevStatusBarBackgroundColor.current = statusBarBackgroundColor.current; statusBarBackgroundColor.current = currentScreenBackgroundColor; From 9b074dd4a52930ba1acffed0adeedc0832a84a6c Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 19 Oct 2023 10:34:23 +0200 Subject: [PATCH 067/449] Add new onyx values --- src/ONYXKEYS.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index ad8b60700e39..c761b540bc22 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -382,9 +382,11 @@ type OnyxValues = { // Collections [ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download; [ONYXKEYS.COLLECTION.POLICY]: OnyxTypes.Policy; + [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategory; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTag; [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMember; + [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; From b4d942fc7d23c314ec7e6efab99c19073cce560f Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 10:46:56 +0200 Subject: [PATCH 068/449] WIP --- src/pages/settings/Wallet/AddDebitCardPage.js | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index e75c3b2c517e..23532346a990 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -22,6 +22,8 @@ import ROUTES from '../../../ROUTES'; import usePrevious from '../../../hooks/usePrevious'; import NotFoundPage from '../../ErrorPage/NotFoundPage'; import Permissions from '../../../libs/Permissions'; +import FormProvider from "../../../components/Form/FormProvider"; +import InputWrapper from "../../../components/Form/InputWrapper"; const propTypes = { /* Onyx Props */ @@ -114,7 +116,7 @@ function DebitCardPage(props) { title={translate('addDebitCardPage.addADebitCard')} onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WALLET)} /> -
- (nameOnCardRef.current = ref)} spellCheck={false} /> - - - - - - + - ( @@ -195,7 +204,7 @@ function DebitCardPage(props) { )} style={[styles.mt4]} /> - + ); } From 55b66073e64fb1f28c62020cf2521107700f4cbc Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 19 Oct 2023 12:29:20 +0200 Subject: [PATCH 069/449] migrate KeyboardAvoidingView to TypeScript --- .../KeyboardAvoidingView/{index.ios.js => index.ios.tsx} | 4 ++-- .../KeyboardAvoidingView/{index.js => index.tsx} | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) rename src/components/KeyboardAvoidingView/{index.ios.js => index.ios.tsx} (60%) rename src/components/KeyboardAvoidingView/{index.js => index.tsx} (50%) diff --git a/src/components/KeyboardAvoidingView/index.ios.js b/src/components/KeyboardAvoidingView/index.ios.tsx similarity index 60% rename from src/components/KeyboardAvoidingView/index.ios.js rename to src/components/KeyboardAvoidingView/index.ios.tsx index c1ea8687f793..4ab04a52a4cf 100644 --- a/src/components/KeyboardAvoidingView/index.ios.js +++ b/src/components/KeyboardAvoidingView/index.ios.tsx @@ -2,9 +2,9 @@ * The KeyboardAvoidingView is only used on ios */ import React from 'react'; -import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native'; +import {KeyboardAvoidingView as KeyboardAvoidingViewComponent, KeyboardAvoidingViewProps} from 'react-native'; -function KeyboardAvoidingView(props) { +function KeyboardAvoidingView(props: KeyboardAvoidingViewProps): React.ReactNode { // eslint-disable-next-line react/jsx-props-no-spreading return ; } diff --git a/src/components/KeyboardAvoidingView/index.js b/src/components/KeyboardAvoidingView/index.tsx similarity index 50% rename from src/components/KeyboardAvoidingView/index.js rename to src/components/KeyboardAvoidingView/index.tsx index 3483b2d007ac..f86f73622515 100644 --- a/src/components/KeyboardAvoidingView/index.js +++ b/src/components/KeyboardAvoidingView/index.tsx @@ -2,14 +2,13 @@ * The KeyboardAvoidingView is only used on ios */ import React from 'react'; -import {View} from 'react-native'; -import _ from 'underscore'; +import {KeyboardAvoidingViewProps, View} from 'react-native'; -function KeyboardAvoidingView(props) { - const viewProps = _.omit(props, ['behavior', 'contentContainerStyle', 'enabled', 'keyboardVerticalOffset']); +function KeyboardAvoidingView(props: KeyboardAvoidingViewProps): React.ReactNode { + const {behavior, contentContainerStyle, enabled, keyboardVerticalOffset, ...rest} = props; return ( // eslint-disable-next-line react/jsx-props-no-spreading - + ); } From 134318a8d22a0e1e7accb7e286b8728ad4e53b2e Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 19 Oct 2023 14:55:40 +0200 Subject: [PATCH 070/449] remove component return type --- src/components/KeyboardAvoidingView/index.ios.tsx | 2 +- src/components/KeyboardAvoidingView/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/KeyboardAvoidingView/index.ios.tsx b/src/components/KeyboardAvoidingView/index.ios.tsx index 4ab04a52a4cf..076dcef3e158 100644 --- a/src/components/KeyboardAvoidingView/index.ios.tsx +++ b/src/components/KeyboardAvoidingView/index.ios.tsx @@ -4,7 +4,7 @@ import React from 'react'; import {KeyboardAvoidingView as KeyboardAvoidingViewComponent, KeyboardAvoidingViewProps} from 'react-native'; -function KeyboardAvoidingView(props: KeyboardAvoidingViewProps): React.ReactNode { +function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) { // eslint-disable-next-line react/jsx-props-no-spreading return ; } diff --git a/src/components/KeyboardAvoidingView/index.tsx b/src/components/KeyboardAvoidingView/index.tsx index f86f73622515..4cff2a2399af 100644 --- a/src/components/KeyboardAvoidingView/index.tsx +++ b/src/components/KeyboardAvoidingView/index.tsx @@ -4,7 +4,7 @@ import React from 'react'; import {KeyboardAvoidingViewProps, View} from 'react-native'; -function KeyboardAvoidingView(props: KeyboardAvoidingViewProps): React.ReactNode { +function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) { const {behavior, contentContainerStyle, enabled, keyboardVerticalOffset, ...rest} = props; return ( // eslint-disable-next-line react/jsx-props-no-spreading From a348d1ba55eb224a53538f5afc9cd9c7b215a97b Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 15:26:34 +0200 Subject: [PATCH 071/449] Fix checkbox proptypes --- src/components/CheckboxWithLabel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js index 63c067c93234..777c46f861aa 100644 --- a/src/components/CheckboxWithLabel.js +++ b/src/components/CheckboxWithLabel.js @@ -54,7 +54,7 @@ const propTypes = { defaultValue: PropTypes.bool, /** React ref being forwarded to the Checkbox input */ - forwardedRef: PropTypes.func, + forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), /** The ID used to uniquely identify the input in a Form */ /* eslint-disable-next-line react/no-unused-prop-types */ From 38d91848c62674d56b4137fdce9c1f482b4e46bd Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 15:27:14 +0200 Subject: [PATCH 072/449] Fix address search prop type issue --- src/components/AddressSearch/index.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 3e676b811c16..3fac925cdb0a 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -1,7 +1,7 @@ import _ from 'underscore'; -import React, {useEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useEffect, useMemo, useRef, useState} from 'react'; import PropTypes from 'prop-types'; -import {Keyboard, LogBox, ScrollView, View, Text, ActivityIndicator} from 'react-native'; +import {ActivityIndicator, Keyboard, LogBox, ScrollView, Text, View} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; import lodashGet from 'lodash/get'; import compose from '../../libs/compose'; @@ -137,6 +137,16 @@ const defaultProps = { resultTypes: 'address', }; +function listLoader() { + return memo(() => ( + + )); +} + + // Do not convert to class component! It's been tried before and presents more challenges than it's worth. // Relevant thread: https://expensify.slack.com/archives/C03TQ48KC/p1634088400387400 // Reference: https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/609#issuecomment-886133839 @@ -374,14 +384,7 @@ function AddressSearch(props) { {props.translate('common.noResultsFound')} ) } - listLoaderComponent={ - - - - } + listLoaderComponent={listLoader} renderHeaderComponent={renderHeaderComponent} onPress={(data, details) => { saveLocationDetails(data, details); From 00e12fc524860cc05fa5c34929a3ccec21e5efb5 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 15:27:36 +0200 Subject: [PATCH 073/449] Add checkbox default value --- src/pages/settings/Wallet/AddDebitCardPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index 23532346a990..ad284da16827 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -196,6 +196,7 @@ function DebitCardPage(props) { InputComponent={CheckboxWithLabel} accessibilityLabel={`${translate('common.iAcceptThe')} ${translate('common.expensifyTermsOfService')}`} inputID="acceptTerms" + defaultValue={false} LabelComponent={() => ( {`${translate('common.iAcceptThe')}`} From 6b401e0e1d6631889f93e1fc1a16f19a068ba5e0 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 19 Oct 2023 08:11:06 -0600 Subject: [PATCH 074/449] Fix lint --- src/libs/ReportUtils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 7a376c59e6a3..3b72e6829c39 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3664,7 +3664,6 @@ function getAddWorkspaceRoomOrChatReportErrors(report) { */ function canUserPerformWriteAction(report) { const reportErrors = getAddWorkspaceRoomOrChatReportErrors(report); - return isArchivedRoom(report) || !_.isEmpty(reportErrors) || !isAllowedToComment(report) || isAnonymousUser; return !isArchivedRoom(report) && _.isEmpty(reportErrors) && isAllowedToComment(report) && !isAnonymousUser; } From bd2274c63dd1c23c8bb509949a44ab6e72d7e75f Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Thu, 19 Oct 2023 16:20:14 +0200 Subject: [PATCH 075/449] WIP --- src/components/FullscreenLoadingIndicator.js | 33 ------------------- src/components/FullscreenLoadingIndicator.tsx | 22 +++++++++++++ 2 files changed, 22 insertions(+), 33 deletions(-) delete mode 100644 src/components/FullscreenLoadingIndicator.js create mode 100644 src/components/FullscreenLoadingIndicator.tsx diff --git a/src/components/FullscreenLoadingIndicator.js b/src/components/FullscreenLoadingIndicator.js deleted file mode 100644 index 5c212b6dc29e..000000000000 --- a/src/components/FullscreenLoadingIndicator.js +++ /dev/null @@ -1,33 +0,0 @@ -import _ from 'underscore'; -import React from 'react'; -import {ActivityIndicator, StyleSheet, View} from 'react-native'; -import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; -import stylePropTypes from '../styles/stylePropTypes'; - -const propTypes = { - /** Additional style props */ - style: stylePropTypes, -}; - -const defaultProps = { - style: [], -}; - -function FullScreenLoadingIndicator(props) { - const additionalStyles = _.isArray(props.style) ? props.style : [props.style]; - return ( - - - - ); -} - -FullScreenLoadingIndicator.propTypes = propTypes; -FullScreenLoadingIndicator.defaultProps = defaultProps; -FullScreenLoadingIndicator.displayName = 'FullScreenLoadingIndicator'; - -export default FullScreenLoadingIndicator; diff --git a/src/components/FullscreenLoadingIndicator.tsx b/src/components/FullscreenLoadingIndicator.tsx new file mode 100644 index 000000000000..3f5f62f533d7 --- /dev/null +++ b/src/components/FullscreenLoadingIndicator.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import {ActivityIndicator, StyleSheet, View} from 'react-native'; +import styles from '../styles/styles'; +import themeColors from '../styles/themes/default'; + +type FullScreenLoadingIndicatorProps = { + style: Record | Array> | (() => void); +}; + +function FullScreenLoadingIndicator({style = []}: FullScreenLoadingIndicatorProps) { + const additionalStyles = Array.isArray(style) ? style : [style]; + return ( + + + + ); +} + +export default FullScreenLoadingIndicator; From b05c8bab57672e9a00aeb1b86b1efcdc9d1bf228 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 16:25:41 +0200 Subject: [PATCH 076/449] Prettier --- src/pages/settings/Wallet/AddDebitCardPage.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index ad284da16827..b0196e8e620e 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -22,8 +22,8 @@ import ROUTES from '../../../ROUTES'; import usePrevious from '../../../hooks/usePrevious'; import NotFoundPage from '../../ErrorPage/NotFoundPage'; import Permissions from '../../../libs/Permissions'; -import FormProvider from "../../../components/Form/FormProvider"; -import InputWrapper from "../../../components/Form/InputWrapper"; +import FormProvider from '../../../components/Form/FormProvider'; +import InputWrapper from '../../../components/Form/InputWrapper'; const propTypes = { /* Onyx Props */ @@ -190,7 +190,10 @@ function DebitCardPage(props) { containerStyles={[styles.mt4]} /> - + Date: Thu, 19 Oct 2023 16:25:56 +0200 Subject: [PATCH 077/449] Spread props --- src/components/AddressSearch/index.js | 146 +++++++++++++++----------- 1 file changed, 86 insertions(+), 60 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 3fac925cdb0a..db8d6b6bae42 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {memo, useEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import PropTypes from 'prop-types'; import {ActivityIndicator, Keyboard, LogBox, ScrollView, Text, View} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; @@ -137,37 +137,49 @@ const defaultProps = { resultTypes: 'address', }; -function listLoader() { - return memo(() => ( - - )); -} - - // Do not convert to class component! It's been tried before and presents more challenges than it's worth. // Relevant thread: https://expensify.slack.com/archives/C03TQ48KC/p1634088400387400 // Reference: https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/609#issuecomment-886133839 -function AddressSearch(props) { +function AddressSearch({ + canUseCurrentLocation, + containerStyles, + defaultValue, + errorText, + hint, + innerRef, + inputID, + isLimitedToUSA, + label, + maxInputLength, + network, + onBlur, + onInputChange, + onPress, + predefinedPlaces, + preferredLocale, + renamedInputKeys, + resultTypes, + shouldSaveDraft, + translate, + value, +}) { const [displayListViewBorder, setDisplayListViewBorder] = useState(false); const [isTyping, setIsTyping] = useState(false); const [isFocused, setIsFocused] = useState(false); - const [searchValue, setSearchValue] = useState(props.value || props.defaultValue || ''); + const [searchValue, setSearchValue] = useState(value || defaultValue || ''); const [locationErrorCode, setLocationErrorCode] = useState(null); const [isFetchingCurrentLocation, setIsFetchingCurrentLocation] = useState(false); const shouldTriggerGeolocationCallbacks = useRef(true); const containerRef = useRef(); const query = useMemo( () => ({ - language: props.preferredLocale, - types: props.resultTypes, - components: props.isLimitedToUSA ? 'country:us' : undefined, + language: preferredLocale, + types: resultTypes, + components: isLimitedToUSA ? 'country:us' : undefined, }), - [props.preferredLocale, props.resultTypes, props.isLimitedToUSA], + [preferredLocale, resultTypes, isLimitedToUSA], ); - const shouldShowCurrentLocationButton = props.canUseCurrentLocation && searchValue.trim().length === 0 && isFocused; + const shouldShowCurrentLocationButton = canUseCurrentLocation && searchValue.trim().length === 0 && isFocused; const saveLocationDetails = (autocompleteData, details) => { const addressComponents = details.address_components; @@ -176,7 +188,7 @@ function AddressSearch(props) { // to this component which don't match the usual properties coming from auto-complete. In that case, only a limited // amount of data massaging needs to happen for what the parent expects to get from this function. if (_.size(details)) { - props.onPress({ + onPress({ address: lodashGet(details, 'description', ''), lat: lodashGet(details, 'geometry.location.lat', 0), lng: lodashGet(details, 'geometry.location.lng', 0), @@ -262,7 +274,7 @@ function AddressSearch(props) { // Not all pages define the Address Line 2 field, so in that case we append any additional address details // (e.g. Apt #) to Address Line 1 - if (subpremise && typeof props.renamedInputKeys.street2 === 'undefined') { + if (subpremise && typeof renamedInputKeys.street2 === 'undefined') { values.street += `, ${subpremise}`; } @@ -271,19 +283,19 @@ function AddressSearch(props) { values.country = country; } - if (props.inputID) { - _.each(values, (value, key) => { - const inputKey = lodashGet(props.renamedInputKeys, key, key); + if (inputID) { + _.each(values, (inputValue, key) => { + const inputKey = lodashGet(renamedInputKeys, key, key); if (!inputKey) { return; } - props.onInputChange(value, inputKey); + onInputChange(inputValue, inputKey); }); } else { - props.onInputChange(values); + onInputChange(values); } - props.onPress(values); + onPress(values); }; /** Gets the user's current location and registers success/error callbacks */ @@ -313,7 +325,7 @@ function AddressSearch(props) { lng: successData.coords.longitude, address: CONST.YOUR_LOCATION_TEXT, }; - props.onPress(location); + onPress(location); }, (errorData) => { if (!shouldTriggerGeolocationCallbacks.current) { @@ -331,16 +343,16 @@ function AddressSearch(props) { }; const renderHeaderComponent = () => - props.predefinedPlaces.length > 0 && ( + predefinedPlaces.length > 0 && ( <> {/* This will show current location button in list if there are some recent destinations */} {shouldShowCurrentLocationButton && ( )} - {!props.value && {props.translate('common.recentDestinations')}} + {!value && {translate('common.recentDestinations')}} ); @@ -352,6 +364,26 @@ function AddressSearch(props) { }; }, []); + const listEmptyComponent = useCallback( + () => + network.isOffline || !isTyping ? null : ( + {translate('common.noResultsFound')} + ), + [isTyping, translate, network.isOffline], + ); + + const listLoader = useCallback( + () => ( + + + + ), + [], + ); + return ( /* * The GooglePlacesAutocomplete component uses a VirtualizedList internally, @@ -378,12 +410,8 @@ function AddressSearch(props) { fetchDetails suppressDefaultStyles enablePoweredByContainer={false} - predefinedPlaces={props.predefinedPlaces} - listEmptyComponent={ - props.network.isOffline || !isTyping ? null : ( - {props.translate('common.noResultsFound')} - ) - } + predefinedPlaces={predefinedPlaces} + listEmptyComponent={listEmptyComponent} listLoaderComponent={listLoader} renderHeaderComponent={renderHeaderComponent} onPress={(data, details) => { @@ -400,34 +428,31 @@ function AddressSearch(props) { query={query} requestUrl={{ useOnPlatform: 'all', - url: props.network.isOffline ? null : ApiUtils.getCommandURL({command: 'Proxy_GooglePlaces&proxyUrl='}), + url: network.isOffline ? null : ApiUtils.getCommandURL({command: 'Proxy_GooglePlaces&proxyUrl='}), }} textInputProps={{ InputComp: TextInput, ref: (node) => { - if (!props.innerRef) { + if (!innerRef) { return; } - if (_.isFunction(props.innerRef)) { - props.innerRef(node); + if (_.isFunction(innerRef)) { + innerRef(node); return; } // eslint-disable-next-line no-param-reassign - props.innerRef.current = node; + innerRef.current = node; }, - label: props.label, - containerStyles: props.containerStyles, - errorText: props.errorText, - hint: - displayListViewBorder || (props.predefinedPlaces.length === 0 && shouldShowCurrentLocationButton) || (props.canUseCurrentLocation && isTyping) - ? undefined - : props.hint, - value: props.value, - defaultValue: props.defaultValue, - inputID: props.inputID, - shouldSaveDraft: props.shouldSaveDraft, + label, + containerStyles, + errorText, + hint: displayListViewBorder || (predefinedPlaces.length === 0 && shouldShowCurrentLocationButton) || (canUseCurrentLocation && isTyping) ? undefined : hint, + value, + defaultValue, + inputID, + shouldSaveDraft, onFocus: () => { setIsFocused(true); }, @@ -437,24 +462,24 @@ function AddressSearch(props) { setIsFocused(false); setIsTyping(false); } - props.onBlur(); + onBlur(); }, autoComplete: 'off', onInputChange: (text) => { setSearchValue(text); setIsTyping(true); - if (props.inputID) { - props.onInputChange(text); + if (inputID) { + onInputChange(text); } else { - props.onInputChange({street: text}); + onInputChange({street: text}); } // If the text is empty and we have no predefined places, we set displayListViewBorder to false to prevent UI flickering - if (_.isEmpty(text) && _.isEmpty(props.predefinedPlaces)) { + if (_.isEmpty(text) && _.isEmpty(predefinedPlaces)) { setDisplayListViewBorder(false); } }, - maxLength: props.maxInputLength, + maxLength: maxInputLength, spellCheck: false, }} styles={{ @@ -475,17 +500,18 @@ function AddressSearch(props) { }} inbetweenCompo={ // We want to show the current location button even if there are no recent destinations - props.predefinedPlaces.length === 0 && shouldShowCurrentLocationButton ? ( + predefinedPlaces.length === 0 && shouldShowCurrentLocationButton ? ( ) : ( <> ) } + placeholder="" /> setLocationErrorCode(null)} From 9d1cd9d436023ac07fde031abc93a3f2eecb54d3 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Thu, 19 Oct 2023 16:35:10 +0200 Subject: [PATCH 078/449] remove moment from datepicker --- src/CONST.ts | 1 - .../DatePicker/datepickerPropTypes.js | 4 +- src/components/DatePicker/index.android.js | 10 ++--- src/components/DatePicker/index.ios.js | 10 ++--- src/components/DatePicker/index.js | 12 +++--- .../NewDatePicker/CalendarPicker/index.js | 40 +++++++++---------- src/components/NewDatePicker/index.js | 10 ++--- src/libs/DateUtils.ts | 36 +++++++++++++++++ tests/unit/CalendarPickerTest.js | 11 +---- 9 files changed, 81 insertions(+), 53 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index bc74cbe77717..6a52563d238b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -131,7 +131,6 @@ const CONST = { DESKTOP: `${ACTIVE_EXPENSIFY_URL}NewExpensify.dmg`, }, DATE: { - MOMENT_FORMAT_STRING: 'YYYY-MM-DD', SQL_DATE_TIME: 'YYYY-MM-DD HH:mm:ss', FNS_FORMAT_STRING: 'yyyy-MM-dd', LOCAL_TIME_FORMAT: 'h:mm a', diff --git a/src/components/DatePicker/datepickerPropTypes.js b/src/components/DatePicker/datepickerPropTypes.js index 8bd5d890c42c..f896023d386b 100644 --- a/src/components/DatePicker/datepickerPropTypes.js +++ b/src/components/DatePicker/datepickerPropTypes.js @@ -6,13 +6,13 @@ const propTypes = { ...fieldPropTypes, /** - * The datepicker supports any value that `moment` can parse. + * The datepicker supports any value that `new Date()` can parse. * `onInputChange` would always be called with a Date (or null) */ value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]), /** - * The datepicker supports any defaultValue that `moment` can parse. + * The datepicker supports any defaultValue that `new Date()` can parse. * `onInputChange` would always be called with a Date (or null) */ defaultValue: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]), diff --git a/src/components/DatePicker/index.android.js b/src/components/DatePicker/index.android.js index 5bdda580d357..24faf2b19745 100644 --- a/src/components/DatePicker/index.android.js +++ b/src/components/DatePicker/index.android.js @@ -1,7 +1,7 @@ import React from 'react'; import {Keyboard} from 'react-native'; import RNDatePicker from '@react-native-community/datetimepicker'; -import moment from 'moment'; +import {format} from 'date-fns'; import _ from 'underscore'; import TextInput from '../TextInput'; import CONST from '../../CONST'; @@ -28,8 +28,7 @@ class DatePicker extends React.Component { this.setState({isPickerVisible: false}); if (event.type === 'set') { - const asMoment = moment(selectedDate, true); - this.props.onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING)); + this.props.onInputChange(format(selectedDate, CONST.DATE.FNS_FORMAT_STRING)); } } @@ -39,7 +38,8 @@ class DatePicker extends React.Component { } render() { - const dateAsText = this.props.value || this.props.defaultValue ? moment(this.props.value || this.props.defaultValue).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; + const date = this.props.value || this.props.defaultValue; + const dateAsText = date ? format(new Date(date), CONST.DATE.FNS_FORMAT_STRING) : ''; return ( <> @@ -73,7 +73,7 @@ class DatePicker extends React.Component { /> {this.state.isPickerVisible && ( { setIsPickerVisible(false); - const asMoment = moment(selectedDate, true); - onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING)); + onInputChange(format(selectedDate, CONST.DATE.FNS_FORMAT_STRING)); }; /** @@ -77,7 +77,7 @@ function DatePicker({value, defaultValue, innerRef, onInputChange, preferredLoca setSelectedDate(date); }; - const dateAsText = value || defaultValue ? moment(value || defaultValue).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; + const dateAsText = dateValue ? format(new Date(dateValue), CONST.DATE.FNS_FORMAT_STRING) : ''; return ( <> diff --git a/src/components/DatePicker/index.js b/src/components/DatePicker/index.js index d14886fd1c59..e0672f847295 100644 --- a/src/components/DatePicker/index.js +++ b/src/components/DatePicker/index.js @@ -1,5 +1,5 @@ import React, {useEffect, useRef} from 'react'; -import moment from 'moment'; +import {format, isValid} from 'date-fns'; import _ from 'underscore'; import TextInput from '../TextInput'; import CONST from '../../CONST'; @@ -13,8 +13,8 @@ function DatePicker({maxDate, minDate, onInputChange, innerRef, label, value, pl useEffect(() => { // Adds nice native datepicker on web/desktop. Not possible to set this through props inputRef.current.setAttribute('type', 'date'); - inputRef.current.setAttribute('max', moment(maxDate).format(CONST.DATE.MOMENT_FORMAT_STRING)); - inputRef.current.setAttribute('min', moment(minDate).format(CONST.DATE.MOMENT_FORMAT_STRING)); + inputRef.current.setAttribute('max', format(new Date(maxDate), CONST.DATE.FNS_FORMAT_STRING)); + inputRef.current.setAttribute('min', format(new Date(minDate), CONST.DATE.FNS_FORMAT_STRING)); inputRef.current.classList.add('expensify-datepicker'); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -29,9 +29,9 @@ function DatePicker({maxDate, minDate, onInputChange, innerRef, label, value, pl return; } - const asMoment = moment(text, true); - if (asMoment.isValid()) { - onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING)); + const date = new Date(text); + if (isValid(date)) { + onInputChange(format(date, CONST.DATE.FNS_FORMAT_STRING)); } }; diff --git a/src/components/NewDatePicker/CalendarPicker/index.js b/src/components/NewDatePicker/CalendarPicker/index.js index d03c36997845..67b3ef3aa91a 100644 --- a/src/components/NewDatePicker/CalendarPicker/index.js +++ b/src/components/NewDatePicker/CalendarPicker/index.js @@ -1,7 +1,7 @@ import _ from 'underscore'; import React from 'react'; import {View} from 'react-native'; -import moment from 'moment'; +import {setYear, format, getYear, subMonths, addMonths, startOfDay, endOfMonth, setDate, isSameDay} from 'date-fns'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; import Text from '../../Text'; @@ -11,6 +11,7 @@ import styles from '../../../styles/styles'; import generateMonthMatrix from './generateMonthMatrix'; import withLocalize, {withLocalizePropTypes} from '../../withLocalize'; import CONST from '../../../CONST'; +import DateUtils from '../../../libs/DateUtils'; import getButtonState from '../../../libs/getButtonState'; import * as StyleUtils from '../../../styles/StyleUtils'; import PressableWithFeedback from '../../Pressable/PressableWithFeedback'; @@ -34,8 +35,8 @@ const propTypes = { const defaultProps = { value: new Date(), - minDate: moment().year(CONST.CALENDAR_PICKER.MIN_YEAR).toDate(), - maxDate: moment().year(CONST.CALENDAR_PICKER.MAX_YEAR).toDate(), + minDate: setYear(new Date(), CONST.CALENDAR_PICKER.MIN_YEAR), + maxDate: setYear(new Date(), CONST.CALENDAR_PICKER.MAX_YEAR), onSelected: () => {}, }; @@ -46,16 +47,15 @@ class CalendarPicker extends React.PureComponent { if (props.minDate >= props.maxDate) { throw new Error('Minimum date cannot be greater than the maximum date.'); } - - let currentDateView = moment(props.value, CONST.DATE.MOMENT_FORMAT_STRING).toDate(); + let currentDateView = new Date(props.value); if (props.maxDate < currentDateView) { currentDateView = props.maxDate; } else if (props.minDate > currentDateView) { currentDateView = props.minDate; } - const minYear = moment(this.props.minDate).year(); - const maxYear = moment(this.props.maxDate).year(); + const minYear = getYear(new Date(this.props.minDate)); + const maxYear = getYear(new Date(this.props.maxDate)); this.state = { currentDateView, @@ -79,7 +79,7 @@ class CalendarPicker extends React.PureComponent { onYearSelected(year) { this.setState((prev) => { - const newCurrentDateView = moment(prev.currentDateView).set('year', year).toDate(); + const newCurrentDateView = setYear(new Date(prev.currentDateView), year); return { currentDateView: newCurrentDateView, @@ -99,9 +99,9 @@ class CalendarPicker extends React.PureComponent { onDayPressed(day) { this.setState( (prev) => ({ - currentDateView: moment(prev.currentDateView).set('date', day).toDate(), + currentDateView: setDate(new Date(prev.currentDateView), day), }), - () => this.props.onSelected(moment(this.state.currentDateView).format('YYYY-MM-DD')), + () => this.props.onSelected(format(new Date(this.state.currentDateView), CONST.DATE.FNS_FORMAT_STRING)), ); } @@ -109,24 +109,24 @@ class CalendarPicker extends React.PureComponent { * Handles the user pressing the previous month arrow of the calendar picker. */ moveToPrevMonth() { - this.setState((prev) => ({currentDateView: moment(prev.currentDateView).subtract(1, 'months').toDate()})); + this.setState((prev) => ({currentDateView: subMonths(new Date(prev.currentDateView), 1)})); } /** * Handles the user pressing the next month arrow of the calendar picker. */ moveToNextMonth() { - this.setState((prev) => ({currentDateView: moment(prev.currentDateView).add(1, 'months').toDate()})); + this.setState((prev) => ({currentDateView: addMonths(new Date(prev.currentDateView), 1)})); } render() { - const monthNames = _.map(moment.localeData(this.props.preferredLocale).months(), Str.recapitalize); - const daysOfWeek = _.map(moment.localeData(this.props.preferredLocale).weekdays(), (day) => day.toUpperCase()); + const monthNames = _.map(DateUtils.getMonthNames(this.props.preferredLocale), Str.recapitalize); + const daysOfWeek = _.map(DateUtils.getDaysOfWeek(this.props.preferredLocale), (day) => day.toUpperCase()); const currentMonthView = this.state.currentDateView.getMonth(); const currentYearView = this.state.currentDateView.getFullYear(); const calendarDaysMatrix = generateMonthMatrix(currentYearView, currentMonthView); - const hasAvailableDatesNextMonth = moment(this.props.maxDate).endOf('month').endOf('day') >= moment(this.state.currentDateView).add(1, 'months'); - const hasAvailableDatesPrevMonth = moment(this.props.minDate).startOf('month').startOf('day') <= moment(this.state.currentDateView).subtract(1, 'months'); + const hasAvailableDatesNextMonth = startOfDay(endOfMonth(new Date(this.props.maxDate))) > addMonths(new Date(this.state.currentDateView), 1); + const hasAvailableDatesPrevMonth = startOfDay(new Date(this.props.minDate)) < endOfMonth(subMonths(new Date(this.state.currentDateView), 1)); return ( @@ -201,11 +201,11 @@ class CalendarPicker extends React.PureComponent { style={styles.flexRow} > {_.map(week, (day, index) => { - const currentDate = moment([currentYearView, currentMonthView, day]); - const isBeforeMinDate = currentDate < moment(this.props.minDate).startOf('day'); - const isAfterMaxDate = currentDate > moment(this.props.maxDate).startOf('day'); + const currentDate = new Date(currentYearView, currentMonthView, day); + const isBeforeMinDate = currentDate < startOfDay(new Date(this.props.minDate)); + const isAfterMaxDate = currentDate > startOfDay(new Date(this.props.maxDate)); const isDisabled = !day || isBeforeMinDate || isAfterMaxDate; - const isSelected = moment(this.props.value).isSame(moment([currentYearView, currentMonthView, day]), 'day'); + const isSelected = isSameDay(new Date(this.props.value), new Date(currentYearView, currentMonthView, day)); return ( { return timezone; } +/** + * @returns [January, Fabruary, March, April, May, June, July, August, ...] + */ +function getMonthNames(preferredLocale: string): string[] { + if (preferredLocale) { + setLocale(preferredLocale); + } + const fullYear = new Date().getFullYear(); + const monthsArray = eachMonthOfInterval({ + start: new Date(fullYear, 0, 1), // January 1st of the current year + end: new Date(fullYear, 11, 31), // December 31st of the current year + }); + + // eslint-disable-next-line rulesdir/prefer-underscore-method + return monthsArray.map((monthDate) => format(monthDate, CONST.DATE.MONTH_FORMAT)); +} + +/** + * @returns [Monday, Thuesday, Wednesday, ...] + */ +function getDaysOfWeek(preferredLocale: string): string[] { + if (preferredLocale) { + setLocale(preferredLocale); + } + const startOfCurrentWeek = startOfWeek(new Date(), {weekStartsOn: 1}); // Assuming Monday is the start of the week + const endOfCurrentWeek = endOfWeek(new Date(), {weekStartsOn: 1}); // Assuming Monday is the start of the week + const daysOfWeek = eachDayOfInterval({start: startOfCurrentWeek, end: endOfCurrentWeek}); + + // eslint-disable-next-line rulesdir/prefer-underscore-method + return daysOfWeek.map((date) => format(date, 'eeee')); +} + // Used to throttle updates to the timezone when necessary let lastUpdatedTimezoneTime = new Date(); @@ -357,6 +391,8 @@ const DateUtils = { isToday, isTomorrow, isYesterday, + getMonthNames, + getDaysOfWeek, }; export default DateUtils; diff --git a/tests/unit/CalendarPickerTest.js b/tests/unit/CalendarPickerTest.js index 512a86a25e19..235dff45f631 100644 --- a/tests/unit/CalendarPickerTest.js +++ b/tests/unit/CalendarPickerTest.js @@ -1,17 +1,10 @@ import {render, fireEvent, within} from '@testing-library/react-native'; -import {format, eachMonthOfInterval, subYears, addYears} from 'date-fns'; +import {subYears, addYears} from 'date-fns'; import DateUtils from '../../src/libs/DateUtils'; import CalendarPicker from '../../src/components/NewDatePicker/CalendarPicker'; import CONST from '../../src/CONST'; -DateUtils.setLocale(CONST.LOCALES.EN); -const fullYear = new Date().getFullYear(); -const monthsArray = eachMonthOfInterval({ - start: new Date(fullYear, 0, 1), // January 1st of the current year - end: new Date(fullYear, 11, 31), // December 31st of the current year -}); -// eslint-disable-next-line rulesdir/prefer-underscore-method -const monthNames = monthsArray.map((monthDate) => format(monthDate, CONST.DATE.MONTH_FORMAT)); +const monthNames = DateUtils.getMonthNames(CONST.LOCALES.EN); jest.mock('@react-navigation/native', () => ({ useNavigation: () => ({navigate: jest.fn()}), From 8d4678fa56bb60d5c5cf9e4371ffe56f1e2d924b Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Thu, 19 Oct 2023 16:37:39 +0200 Subject: [PATCH 079/449] Drop func from possible types --- src/components/FullscreenLoadingIndicator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FullscreenLoadingIndicator.tsx b/src/components/FullscreenLoadingIndicator.tsx index 3f5f62f533d7..92e59d0e2d1c 100644 --- a/src/components/FullscreenLoadingIndicator.tsx +++ b/src/components/FullscreenLoadingIndicator.tsx @@ -4,7 +4,7 @@ import styles from '../styles/styles'; import themeColors from '../styles/themes/default'; type FullScreenLoadingIndicatorProps = { - style: Record | Array> | (() => void); + style: Record | Array>; }; function FullScreenLoadingIndicator({style = []}: FullScreenLoadingIndicatorProps) { From 7ae4259e4167e62ee71ea30ea2a70dcf633cddb7 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 19 Oct 2023 17:35:05 +0200 Subject: [PATCH 080/449] Adjust the code after review --- src/pages/workspace/withPolicy.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index f168061f5fea..23473ec726dc 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -62,13 +62,6 @@ const policyPropTypes = { policyMembers: PropTypes.objectOf(policyMemberPropType), }; -const policyDefaultProps: WithPolicyOnyxProps = { - policy: {} as OnyxTypes.Policy, - policyMembers: {}, - policyDraft: {} as OnyxTypes.Policy, - policyMembersDraft: {}, -}; - type WithPolicyOnyxProps = { policy: OnyxEntry; policyMembers: OnyxEntry; @@ -79,6 +72,14 @@ type WithPolicyOnyxProps = { type WithPolicyProps = WithPolicyOnyxProps & { route: PolicyRoute; }; + +const policyDefaultProps: WithPolicyOnyxProps = { + policy: {} as OnyxTypes.Policy, + policyMembers: {}, + policyDraft: {} as OnyxTypes.Policy, + policyMembersDraft: {}, +}; + /* * HOC for connecting a policy in Onyx corresponding to the policyID in route params */ @@ -90,7 +91,7 @@ export default function withPolicy( const currentRoute = routes?.[routes.length - 1]; const policyID = getPolicyIDFromRoute(currentRoute as PolicyRoute); - if (typeof policyID === 'string' && policyID.length > 0) { + if (policyID.length > 0) { Policy.updateLastAccessedWorkspace(policyID); } From 64dbcd48507efa6329be72a437f85fabed6e31a6 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 17:45:01 +0200 Subject: [PATCH 081/449] Lint fix --- src/components/AddressSearch/index.js | 2 +- src/pages/settings/Wallet/AddDebitCardPage.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index db8d6b6bae42..7c754a33e370 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import PropTypes from 'prop-types'; import {ActivityIndicator, Keyboard, LogBox, ScrollView, Text, View} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index b0196e8e620e..b9ea0ac6df3a 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -16,7 +16,6 @@ import TextInput from '../../../components/TextInput'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; import AddressSearch from '../../../components/AddressSearch'; -import Form from '../../../components/Form'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import usePrevious from '../../../hooks/usePrevious'; From cf90f7bd132d9562b80a09a6e027be38a8fd6d9a Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 19 Oct 2023 09:47:00 -0600 Subject: [PATCH 082/449] Negate logic for the referenced variable --- src/libs/SidebarUtils.js | 2 +- src/pages/home/report/ReportActionsList.js | 2 +- src/pages/home/report/ReportFooter.js | 2 +- src/pages/iou/steps/NewRequestAmountPage.js | 2 +- src/pages/tasks/TaskShareDestinationSelectorModal.js | 2 +- tests/unit/OptionsListUtilsTest.js | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index 2c679ef3cda7..6eb07c6b19e0 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -305,7 +305,7 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, result.parentReportID = report.parentReportID || null; result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; result.notificationPreference = report.notificationPreference || null; - result.isAllowedToComment = !ReportUtils.canUserPerformWriteAction(report); + result.isAllowedToComment = ReportUtils.canUserPerformWriteAction(report); const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; const subtitle = ReportUtils.getChatRoomSubtitle(report); diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index e986c7b53528..32499ebc4090 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -334,7 +334,7 @@ function ReportActionsList({ // Native mobile does not render updates flatlist the changes even though component did update called. // To notify there something changes we can use extraData prop to flatlist const extraData = [isSmallScreenWidth ? currentUnreadMarker : undefined, ReportUtils.isArchivedRoom(report)]; - const hideComposer = ReportUtils.canUserPerformWriteAction(report); + const hideComposer = !ReportUtils.canUserPerformWriteAction(report); const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(personalDetailsList, report, currentUserPersonalDetails.accountID) && !isComposerFullSize; const renderFooter = useCallback(() => { diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index c96824770f07..754fae8d670a 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -70,7 +70,7 @@ function ReportFooter(props) { const isAnonymousUser = Session.isAnonymousUser(); const isSmallSizeLayout = props.windowWidth - (props.isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; - const hideComposer = ReportUtils.canUserPerformWriteAction(props.report); + const hideComposer = !ReportUtils.canUserPerformWriteAction(props.report); return ( <> diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index dda52352e015..c624973a0189 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -85,7 +85,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { // Check and dismiss modal useEffect(() => { - if (!ReportUtils.canUserPerformWriteAction(report)) { + if (ReportUtils.canUserPerformWriteAction(report)) { return; } Navigation.dismissModal(reportID); diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index f1cd009d197d..32e02e7ba42e 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -52,7 +52,7 @@ function TaskShareDestinationSelectorModal(props) { const reports = {}; _.keys(props.reports).forEach((reportKey) => { if ( - ReportUtils.canUserPerformWriteAction(props.reports[reportKey]) || + !ReportUtils.canUserPerformWriteAction(props.reports[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(props.reports[reportKey]) || ReportUtils.isCanceledTaskReport(props.reports[reportKey]) ) { diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 0032f6a1ad42..50edfa7f0522 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -590,7 +590,7 @@ describe('OptionsListUtils', () => { // Filter current REPORTS as we do in the component, before getting share destination options const filteredReports = {}; _.keys(REPORTS).forEach((reportKey) => { - if (ReportUtils.canUserPerformWriteAction(REPORTS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS[reportKey])) { + if (!ReportUtils.canUserPerformWriteAction(REPORTS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS[reportKey])) { return; } filteredReports[reportKey] = REPORTS[reportKey]; @@ -617,7 +617,7 @@ describe('OptionsListUtils', () => { // Filter current REPORTS_WITH_WORKSPACE_ROOMS as we do in the component, before getting share destination options const filteredReportsWithWorkspaceRooms = {}; _.keys(REPORTS_WITH_WORKSPACE_ROOMS).forEach((reportKey) => { - if (ReportUtils.canUserPerformWriteAction(REPORTS_WITH_WORKSPACE_ROOMS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS_WITH_WORKSPACE_ROOMS[reportKey])) { + if (!ReportUtils.canUserPerformWriteAction(REPORTS_WITH_WORKSPACE_ROOMS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS_WITH_WORKSPACE_ROOMS[reportKey])) { return; } filteredReportsWithWorkspaceRooms[reportKey] = REPORTS_WITH_WORKSPACE_ROOMS[reportKey]; From 7ffe27e996defa5cfce388dbcec6f5c19935bf3a Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 19 Oct 2023 18:45:19 +0200 Subject: [PATCH 083/449] Fix forwardRef error --- src/pages/workspace/withPolicy.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index 23473ec726dc..2e55230a0a33 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -104,7 +104,6 @@ export default function withPolicy( ); } - WithPolicy.defaultProps = policyDefaultProps; WithPolicy.displayName = `withPolicy(${getComponentDisplayName(WrappedComponent)})`; return withOnyx, WithPolicyOnyxProps>({ From ba7bd1714b873326c7bda6a75e29c33473100e40 Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Thu, 19 Oct 2023 15:45:01 -0400 Subject: [PATCH 084/449] Allow requesting money when expensify owns workspace --- src/libs/ReportUtils.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 1b03c3a1bdb2..3e0b8962bf37 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3515,15 +3515,18 @@ function getMoneyRequestOptions(report, reportParticipants) { const participants = _.filter(reportParticipants, (accountID) => currentUserPersonalDetails.accountID !== accountID); - // Verify if there is any of the expensify accounts amongst the participants in which case user cannot take IOU actions on such report - const hasExcludedIOUAccountIDs = lodashIntersection(reportParticipants, CONST.EXPENSIFY_ACCOUNT_IDS).length > 0; - const hasSingleParticipantInReport = participants.length === 1; - const hasMultipleParticipants = participants.length > 1; - - if (hasExcludedIOUAccountIDs) { + // We don't allow IOU actions if an Expensify account is a participant of the report, unless the policy that the report is on is owned by an Expensify account + const doParticipantsIncludeExpensifyAccounts = lodashIntersection(reportParticipants, CONST.EXPENSIFY_ACCOUNT_IDS).length > 0; + const policyID = lodashGet(report, 'policyID', ''); + const policyOwnerAccountID = lodashGet(allPolicies, `${ONYXKEYS.COLLECTION.POLICY}${policyID}.ownerAccountID`, 0); + const doExpensifyAccountsOwnPolicy = CONST.EXPENSIFY_ACCOUNT_IDS.includes(policyOwnerAccountID); + if (doParticipantsIncludeExpensifyAccounts && !doExpensifyAccountsOwnPolicy) { return []; } + const hasSingleParticipantInReport = participants.length === 1; + const hasMultipleParticipants = participants.length > 1; + // User created policy rooms and default rooms like #admins or #announce will always have the Split Bill option // unless there are no participants at all (e.g. #admins room for a policy with only 1 admin) // DM chats will have the Split Bill option only when there are at least 3 people in the chat. From ce653159b75e12465402d199712bd2e02e9f4a37 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 20 Oct 2023 14:30:33 +0700 Subject: [PATCH 085/449] fix error when open sign in modal --- .../HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js | 5 +++++ src/libs/Navigation/NavigationRoot.js | 8 -------- src/libs/actions/Report.js | 8 +++++++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js index 92a313cf1e0a..5b15d7014d1f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js @@ -15,6 +15,7 @@ import * as Url from '../../../libs/Url'; import ROUTES from '../../../ROUTES'; import tryResolveUrlFromApiRoot from '../../../libs/tryResolveUrlFromApiRoot'; import useEnvironment from '../../../hooks/useEnvironment'; +import * as Session from '../../../libs/actions/Session'; function AnchorRenderer(props) { const htmlAttribs = props.tnode.attributes; @@ -52,6 +53,10 @@ function AnchorRenderer(props) { // If we are handling a New Expensify link then we will assume this should be opened by the app internally. This ensures that the links are opened internally via react-navigation // instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag) if (internalNewExpensifyPath && hasSameOrigin) { + if (Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(internalNewExpensifyPath)) { + Session.signOutAndRedirectToSignIn(); + return; + } Navigation.navigate(internalNewExpensifyPath); return; } diff --git a/src/libs/Navigation/NavigationRoot.js b/src/libs/Navigation/NavigationRoot.js index a22b6714a306..c7a3b14e4fb0 100644 --- a/src/libs/Navigation/NavigationRoot.js +++ b/src/libs/Navigation/NavigationRoot.js @@ -12,9 +12,6 @@ import StatusBar from '../StatusBar'; import useCurrentReportID from '../../hooks/useCurrentReportID'; import useWindowDimensions from '../../hooks/useWindowDimensions'; import {SidebarNavigationContext} from '../../pages/home/sidebar/SidebarNavigationContext'; -import * as Session from '../actions/Session'; -import getCurrentUrl from './currentUrl'; -import ROUTES from '../../ROUTES'; // https://reactnavigation.org/docs/themes const navigationTheme = { @@ -136,11 +133,6 @@ function NavigationRoot(props) { // Update the global navigation to show the correct selected menu items. globalNavigation.updateFromNavigationState(state); - - const route = Navigation.getActiveRoute(); - if (Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(route) && !getCurrentUrl().includes(ROUTES.SIGN_IN_MODAL)) { - Session.signOutAndRedirectToSignIn(); - } }; return ( diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index af1b4a0ac1dd..d3e2f9c749d2 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1909,7 +1909,13 @@ function openReportFromDeepLink(url, isAuthenticated) { InteractionManager.runAfterInteractions(() => { Session.waitForUserSignIn().then(() => { if (route === ROUTES.CONCIERGE) { - navigateToConciergeChat(true); + navigateToConciergeChat(); + return; + } + if (Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(route)) { + Navigation.isNavigationReady().then(() => { + Session.signOutAndRedirectToSignIn(); + }); return; } Navigation.navigate(route, CONST.NAVIGATION.TYPE.PUSH); From ced932a1d2c8af33881b72772bec64293b6a014a Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 20 Oct 2023 14:36:09 +0700 Subject: [PATCH 086/449] fix revert not related changes --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index d3e2f9c749d2..51dcdc49847d 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1909,7 +1909,7 @@ function openReportFromDeepLink(url, isAuthenticated) { InteractionManager.runAfterInteractions(() => { Session.waitForUserSignIn().then(() => { if (route === ROUTES.CONCIERGE) { - navigateToConciergeChat(); + navigateToConciergeChat(true); return; } if (Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(route)) { From dfddd3300c375d738e491fe71db1f2cb4d6afadb Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 20 Oct 2023 14:40:30 +0700 Subject: [PATCH 087/449] fix remove not related change --- src/libs/ReportUtils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 88261a0061ea..011907c2c88b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -4065,6 +4065,5 @@ export { getIOUReportActionDisplayMessage, isWaitingForTaskCompleteFromAssignee, isReportDraft, - parseReportRouteParams, shouldUseFullTitleToDisplay, }; From 7ca29f856c41305baebae957112b9e7ce7089d02 Mon Sep 17 00:00:00 2001 From: sarious Date: Fri, 20 Oct 2023 13:07:45 +0400 Subject: [PATCH 088/449] Fix 17866: After pressing the back arrow to "Send/Request money" screen, the keyboard flashes --- src/components/ScreenWrapper/index.js | 6 ++- src/hooks/useInitialWindowDimensions/index.js | 50 +++++++++++++++++++ .../index.native.js | 50 +++++++++++++++++++ src/pages/iou/MoneyRequestSelectorPage.js | 2 + 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useInitialWindowDimensions/index.js create mode 100644 src/hooks/useInitialWindowDimensions/index.native.js diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index e2af40589a8a..ecb83c28759e 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -16,12 +16,14 @@ import toggleTestToolsModal from '../../libs/actions/TestTool'; import CustomDevMenu from '../CustomDevMenu'; import * as Browser from '../../libs/Browser'; import useWindowDimensions from '../../hooks/useWindowDimensions'; +import useInitialDimensions from '../../hooks/useInitialWindowDimensions'; import useKeyboardState from '../../hooks/useKeyboardState'; import useEnvironment from '../../hooks/useEnvironment'; import useNetwork from '../../hooks/useNetwork'; function ScreenWrapper({ shouldEnableMaxHeight, + shouldEnableMinHeight, includePaddingTop, keyboardAvoidingViewBehavior, includeSafeAreaPaddingBottom, @@ -37,12 +39,14 @@ function ScreenWrapper({ testID, }) { const {windowHeight, isSmallScreenWidth} = useWindowDimensions(); + const {initialHeight} = useInitialDimensions(); const keyboardState = useKeyboardState(); const {isDevelopment} = useEnvironment(); const {isOffline} = useNetwork(); const navigation = useNavigation(); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const maxHeight = shouldEnableMaxHeight ? windowHeight : undefined; + const minHeight = shouldEnableMinHeight ? initialHeight : undefined; const isKeyboardShown = lodashGet(keyboardState, 'isKeyboardShown', false); const panResponder = useRef( @@ -125,7 +129,7 @@ function ScreenWrapper({ {...keyboardDissmissPanResponder.panHandlers} > diff --git a/src/hooks/useInitialWindowDimensions/index.js b/src/hooks/useInitialWindowDimensions/index.js new file mode 100644 index 000000000000..ae498f45377f --- /dev/null +++ b/src/hooks/useInitialWindowDimensions/index.js @@ -0,0 +1,50 @@ +// eslint-disable-next-line no-restricted-imports +import {useState, useEffect} from 'react'; +import {Dimensions} from 'react-native'; + +export default function () { + const [dimensions, setDimensions] = useState(() => { + const window = Dimensions.get('window'); + const screen = Dimensions.get('screen'); + + return { + screenHeight: screen.height, + screenWidth: screen.width, + initialHeight: window.height, + initialWidth: window.width, + }; + }); + + useEffect(() => { + const onDimensionChange = (newDimensions) => { + const {window, screen} = newDimensions; + + setDimensions((oldState) => { + if (screen.width !== oldState.screenWidth || screen.height !== oldState.screenHeight || window.height > oldState.initialHeight) { + return { + initialHeight: window.height, + initialWidth: window.width, + screenHeight: screen.height, + screenWidth: screen.width, + }; + } + + return oldState; + }); + }; + + const dimensionsEventListener = Dimensions.addEventListener('change', onDimensionChange); + + return () => { + if (!dimensionsEventListener) { + return; + } + dimensionsEventListener.remove(); + }; + }, []); + + return { + initialWidth: dimensions.initialWidth, + initialHeight: dimensions.initialHeight, + }; +} diff --git a/src/hooks/useInitialWindowDimensions/index.native.js b/src/hooks/useInitialWindowDimensions/index.native.js new file mode 100644 index 000000000000..ae498f45377f --- /dev/null +++ b/src/hooks/useInitialWindowDimensions/index.native.js @@ -0,0 +1,50 @@ +// eslint-disable-next-line no-restricted-imports +import {useState, useEffect} from 'react'; +import {Dimensions} from 'react-native'; + +export default function () { + const [dimensions, setDimensions] = useState(() => { + const window = Dimensions.get('window'); + const screen = Dimensions.get('screen'); + + return { + screenHeight: screen.height, + screenWidth: screen.width, + initialHeight: window.height, + initialWidth: window.width, + }; + }); + + useEffect(() => { + const onDimensionChange = (newDimensions) => { + const {window, screen} = newDimensions; + + setDimensions((oldState) => { + if (screen.width !== oldState.screenWidth || screen.height !== oldState.screenHeight || window.height > oldState.initialHeight) { + return { + initialHeight: window.height, + initialWidth: window.width, + screenHeight: screen.height, + screenWidth: screen.width, + }; + } + + return oldState; + }); + }; + + const dimensionsEventListener = Dimensions.addEventListener('change', onDimensionChange); + + return () => { + if (!dimensionsEventListener) { + return; + } + dimensionsEventListener.remove(); + }; + }, []); + + return { + initialWidth: dimensions.initialWidth, + initialHeight: dimensions.initialHeight, + }; +} diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 0786faa3841b..bcf75bd64056 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -22,6 +22,7 @@ import NewRequestAmountPage from './steps/NewRequestAmountPage'; import reportPropTypes from '../reportPropTypes'; import * as ReportUtils from '../../libs/ReportUtils'; import usePrevious from '../../hooks/usePrevious'; +import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; const propTypes = { /** React Navigation route */ @@ -85,6 +86,7 @@ function MoneyRequestSelectorPage(props) { From 4fca0496676475b9c3725bd455364e850442a830 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Fri, 20 Oct 2023 14:13:05 +0200 Subject: [PATCH 089/449] clean up show GBR --- src/components/LHNOptionsList/OptionRowLHN.js | 2 +- src/libs/ReportUtils.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index 064c1c146163..ae9a6da40c5d 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -116,7 +116,7 @@ function OptionRowLHN(props) { const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; const defaultSubscriptSize = optionItem.isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; - const shouldShowGreenDotIndicator = !hasBrickError && (optionItem.isUnreadWithMention || optionItem.isWaitingForTaskCompleteFromAssignee || ReportUtils.shouldShowGBR(optionItem)); + const shouldShowGreenDotIndicator = !hasBrickError && ReportUtils.shouldShowGBR(optionItem); /** * Show the ReportActionContextMenu modal popover. diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index c8339810cb1f..a6578a320f39 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1227,6 +1227,14 @@ function shouldShowGBR(report) { return false; } + if (report.isUnreadWithMention) { + return true; + } + + if (report.isWaitingForTaskCompleteFromAssignee) { + return true; + } + // Money request waiting for current user to add their credit bank account if (report.hasOutstandingIOU && report.ownerAccountID === currentUserAccountID && report.isWaitingOnBankAccount) { return true; From 7058006baf3696aa5a0b19e6e0c7dd1c6b78786b Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Oct 2023 14:26:38 +0200 Subject: [PATCH 090/449] Improve PolicyRoute type --- src/pages/workspace/withPolicy.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index 2e55230a0a33..7bb9db9ad8e0 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -1,7 +1,7 @@ import React, {ComponentType, ForwardedRef, RefAttributes, forwardRef} from 'react'; import PropTypes from 'prop-types'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; -import {useNavigationState} from '@react-navigation/native'; +import {RouteProp, useNavigationState} from '@react-navigation/native'; import CONST from '../../CONST'; import getComponentDisplayName from '../../libs/getComponentDisplayName'; import * as Policy from '../../libs/actions/Policy'; @@ -9,11 +9,7 @@ import ONYXKEYS from '../../ONYXKEYS'; import policyMemberPropType from '../policyMemberPropType'; import * as OnyxTypes from '../../types/onyx'; -type PolicyRoute = { - params?: { - policyID: string; - }; -}; +type PolicyRoute = RouteProp<{params: {policyID: string}}>; function getPolicyIDFromRoute(route: PolicyRoute): string { return route?.params?.policyID ?? ''; From 196689db016c212d18b43e13b8316a17cdbc6193 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 20 Oct 2023 16:35:47 +0200 Subject: [PATCH 091/449] Fix propType --- src/components/CheckboxWithLabel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js index 777c46f861aa..70fa972a5d2f 100644 --- a/src/components/CheckboxWithLabel.js +++ b/src/components/CheckboxWithLabel.js @@ -8,6 +8,7 @@ import Text from './Text'; import FormHelpMessage from './FormHelpMessage'; import variables from '../styles/variables'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; +import refPropTypes from "./refPropTypes"; /** * Returns an error if the required props are not provided @@ -54,7 +55,7 @@ const propTypes = { defaultValue: PropTypes.bool, /** React ref being forwarded to the Checkbox input */ - forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), + forwardedRef: refPropTypes, /** The ID used to uniquely identify the input in a Form */ /* eslint-disable-next-line react/no-unused-prop-types */ From 4d7989493d72a2bc9ef756bb6eb00461415d31a2 Mon Sep 17 00:00:00 2001 From: AmjedNazzal Date: Fri, 20 Oct 2023 17:39:17 +0300 Subject: [PATCH 092/449] Issue28820 --- src/libs/Clipboard/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Clipboard/index.js b/src/libs/Clipboard/index.js index 6fbaa8eccd31..2f7b647d9eeb 100644 --- a/src/libs/Clipboard/index.js +++ b/src/libs/Clipboard/index.js @@ -62,7 +62,7 @@ function setHTMLSync(html, text) { if (isComposer) { firstAnchorChild.setSelectionRange(originalSelection.start, originalSelection.end, originalSelection.direction); - } else { + } else if (originalSelection.anchorNode && originalSelection.focusNode) { selection.setBaseAndExtent(originalSelection.anchorNode, originalSelection.anchorOffset, originalSelection.focusNode, originalSelection.focusOffset); } From bd92d7e6503bdf413bedfc8e216d74a814e92b68 Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Fri, 20 Oct 2023 12:12:55 -0400 Subject: [PATCH 093/449] Allow assigning task in workspace chat where expensify is owner --- .../report/ReportActionCompose/AttachmentPickerWithMenuItems.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 6522bedc825a..d3be3f47bf2f 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -152,7 +152,7 @@ function AttachmentPickerWithMenuItems({ */ const taskOption = useMemo(() => { // We only prevent the task option from showing if it's a DM and the other user is an Expensify default email - if (!Permissions.canUseTasks(betas) || ReportUtils.isExpensifyOnlyParticipantInReport(report)) { + if (!Permissions.canUseTasks(betas) || (!ReportUtils.isPolicyExpenseChat(report) && ReportUtils.isExpensifyOnlyParticipantInReport(report))) { return []; } From 0daa9f1dcc04a37f8516141ad25584f2fc2e8b82 Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Sat, 21 Oct 2023 09:46:45 +0530 Subject: [PATCH 094/449] fix: console error old-dot to new-dot new workspace transition --- .../Navigators/CentralPaneNavigator.js | 2 +- src/libs/ReportUtils.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js index 64eadcbe06c3..9c2bd6820e85 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js @@ -18,7 +18,7 @@ function CentralPaneNavigator() { Date: Sun, 22 Oct 2023 23:11:08 +0000 Subject: [PATCH 095/449] Update Auto-Reconciliation.md Updating image placeholders with actual image URLs --- .../expensify-card/Auto-Reconciliation.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md b/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md index 059877a18075..824f01f688b3 100644 --- a/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md +++ b/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md @@ -35,6 +35,8 @@ To set up your auto-reconciliation account with the Expensify Card, follow these 5. Head to the "Settings" tab. 6. Select the account in your accounting solution that you want to use for reconciliation. Make sure this account matches the settlement business bank account. +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/Auto-Reconciliaton_Image1.png){:width="100%"} + That's it! You've successfully set up your auto-reconciliation account. ## How does Auto-Reconciliation work @@ -44,13 +46,11 @@ Once Auto-Reconciliation is enabled, there are a few things that happen. Let’s **What happens**: When an Expensify Card is used to make purchases, the amount spent is automatically deducted from your company’s 'Settlement Account' (your business checking account). This deduction happens on a daily or monthly basis, depending on your chosen settlement frequency. Don't worry; this settlement account is pre-defined when you apply for the Expensify Card, and you can't accidentally change it. **Accounting treatment**: After your card balance is settled each day, we update your accounting system with a journal entry. This entry credits your bank account (referred to as the GL account) and debits the Expensify Card Clearing Account. To ensure accuracy, please make sure that the 'bank account' in your Expensify Card settings matches your real-life settlement account. You can easily verify this by navigating to **Settings > Account > Payments**, where you'll see 'Settlement Account' next to your business bank account. To keep track of settlement figures by date, use the Company Card Reconciliation Dashboard's Settlements tab: -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/Auto-Reconciliation_Image2.png){:width="100%"} ### Submitting, Approving, and Exporting Expenses **What happens**: Users submit their expenses on a report, which might occur after some time has passed since the initial purchase. Once the report is approved, it's then exported to your accounting software. -**Accounting treatment**: When the report is exported, we create a journal entry in your accounting system. This entry credits the Clearing Account and debits the Liability Account for the purchase amount. The Liability Account functions as a bank account in your ledger, specifically for Expensify Card expenses: - -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} +**Accounting treatment**: When the report is exported, we create a journal entry in your accounting system. This entry credits the Clearing Account and debits the Liability Account for the purchase amount. The Liability Account functions as a bank account in your ledger, specifically for Expensify Card expenses. # Deep Dive ## QuickBooks Online From a88dabc930281aed291622932b25c364d942f5d4 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 23 Oct 2023 10:15:02 +0700 Subject: [PATCH 096/449] fix update the latest change in staging --- src/libs/actions/App.js | 31 +++---------------- .../FloatingActionButtonAndPopover.js | 2 +- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 42efa0353fd5..d4837a0b17a4 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -333,14 +333,7 @@ function endSignOnTransition() { * @param {Boolean} [isThereModalToDismiss] Optional, if there is a modal to dismiss */ -function createWorkspaceAndNavigateToIt( - policyOwnerEmail = '', - makeMeAdmin = false, - policyName = '', - transitionFromOldDot = false, - shouldNavigateToAdminChat = true, - isThereModalToDismiss = true, -) { +function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', transitionFromOldDot = false, shouldNavigateToAdminChat = true) { const policyID = Policy.generatePolicyID(); const adminsChatReportID = Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID); Navigation.isNavigationReady() @@ -351,26 +344,10 @@ function createWorkspaceAndNavigateToIt( } if (shouldNavigateToAdminChat) { - if (isThereModalToDismiss) { - Navigation.dismissModal(adminsChatReportID); - setTimeout(() => { - Navigation.navigate(ROUTES.SETTINGS); - setTimeout(() => { - Navigation.navigate(ROUTES.SETTINGS_WORKSPACES); - setTimeout(() => { - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); - }, 50); - }, 50); - }, 50); - } else { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); - setTimeout(() => { - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); - }, 50); - } - } else { - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); } + + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); }) .then(endSignOnTransition); } diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 5932049cd32c..02f1856c7bf5 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -228,7 +228,7 @@ function FloatingActionButtonAndPopover(props) { iconHeight: 40, text: props.translate('workspace.new.newWorkspace'), description: props.translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => interceptAnonymousUser(() => App.createWorkspaceAndNavigateToIt('', false, '', false, !props.isSmallScreenWidth, false)), + onSelected: () => interceptAnonymousUser(() => App.createWorkspaceAndNavigateToIt('', false, '', false, !props.isSmallScreenWidth)), }, ] : []), From 54a01bb3a1153a629d70c78b2a4f4ac5e7e91add Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 23 Oct 2023 10:52:30 +0700 Subject: [PATCH 097/449] fix on press create new workspace --- src/libs/actions/App.js | 2 -- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index d4837a0b17a4..75520d483f98 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -330,8 +330,6 @@ function endSignOnTransition() { * @param {String} [policyName] Optional, custom policy name we will use for created workspace * @param {Boolean} [transitionFromOldDot] Optional, if the user is transitioning from old dot * @param {Boolean} [shouldNavigateToAdminChat] Optional, navigate to the #admin room after creation - * @param {Boolean} [isThereModalToDismiss] Optional, if there is a modal to dismiss - */ function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', transitionFromOldDot = false, shouldNavigateToAdminChat = true) { const policyID = Policy.generatePolicyID(); diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 02f1856c7bf5..1bbb64d99c2b 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -228,7 +228,7 @@ function FloatingActionButtonAndPopover(props) { iconHeight: 40, text: props.translate('workspace.new.newWorkspace'), description: props.translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => interceptAnonymousUser(() => App.createWorkspaceAndNavigateToIt('', false, '', false, !props.isSmallScreenWidth)), + onSelected: () => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()), }, ] : []), From d0ea5b9175ddce96d464c28d6f13fd06d149f526 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:59:05 +0200 Subject: [PATCH 098/449] Add FormProvider to NewTaskDescriptionPage --- src/pages/tasks/NewTaskDescriptionPage.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/tasks/NewTaskDescriptionPage.js b/src/pages/tasks/NewTaskDescriptionPage.js index 44fd4346538d..378adf6a9a41 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.js +++ b/src/pages/tasks/NewTaskDescriptionPage.js @@ -10,7 +10,6 @@ import Navigation from '../../libs/Navigation/Navigation'; import ScreenWrapper from '../../components/ScreenWrapper'; import styles from '../../styles/styles'; import ONYXKEYS from '../../ONYXKEYS'; -import Form from '../../components/Form'; import TextInput from '../../components/TextInput'; import Permissions from '../../libs/Permissions'; import ROUTES from '../../ROUTES'; @@ -18,6 +17,8 @@ import * as Task from '../../libs/actions/Task'; import updateMultilineInputRange from '../../libs/UpdateMultilineInputRange'; import CONST from '../../CONST'; import * as Browser from '../../libs/Browser'; +import FormProvider from '../../components/Form/FormProvider'; +import InputWrapper from '../../components/Form/InputWrapper'; const propTypes = { /** Beta features list */ @@ -82,7 +83,7 @@ function NewTaskDescriptionPage(props) { onCloseButtonPress={() => Task.dismissModalAndClearOutTaskInfo()} onBackButtonPress={() => Navigation.goBack(ROUTES.NEW_TASK)} /> -
- -
+
); From d402f9c15e909c7aa834af87145015ea5e647dda Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Mon, 23 Oct 2023 13:10:18 +0200 Subject: [PATCH 099/449] Add FormProvider in NewTaskTitlePage --- src/pages/tasks/NewTaskTitlePage.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/tasks/NewTaskTitlePage.js b/src/pages/tasks/NewTaskTitlePage.js index 62eb1da4872a..2a8d776eaefd 100644 --- a/src/pages/tasks/NewTaskTitlePage.js +++ b/src/pages/tasks/NewTaskTitlePage.js @@ -10,12 +10,13 @@ import ScreenWrapper from '../../components/ScreenWrapper'; import styles from '../../styles/styles'; import ONYXKEYS from '../../ONYXKEYS'; import * as ErrorUtils from '../../libs/ErrorUtils'; -import Form from '../../components/Form'; import TextInput from '../../components/TextInput'; import Permissions from '../../libs/Permissions'; import ROUTES from '../../ROUTES'; import * as Task from '../../libs/actions/Task'; import CONST from '../../CONST'; +import FormProvider from '../../components/Form/FormProvider'; +import InputWrapper from '../../components/Form/InputWrapper'; const propTypes = { /** Beta features list */ @@ -85,7 +86,7 @@ function NewTaskTitlePage(props) { shouldShowBackButton onBackButtonPress={() => Navigation.goBack(ROUTES.NEW_TASK)} /> -
- (inputRef.current = el)} @@ -103,7 +105,7 @@ function NewTaskTitlePage(props) { accessibilityLabel={props.translate('task.title')} /> -
+ ); } From 1783d6ccbded3e5b423f878a93b6ff7014e3e5f6 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Mon, 23 Oct 2023 13:24:00 +0100 Subject: [PATCH 100/449] update text styles to better handle ellipsize on Android native --- src/pages/ProfilePage.js | 14 ++++++++------ src/pages/settings/InitialSettingsPage.js | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 8e0ed04ab94a..7f5801e8ddfe 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -187,12 +187,14 @@ function ProfilePage(props) { )} {Boolean(displayName) && ( - - {displayName} - + + + {displayName} + + )} {hasStatus && ( diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index d81c9d057174..32bb64f95420 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -356,14 +356,14 @@ function InitialSettingsPage(props) {
{props.currentUserPersonalDetails.displayName ? props.currentUserPersonalDetails.displayName : props.formatPhoneNumber(props.session.email)} From dd85519875f12ef3d37a990e862501205e9fa153 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Mon, 23 Oct 2023 15:07:23 +0200 Subject: [PATCH 101/449] Add FormProvider in MoneyRequestDescriptionPage --- src/pages/iou/MoneyRequestDescriptionPage.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/MoneyRequestDescriptionPage.js b/src/pages/iou/MoneyRequestDescriptionPage.js index cfdbb60b4f0d..4c2ae3bdf4ae 100644 --- a/src/pages/iou/MoneyRequestDescriptionPage.js +++ b/src/pages/iou/MoneyRequestDescriptionPage.js @@ -9,7 +9,6 @@ import {iouPropTypes, iouDefaultProps} from './propTypes'; import TextInput from '../../components/TextInput'; import ScreenWrapper from '../../components/ScreenWrapper'; import HeaderWithBackButton from '../../components/HeaderWithBackButton'; -import Form from '../../components/Form'; import ONYXKEYS from '../../ONYXKEYS'; import styles from '../../styles/styles'; import Navigation from '../../libs/Navigation/Navigation'; @@ -20,6 +19,8 @@ import CONST from '../../CONST'; import useLocalize from '../../hooks/useLocalize'; import updateMultilineInputRange from '../../libs/UpdateMultilineInputRange'; import * as Browser from '../../libs/Browser'; +import FormProvider from '../../components/Form/FormProvider'; +import InputWrapper from '../../components/Form/InputWrapper'; const propTypes = { /** Onyx Props */ @@ -115,7 +116,7 @@ function MoneyRequestDescriptionPage({iou, route, selectedTab}) { title={translate('common.description')} onBackButtonPress={() => navigateBack()} /> -
updateComment(value)} @@ -123,7 +124,8 @@ function MoneyRequestDescriptionPage({iou, route, selectedTab}) { enabledWhenOffline > - -
+ ); From b418a4cb7561b597c3423b5602cbeb491b8d63fc Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Mon, 23 Oct 2023 15:35:53 +0200 Subject: [PATCH 102/449] Code review changes --- src/components/AddressSearch/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 7c754a33e370..a4f712435af3 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -137,9 +137,6 @@ const defaultProps = { resultTypes: 'address', }; -// Do not convert to class component! It's been tried before and presents more challenges than it's worth. -// Relevant thread: https://expensify.slack.com/archives/C03TQ48KC/p1634088400387400 -// Reference: https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/609#issuecomment-886133839 function AddressSearch({ canUseCurrentLocation, containerStyles, @@ -372,7 +369,7 @@ function AddressSearch({ [isTyping, translate, network.isOffline], ); - const listLoader = useCallback( + const listLoader = useMemo( () => ( Date: Mon, 23 Oct 2023 22:03:31 +0700 Subject: [PATCH 103/449] fix remove createWorkspaceAndNavigateToIt --- .../Navigation/AppNavigator/AuthScreens.js | 2 +- src/libs/actions/App.js | 39 +++---------------- src/libs/actions/Policy.js | 4 +- src/pages/workspace/WorkspaceInitialPage.js | 2 +- src/pages/workspace/WorkspaceNewRoomPage.js | 2 +- 5 files changed, 11 insertions(+), 38 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index dd7175dbc6f6..8b07034400a3 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -184,7 +184,7 @@ class AuthScreens extends React.Component { App.reconnectApp(this.props.lastUpdateIDAppliedToClient); } - App.setUpPoliciesAndNavigate(this.props.session, !this.props.isSmallScreenWidth); + App.setUpPoliciesAndNavigate(this.props.session); App.redirectThirdPartyDesktopSignIn(); // Check if we should be running any demos immediately after signing in. diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 75520d483f98..678b5f9f4594 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -322,44 +322,17 @@ function endSignOnTransition() { return resolveSignOnTransitionToFinishPromise(); } -/** - * Create a new workspace and navigate to it - * - * @param {String} [policyOwnerEmail] Optional, the email of the account to make the owner of the policy - * @param {Boolean} [makeMeAdmin] Optional, leave the calling account as an admin on the policy - * @param {String} [policyName] Optional, custom policy name we will use for created workspace - * @param {Boolean} [transitionFromOldDot] Optional, if the user is transitioning from old dot - * @param {Boolean} [shouldNavigateToAdminChat] Optional, navigate to the #admin room after creation - */ -function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', transitionFromOldDot = false, shouldNavigateToAdminChat = true) { - const policyID = Policy.generatePolicyID(); - const adminsChatReportID = Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID); - Navigation.isNavigationReady() - .then(() => { - if (transitionFromOldDot) { - // We must call goBack() to remove the /transition route from history - Navigation.goBack(ROUTES.HOME); - } - - if (shouldNavigateToAdminChat) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); - } - - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); - }) - .then(endSignOnTransition); -} - /** * Create a new draft workspace and navigate to it * * @param {String} [policyOwnerEmail] Optional, the email of the account to make the owner of the policy * @param {String} [policyName] Optional, custom policy name we will use for created workspace * @param {Boolean} [transitionFromOldDot] Optional, if the user is transitioning from old dot + * @param {Boolean} [makeMeAdmin] Optional, leave the calling account as an admin on the policy */ -function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', policyName = '', transitionFromOldDot = false) { +function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', policyName = '', transitionFromOldDot = false, makeMeAdmin = false) { const policyID = Policy.generatePolicyID(); - Policy.createDraftInitialWorkspace(policyOwnerEmail, policyName, policyID); + Policy.createDraftInitialWorkspace(policyOwnerEmail, policyName, policyID, makeMeAdmin); Navigation.isNavigationReady() .then(() => { @@ -403,9 +376,8 @@ function savePolicyDraftByNewWorkspace(policyID, policyName, policyOwnerEmail = * pass it in as a parameter. withOnyx guarantees that the value has been read * from Onyx because it will not render the AuthScreens until that point. * @param {Object} session - * @param {Boolean} shouldNavigateToAdminChat Should we navigate to admin chat after creating workspace */ -function setUpPoliciesAndNavigate(session, shouldNavigateToAdminChat) { +function setUpPoliciesAndNavigate(session) { const currentUrl = getCurrentUrl(); if (!session || !currentUrl || !currentUrl.includes('exitTo')) { return; @@ -426,7 +398,7 @@ function setUpPoliciesAndNavigate(session, shouldNavigateToAdminChat) { const shouldCreateFreePolicy = !isLoggingInAsNewUser && isTransitioning && exitTo === ROUTES.WORKSPACE_NEW; if (shouldCreateFreePolicy) { - createWorkspaceAndNavigateToIt(policyOwnerEmail, makeMeAdmin, policyName, true, shouldNavigateToAdminChat); + createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail, policyName, true, makeMeAdmin); return; } if (!isLoggingInAsNewUser && exitTo) { @@ -555,7 +527,6 @@ export { handleRestrictedEvent, beginDeepLinkRedirect, beginDeepLinkRedirectAfterTransition, - createWorkspaceAndNavigateToIt, getMissingOnyxUpdates, finalReconnectAppAfterActivatingReliableUpdates, savePolicyDraftByNewWorkspace, diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 89324dd35485..a239ca691de3 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -922,8 +922,9 @@ function buildOptimisticCustomUnits() { * @param {String} [policyOwnerEmail] Optional, the email of the account to make the owner of the policy * @param {String} [policyName] Optional, custom policy name we will use for created workspace * @param {String} [policyID] Optional, custom policy id we will use for created workspace + * @param {Boolean} [makeMeAdmin] Optional, leave the calling account as an admin on the policy */ -function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', policyID = generatePolicyID()) { +function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', policyID = generatePolicyID(), makeMeAdmin = false) { const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); const {customUnits} = buildOptimisticCustomUnits(); @@ -941,6 +942,7 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol outputCurrency: lodashGet(allPersonalDetails, [sessionAccountID, 'localCurrencyCode'], CONST.CURRENCY.USD), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, customUnits, + makeMeAdmin, }, }, { diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js index d275b7f0dd10..edaa1b29bffd 100644 --- a/src/pages/workspace/WorkspaceInitialPage.js +++ b/src/pages/workspace/WorkspaceInitialPage.js @@ -88,7 +88,7 @@ function WorkspaceInitialPage(props) { return; } - App.savePolicyDraftByNewWorkspace(props.policyDraft.id, props.policyDraft.name, '', false); + App.savePolicyDraftByNewWorkspace(props.policyDraft.id, props.policyDraft.name, '', props.policyDraft.makeMeAdmin); // We only care when the component renders the first time // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index da0bf845cc81..8a62a3b876ea 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -165,7 +165,7 @@ function WorkspaceNewRoomPage(props) { shouldShow={!Permissions.canUsePolicyRooms(props.betas) || !workspaceOptions.length} shouldShowBackButton={false} linkKey="workspace.emptyWorkspace.title" - onLinkPress={() => App.createWorkspaceAndNavigateToIt('', false, '', false, false)} + onLinkPress={() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()} > Date: Mon, 23 Oct 2023 19:49:21 +0200 Subject: [PATCH 104/449] Restore displayName --- src/components/FullscreenLoadingIndicator.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/FullscreenLoadingIndicator.tsx b/src/components/FullscreenLoadingIndicator.tsx index 92e59d0e2d1c..4cd8002cf2c7 100644 --- a/src/components/FullscreenLoadingIndicator.tsx +++ b/src/components/FullscreenLoadingIndicator.tsx @@ -19,4 +19,6 @@ function FullScreenLoadingIndicator({style = []}: FullScreenLoadingIndicatorProp ); } +FullScreenLoadingIndicator.displayName = 'FullScreenLoadingIndicator'; + export default FullScreenLoadingIndicator; From 7044bbb83da3685e1eb19a4fc4b2ba4c9f2841de Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Mon, 23 Oct 2023 19:54:07 +0200 Subject: [PATCH 105/449] Use ViewStyle --- src/components/FullscreenLoadingIndicator.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/FullscreenLoadingIndicator.tsx b/src/components/FullscreenLoadingIndicator.tsx index 4cd8002cf2c7..43a7c4180284 100644 --- a/src/components/FullscreenLoadingIndicator.tsx +++ b/src/components/FullscreenLoadingIndicator.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import {ActivityIndicator, StyleSheet, View} from 'react-native'; +import {ActivityIndicator, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; import styles from '../styles/styles'; import themeColors from '../styles/themes/default'; type FullScreenLoadingIndicatorProps = { - style: Record | Array>; + style: StyleProp; }; function FullScreenLoadingIndicator({style = []}: FullScreenLoadingIndicatorProps) { From 1088ada601544648d36bbdbbce33b409d8d1a6d2 Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Mon, 23 Oct 2023 21:45:59 +0200 Subject: [PATCH 106/449] Drop not needed casting --- src/components/FullscreenLoadingIndicator.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/FullscreenLoadingIndicator.tsx b/src/components/FullscreenLoadingIndicator.tsx index 43a7c4180284..dc2a08e3ac5e 100644 --- a/src/components/FullscreenLoadingIndicator.tsx +++ b/src/components/FullscreenLoadingIndicator.tsx @@ -8,9 +8,8 @@ type FullScreenLoadingIndicatorProps = { }; function FullScreenLoadingIndicator({style = []}: FullScreenLoadingIndicatorProps) { - const additionalStyles = Array.isArray(style) ? style : [style]; return ( - + Date: Tue, 24 Oct 2023 02:39:35 +0200 Subject: [PATCH 107/449] tests --- src/pages/home/sidebar/SidebarLinksData.js | 1 + src/types/onyx/Report.ts | 3 +++ tests/unit/SidebarOrderTest.js | 28 +++++++++++----------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 394f6c5ddc5a..2875b61256af 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -143,6 +143,7 @@ const chatReportSelector = (report) => total: report.total, nonReimbursableTotal: report.nonReimbursableTotal, hasOutstandingIOU: report.hasOutstandingIOU, + hasOutstandingChildRequest: report.hasOutstandingChildRequest, isWaitingOnBankAccount: report.isWaitingOnBankAccount, statusNum: report.statusNum, stateNum: report.stateNum, diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 8587cf9b7cd5..c8b3b0752959 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -9,6 +9,9 @@ type Report = { /** Whether there is an outstanding amount in IOU */ hasOutstandingIOU?: boolean; + /** Whether child has an outstanding request */ + hasOutstandingChildRequest?: boolean; + /** List of icons for report participants */ icons?: OnyxCommon.Icon[]; diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index 4a693d679b86..64587466c942 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -385,7 +385,7 @@ describe('Sidebar', () => { }; const report3 = { ...LHNTestUtils.getFakeReport([5, 6], 1), - hasOutstandingIOU: false, + hasOutstandingChildRequest: false, // This has to be added after the IOU report is generated iouReportID: null, @@ -395,7 +395,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 2, - hasOutstandingIOU: true, + hasOutstandingChildRequest: true, total: 10000, currency: 'USD', chatReportID: report3.reportID, @@ -404,7 +404,7 @@ describe('Sidebar', () => { const currentReportId = report2.reportID; const currentlyLoggedInUserAccountID = 9; LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); - + console.log('iouReport :>> ', iouReport); return ( waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders @@ -431,7 +431,7 @@ describe('Sidebar', () => { expect(screen.queryAllByTestId('Pin Icon')).toHaveLength(1); expect(screen.queryAllByTestId('Pencil Icon')).toHaveLength(1); expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two'); - expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Two owes $100.00'); + expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Two paid $100.00'); expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four'); }) ); @@ -733,7 +733,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 2, - hasOutstandingIOU: true, + hasOutstandingChildRequest: true, total: 10000, currency: 'USD', chatReportID: report3.reportID, @@ -743,7 +743,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 3, - hasOutstandingIOU: true, + hasOutstandingChildRequest: true, total: 10000, currency: 'USD', chatReportID: report3.reportID, @@ -753,7 +753,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 4, - hasOutstandingIOU: true, + hasOutstandingChildRequest: true, total: 100000, currency: 'USD', chatReportID: report3.reportID, @@ -763,7 +763,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 5, - hasOutstandingIOU: true, + hasOutstandingChildRequest: true, total: 10000, currency: 'USD', chatReportID: report3.reportID, @@ -773,7 +773,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 6, - hasOutstandingIOU: true, + hasOutstandingChildRequest: true, total: 10000, currency: 'USD', chatReportID: report3.reportID, @@ -814,11 +814,11 @@ describe('Sidebar', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNames = screen.queryAllByLabelText(hintText); expect(displayNames).toHaveLength(5); - expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Four owes $1,000.00'); - expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Five owes $100.00'); - expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Email Six owes $100.00'); - expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Email Three owes $100.00'); - expect(lodashGet(displayNames, [4, 'props', 'children'])).toBe('Email Two owes $100.00'); + expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Four paid $1,000.00'); + expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Five paid $100.00'); + expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Email Six paid $100.00'); + expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Email Three paid $100.00'); + expect(lodashGet(displayNames, [4, 'props', 'children'])).toBe('Email Two paid $100.00'); }) ); }); From f4cb0093f0981445a1bebe2709f00b84cd831ce7 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Tue, 24 Oct 2023 02:47:08 +0200 Subject: [PATCH 108/449] fix lint --- tests/unit/SidebarOrderTest.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index 64587466c942..a2b82dc1cb58 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -404,7 +404,6 @@ describe('Sidebar', () => { const currentReportId = report2.reportID; const currentlyLoggedInUserAccountID = 9; LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); - console.log('iouReport :>> ', iouReport); return ( waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders From 8413ffde609d1f04b52ddd63d681800be922bb2a Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 24 Oct 2023 10:55:59 +0700 Subject: [PATCH 109/449] fix: regression 29888 --- src/components/ReportActionItem/MoneyRequestPreview.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 43500c731728..06e374306ae8 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -263,7 +263,9 @@ function MoneyRequestPreview(props) { ) : ( - {getPreviewHeaderText() + (isSettled ? ` • ${getSettledMessage()}` : '')} + + {getPreviewHeaderText() + (isSettled ? ` • ${getSettledMessage()}` : '')} + {hasFieldErrors && ( Date: Tue, 24 Oct 2023 15:14:57 +0530 Subject: [PATCH 110/449] enable maxHeight to avoid keyboard overlapping the save button --- src/pages/ReportWelcomeMessagePage.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/ReportWelcomeMessagePage.js b/src/pages/ReportWelcomeMessagePage.js index 48dd07090ef4..0b572af69d2c 100644 --- a/src/pages/ReportWelcomeMessagePage.js +++ b/src/pages/ReportWelcomeMessagePage.js @@ -75,7 +75,11 @@ function ReportWelcomeMessagePage(props) { ); return ( - + Date: Tue, 24 Oct 2023 12:11:32 +0200 Subject: [PATCH 111/449] Fix not displaying all errors --- src/components/Form/FormProvider.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index ada40c24ed89..5047903040f4 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -103,6 +103,7 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC const inputRefs = useRef(null); const touchedInputs = useRef({}); const [inputValues, setInputValues] = useState({}); + const submitPressed = useRef(false); const [errors, setErrors] = useState({}); const hasServerError = useMemo(() => Boolean(formState) && !_.isEmpty(formState.errors), [formState]); @@ -159,7 +160,7 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC throw new Error('Validate callback must return an empty object or an object with shape {inputID: error}'); } - const touchedInputErrors = _.pick(validateErrors, (inputValue, inputID) => Boolean(touchedInputs.current[inputID])); + const touchedInputErrors = _.pick(validateErrors, (inputValue, inputID) => Boolean(touchedInputs.current[inputID]) || submitPressed.current); if (!_.isEqual(errors, touchedInputErrors)) { setErrors(touchedInputErrors); @@ -181,6 +182,7 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC ); const submit = useCallback(() => { + submitPressed.current = true; // Return early if the form is already submitting to avoid duplicate submission if (formState.isLoading) { return; From 677c2a51fa139cc905459aa19ea1fe7c8d9f54e2 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Tue, 24 Oct 2023 12:15:15 +0200 Subject: [PATCH 112/449] Fix prettier --- src/components/CheckboxWithLabel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js index 70fa972a5d2f..8ce08caad54b 100644 --- a/src/components/CheckboxWithLabel.js +++ b/src/components/CheckboxWithLabel.js @@ -8,7 +8,7 @@ import Text from './Text'; import FormHelpMessage from './FormHelpMessage'; import variables from '../styles/variables'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; -import refPropTypes from "./refPropTypes"; +import refPropTypes from './refPropTypes'; /** * Returns an error if the required props are not provided From 3de148b4a52287d113f8a60d2cd38b8ffe8a4959 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 24 Oct 2023 13:21:50 +0200 Subject: [PATCH 113/449] draft option row left indent --- src/components/OptionRow.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index e3ea3acfc2ee..114b728f62d6 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -109,9 +109,20 @@ function OptionRow(props) { setIsDisabled(props.isDisabled); }, [props.isDisabled]); + const text = lodashGet(props.option, 'text', ''); + const fullTitle = props.isMultilineSupported ? text.trimStart() : text; + const indentsLength = text.length - fullTitle.length; + const paddingLeft = Math.floor(indentsLength / CONST.INDENTS.length) * styles.ml3.marginLeft; const textStyle = props.optionIsFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; const textUnreadStyle = props.boldStyle || props.option.boldStyle ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; - const displayNameStyle = StyleUtils.combineStyles(styles.optionDisplayName, textUnreadStyle, props.style, styles.pre, isDisabled ? styles.optionRowDisabled : {}); + const displayNameStyle = StyleUtils.combineStyles( + styles.optionDisplayName, + textUnreadStyle, + props.style, + styles.pre, + isDisabled ? styles.optionRowDisabled : {}, + props.isMultilineSupported ? {paddingLeft} : {}, + ); const alternateTextStyle = StyleUtils.combineStyles( textStyle, styles.optionAlternateText, @@ -204,7 +215,7 @@ function OptionRow(props) { Date: Tue, 24 Oct 2023 13:24:18 +0200 Subject: [PATCH 114/449] tests fix --- tests/unit/ReportUtilsTest.js | 1 + tests/unit/SidebarOrderTest.js | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index 0c888b80b51b..81249214a4e8 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -302,6 +302,7 @@ describe('ReportUtils', () => { const report = { ...LHNTestUtils.getFakeReport(), ownerAccountID: 99, + hasOutstandingIOU: true, hasOutstandingChildRequest: true, isWaitingOnBankAccount: false, }; diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index a2b82dc1cb58..ea6fe49befc8 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -395,6 +395,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 2, + hasOutstandingIOU: true, hasOutstandingChildRequest: true, total: 10000, currency: 'USD', @@ -430,7 +431,7 @@ describe('Sidebar', () => { expect(screen.queryAllByTestId('Pin Icon')).toHaveLength(1); expect(screen.queryAllByTestId('Pencil Icon')).toHaveLength(1); expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two'); - expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Two paid $100.00'); + expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Two owes $100.00'); expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four'); }) ); @@ -732,6 +733,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 2, + hasOutstandingIOU: true, hasOutstandingChildRequest: true, total: 10000, currency: 'USD', @@ -742,6 +744,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 3, + hasOutstandingIOU: true, hasOutstandingChildRequest: true, total: 10000, currency: 'USD', @@ -752,6 +755,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 4, + hasOutstandingIOU: true, hasOutstandingChildRequest: true, total: 100000, currency: 'USD', @@ -762,6 +766,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 5, + hasOutstandingIOU: true, hasOutstandingChildRequest: true, total: 10000, currency: 'USD', @@ -772,6 +777,7 @@ describe('Sidebar', () => { type: CONST.REPORT.TYPE.IOU, ownerAccountID: 2, managerID: 6, + hasOutstandingIOU: true, hasOutstandingChildRequest: true, total: 10000, currency: 'USD', @@ -813,11 +819,11 @@ describe('Sidebar', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNames = screen.queryAllByLabelText(hintText); expect(displayNames).toHaveLength(5); - expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Four paid $1,000.00'); - expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Five paid $100.00'); - expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Email Six paid $100.00'); - expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Email Three paid $100.00'); - expect(lodashGet(displayNames, [4, 'props', 'children'])).toBe('Email Two paid $100.00'); + expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Four owes $1,000.00'); + expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Five owes $100.00'); + expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Email Six owes $100.00'); + expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Email Three owes $100.00'); + expect(lodashGet(displayNames, [4, 'props', 'children'])).toBe('Email Two owes $100.00'); }) ); }); From dd5d90cebaaf17a7fbb6c17176198df774059a19 Mon Sep 17 00:00:00 2001 From: Sonia Liapounova Date: Tue, 24 Oct 2023 04:32:10 -0700 Subject: [PATCH 115/449] Create Fringe-Benefits.md --- .../Fringe-Benefits.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/articles/expensify-classic/insights-and-custom-reporting/Fringe-Benefits.md diff --git a/docs/articles/expensify-classic/insights-and-custom-reporting/Fringe-Benefits.md b/docs/articles/expensify-classic/insights-and-custom-reporting/Fringe-Benefits.md new file mode 100644 index 000000000000..d76b6e413840 --- /dev/null +++ b/docs/articles/expensify-classic/insights-and-custom-reporting/Fringe-Benefits.md @@ -0,0 +1,43 @@ +--- +title: Fringe Benefits +description: How to track your Fringe Benefits +--- +# Overview +If you’re looking to track and report expense data to calculate Fringe Benefits Tax (FBT), you can use Expensify’s special workflow that allows you to capture extra information and use a template to export to a spreadsheet. + +# How to set up Fringe Benefit Tax + +## Add Attendee Count Tags +First, you’ll need to add these exact two tags to your Workspace: +1) Number of Internal Attendees +2) Number of External Attendees + +These tags must be named exactly as written above, ensuring there are no extra spaces at the beginning or at the end. You’ll need to set the tags to be numbers 00 - 10 or whatever number you wish to go up to (up to the maximum number of attendees you would expect at any one time), one tag per number i.e. “01”, “02”, “03” etc. These tags can be added in addition to those that are pulled in from your accounting solution. Follow these [instructions](https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/Tags#gsc.tab=0) to add tags. + +## Add Payroll Code +Go to **Settings > Workspaces > Group > _Workspace Name_ > Categories** and within the categories you wish to track FBT against, select **Edit Category** and add the code “TAG”: + +## Enable Workflow +Once you’ve added the two tags (Internal Attendees and External Attendees) and added the payroll code “TAG” to FBT categories, you can now contact Expensify at concierge@expensify.com and send through a request to enable the FBT workflow. Please send through the following request: +>“Can you please add the custom workflow/DEW named FRINGE_BENEFIT_TAX to my company workspace named ?” +Once the FBT workflow is enabled, it will require anything with the code “TAG” to include the two attendee count tags in order to be submitted. + + +# For Users +Once these steps are completed, users who create expenses coded with any category that has the payroll code “TAG” (e.g. Entertainment Expenses) but don’t add the internal and external attendee counts, will not be able to submit their expenses. +# For Admins +You are now able to create and run a report, which shows all expenses under these categories and also shows the number of internal and external attendees. Because we don’t presume to know all of the data points you wish to capture, you’ll need to create a Custom CSV export. +Here’s an example of the Excel formulas to use to report on attendees: +- `{expense:tag:ntag-1}` outputs the first tag the user chooses. +- `{expense:tag:ntag-3}` outputs the third tag the user chooses. + +Your expenses may have multiple levels of coding, i.e.: +- GL Code (Category) +- Department (Tag 1) +- Location (Tag 2) +- Number of Internal Attendees (Tag 3) +- Number of External Attendees (Tag 4) + +In the above case, you’ll want to use `{expense:tag:ntag-3}` and `{expense:tag:ntag-4}` as formulas to report on the number of internal and external attendees. + +Our article on [Custom Templates](https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates#gsc.tab=0) shows you how to create a custom CSV. From 8098acf53f512e8ffcec8fbeaf7865a689c3885b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 24 Oct 2023 15:20:50 +0200 Subject: [PATCH 116/449] pass sectionHeaderStyle to OptionsList --- src/components/OptionsList/BaseOptionsList.js | 3 ++- src/components/OptionsList/optionsListPropTypes.js | 5 +++++ src/components/OptionsSelector/BaseOptionsSelector.js | 1 + src/components/OptionsSelector/optionsSelectorPropTypes.js | 5 +++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 91fd77dbea30..f633156b6ed7 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -51,6 +51,7 @@ function BaseOptionsList({ showTitleTooltip, optionHoveredStyle, contentContainerStyles, + sectionHeaderStyle, showScrollIndicator, listContainerStyles, shouldDisableRowInnerPadding, @@ -233,7 +234,7 @@ function BaseOptionsList({ // We do this so that we can reference the height in `getItemLayout` – // we need to know the heights of all list items up-front in order to synchronously compute the layout of any given list item. // So be aware that if you adjust the content of the section header (for example, change the font size), you may need to adjust this explicit height as well. - + {title} ); diff --git a/src/components/OptionsList/optionsListPropTypes.js b/src/components/OptionsList/optionsListPropTypes.js index caabf39a41bb..5dc309e2b20c 100644 --- a/src/components/OptionsList/optionsListPropTypes.js +++ b/src/components/OptionsList/optionsListPropTypes.js @@ -14,6 +14,10 @@ const propTypes = { /** Extra styles for the section list container */ contentContainerStyles: PropTypes.arrayOf(PropTypes.object), + /** Style for section headers */ + // eslint-disable-next-line react/forbid-prop-types + sectionHeaderStyle: PropTypes.object, + /** Sections for the section list */ sections: PropTypes.arrayOf( PropTypes.shape({ @@ -101,6 +105,7 @@ const propTypes = { const defaultProps = { optionHoveredStyle: undefined, contentContainerStyles: [], + sectionHeaderStyle: undefined, listContainerStyles: [styles.flex1], sections: [], focusedIndex: 0, diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 0125fc8e178e..7c09edf64b0d 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -427,6 +427,7 @@ class BaseOptionsSelector extends Component { } }} contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} + sectionHeaderStyle={this.props.sectionHeaderStyle} listContainerStyles={this.props.listContainerStyles} listStyles={this.props.listStyles} isLoading={!this.props.shouldShowOptions} diff --git a/src/components/OptionsSelector/optionsSelectorPropTypes.js b/src/components/OptionsSelector/optionsSelectorPropTypes.js index bfef8ca3a925..b2e422f0a0d9 100644 --- a/src/components/OptionsSelector/optionsSelectorPropTypes.js +++ b/src/components/OptionsSelector/optionsSelectorPropTypes.js @@ -108,6 +108,10 @@ const propTypes = { /** Hover style for options in the OptionsList */ optionHoveredStyle: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + /** Style for section headers */ + // eslint-disable-next-line react/forbid-prop-types + sectionHeaderStyle: PropTypes.object, + /** Whether to show options list */ shouldShowOptions: PropTypes.bool, @@ -159,6 +163,7 @@ const defaultProps = { shouldTextInputAppearBelowOptions: false, footerContent: undefined, optionHoveredStyle: styles.hoveredComponentBG, + sectionHeaderStyle: undefined, shouldShowOptions: true, disableArrowKeysActions: false, isDisabled: false, From d5f4e0e47cf15849ab69c2bdd644f3f2168bc8e3 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 24 Oct 2023 15:21:04 +0200 Subject: [PATCH 117/449] Apply sectionHeaderStyle for category and tag pickers --- src/components/CategoryPicker/index.js | 1 + src/components/TagPicker/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js index e7f68e7011fc..a2e97ce70d28 100644 --- a/src/components/CategoryPicker/index.js +++ b/src/components/CategoryPicker/index.js @@ -69,6 +69,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC return ( Date: Tue, 24 Oct 2023 16:18:03 +0200 Subject: [PATCH 118/449] Remove unused ref --- src/components/Form/FormProvider.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index ecd9f2322cc5..add58dbef18c 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -103,7 +103,6 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC const inputRefs = useRef({}); const touchedInputs = useRef({}); const [inputValues, setInputValues] = useState({}); - const submitPressed = useRef(false); const [errors, setErrors] = useState({}); const hasServerError = useMemo(() => Boolean(formState) && !_.isEmpty(formState.errors), [formState]); @@ -160,7 +159,7 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC throw new Error('Validate callback must return an empty object or an object with shape {inputID: error}'); } - const touchedInputErrors = _.pick(validateErrors, (inputValue, inputID) => Boolean(touchedInputs.current[inputID]) || submitPressed.current); + const touchedInputErrors = _.pick(validateErrors, (inputValue, inputID) => Boolean(touchedInputs.current[inputID])); if (!_.isEqual(errors, touchedInputErrors)) { setErrors(touchedInputErrors); @@ -182,7 +181,6 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC ); const submit = useCallback(() => { - submitPressed.current = true; // Return early if the form is already submitting to avoid duplicate submission if (formState.isLoading) { return; From 51fa6ed5dfc99e76f3132f9fe2833e4e05d81f88 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Tue, 24 Oct 2023 08:34:33 -0600 Subject: [PATCH 119/449] Rename a missed spot --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index d49af6c84074..93f456b22ed5 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3730,7 +3730,7 @@ function getPolicyExpenseChatReportIDByOwner(policyOwner) { */ function canCreateRequest(report, betas, iouType) { const participantAccountIDs = lodashGet(report, 'participantAccountIDs', []); - if (shouldDisableWriteActions(report)) { + if (!canUserPerformWriteAction(report)) { return false; } return getMoneyRequestOptions(report, participantAccountIDs, betas).includes(iouType); From 2cc169d984e5644d7cdb1698fd89b210cbc4fc83 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 24 Oct 2023 11:11:12 -0400 Subject: [PATCH 120/449] Update README.md Co-authored-by: Brandon Stites <42391420+stitesExpensify@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 48c4cb03baf0..46eb240e310b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ These instructions should get you set up ready to work on New Expensify 🙌 1. Install `nvm` then `node` & `npm`: `brew install nvm && nvm install` 2. Install `watchman`: `brew install watchman` 3. Install dependencies: `npm install` -4. Install `mkcert`: `brew install mkcert` followed by `npm run setup-https`. If you're using another OS, follow the instructions [here](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#installation). +4. Install `mkcert`: `brew install mkcert` followed by `npm run setup-https`. If you are not using macOS, follow the instructions [here](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#installation). 5. Create a host entry in your local hosts file, `/etc/hosts` for new.expensify.com.dev pointing to localhost: ``` 127.0.0.1 new.expensify.com.dev From 35f2dcba9ec425222feeb8776321f8660dd41d9d Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 24 Oct 2023 17:14:36 +0200 Subject: [PATCH 121/449] add a section header for empty title --- src/components/OptionsList/BaseOptionsList.js | 4 ++++ src/libs/OptionsListUtils.js | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index f633156b6ed7..2a71fb189ee4 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -228,6 +228,10 @@ function BaseOptionsList({ * @return {Component} */ const renderSectionHeader = ({section: {title, shouldShow}}) => { + if (!title && shouldShow && !hideSectionHeaders && sectionHeaderStyle) { + return ; + } + if (title && shouldShow && !hideSectionHeaders) { return ( // Note: The `optionsListSectionHeader` style provides an explicit height to section headers. diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index e909f0d86453..5391e5f4c234 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -756,7 +756,7 @@ function getCategoryListSections(categories, recentlyUsedCategories, selectedOpt categorySections.push({ // "Search" section title: '', - shouldShow: false, + shouldShow: true, indexOffset, data: getCategoryOptionTree(searchCategories, true), }); @@ -790,7 +790,7 @@ function getCategoryListSections(categories, recentlyUsedCategories, selectedOpt categorySections.push({ // "Selected" section title: '', - shouldShow: false, + shouldShow: true, indexOffset, data: getCategoryOptionTree(selectedOptions, true), }); From 0ca4eb83f513ac0d419f504257028ed97e9f5725 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 24 Oct 2023 17:22:05 +0200 Subject: [PATCH 122/449] add a section header for empty title --- src/libs/OptionsListUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index c61d7368b88d..eca0519a8011 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -962,7 +962,7 @@ function getTagListSections(tags, recentlyUsedTags, selectedOptions, searchInput tagSections.push({ // "Search" section title: '', - shouldShow: false, + shouldShow: true, indexOffset, data: getTagsOptions(searchTags), }); @@ -1004,7 +1004,7 @@ function getTagListSections(tags, recentlyUsedTags, selectedOptions, searchInput tagSections.push({ // "Selected" section title: '', - shouldShow: false, + shouldShow: true, indexOffset, data: getTagsOptions(selectedTagOptions), }); From 127bff691099a5cebea9994c9582b01b96e35bbd Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 24 Oct 2023 17:24:57 +0200 Subject: [PATCH 123/449] fix tests --- tests/unit/OptionsListUtilsTest.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 7a9fbb558455..006f8a82febd 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -729,7 +729,7 @@ describe('OptionsListUtils', () => { const smallSearchResultList = [ { title: '', - shouldShow: false, + shouldShow: true, indexOffset: 0, data: [ { @@ -752,7 +752,7 @@ describe('OptionsListUtils', () => { const smallWrongSearchResultList = [ { title: '', - shouldShow: false, + shouldShow: true, indexOffset: 0, data: [], }, @@ -818,7 +818,7 @@ describe('OptionsListUtils', () => { const largeResultList = [ { title: '', - shouldShow: false, + shouldShow: true, indexOffset: 0, data: [ { @@ -932,7 +932,7 @@ describe('OptionsListUtils', () => { const largeSearchResultList = [ { title: '', - shouldShow: false, + shouldShow: true, indexOffset: 0, data: [ { @@ -962,7 +962,7 @@ describe('OptionsListUtils', () => { const largeWrongSearchResultList = [ { title: '', - shouldShow: false, + shouldShow: true, indexOffset: 0, data: [], }, @@ -1105,7 +1105,7 @@ describe('OptionsListUtils', () => { const smallSearchResultList = [ { title: '', - shouldShow: false, + shouldShow: true, indexOffset: 0, data: [ { @@ -1121,7 +1121,7 @@ describe('OptionsListUtils', () => { const smallWrongSearchResultList = [ { title: '', - shouldShow: false, + shouldShow: true, indexOffset: 0, data: [], }, @@ -1175,7 +1175,7 @@ describe('OptionsListUtils', () => { const largeResultList = [ { title: '', - shouldShow: false, + shouldShow: true, indexOffset: 0, data: [ { @@ -1261,7 +1261,7 @@ describe('OptionsListUtils', () => { const largeSearchResultList = [ { title: '', - shouldShow: false, + shouldShow: true, indexOffset: 0, data: [ { @@ -1284,7 +1284,7 @@ describe('OptionsListUtils', () => { const largeWrongSearchResultList = [ { title: '', - shouldShow: false, + shouldShow: true, indexOffset: 0, data: [], }, From 87f60149c8b3540a06d9b51f67e6f77110b42b2f Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 24 Oct 2023 18:25:05 +0200 Subject: [PATCH 124/449] limit report routes in customStackNavigator --- .../createCustomStackNavigator/index.js | 36 ++++++++++- .../index.native.js | 60 +++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.js diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js index 58be3d2af3da..bfc56cf171c0 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js @@ -1,9 +1,10 @@ -import React, {useRef} from 'react'; +import React, {useRef, useMemo} from 'react'; import PropTypes from 'prop-types'; import {useNavigationBuilder, createNavigatorFactory} from '@react-navigation/native'; import {StackView} from '@react-navigation/stack'; import CustomRouter from './CustomRouter'; import useWindowDimensions from '../../../../hooks/useWindowDimensions'; +import NAVIGATORS from '../../../../NAVIGATORS'; const propTypes = { /* Determines if the navigator should render the StackView (narrow) or ThreePaneView (wide) */ @@ -25,6 +26,24 @@ const defaultProps = { screenOptions: undefined, }; +function splitRoutes(routes) { + const reportRoutes = []; + const rhpRoutes = []; + const otherRoutes = []; + + routes.forEach((route) => { + if (route.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR) { + reportRoutes.push(route); + } else if (route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR) { + rhpRoutes.push(route); + } else { + otherRoutes.push(route); + } + }); + + return {reportRoutes, rhpRoutes, otherRoutes}; +} + function ResponsiveStackNavigator(props) { const {isSmallScreenWidth} = useWindowDimensions(); @@ -40,12 +59,25 @@ function ResponsiveStackNavigator(props) { getIsSmallScreenWidth: () => isSmallScreenWidthRef.current, }); + const stateToRender = useMemo(() => { + const {reportRoutes, rhpRoutes, otherRoutes} = splitRoutes(state.routes); + + // Remove all report routes except the last 3. This will improve performance. + const limitedReportRoutes = reportRoutes.slice(-3); + + return { + ...state, + index: otherRoutes.length + limitedReportRoutes.length + rhpRoutes.length - 1, + routes: [...otherRoutes, ...limitedReportRoutes, ...rhpRoutes], + }; + }, [state]); + return ( diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.js b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.js new file mode 100644 index 000000000000..58be3d2af3da --- /dev/null +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.js @@ -0,0 +1,60 @@ +import React, {useRef} from 'react'; +import PropTypes from 'prop-types'; +import {useNavigationBuilder, createNavigatorFactory} from '@react-navigation/native'; +import {StackView} from '@react-navigation/stack'; +import CustomRouter from './CustomRouter'; +import useWindowDimensions from '../../../../hooks/useWindowDimensions'; + +const propTypes = { + /* Determines if the navigator should render the StackView (narrow) or ThreePaneView (wide) */ + isSmallScreenWidth: PropTypes.bool.isRequired, + + /* Children for the useNavigationBuilder hook */ + children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired, + + /* initialRouteName for this navigator */ + initialRouteName: PropTypes.oneOf([PropTypes.string, PropTypes.undefined]), + + /* Screen options defined for this navigator */ + // eslint-disable-next-line react/forbid-prop-types + screenOptions: PropTypes.object, +}; + +const defaultProps = { + initialRouteName: undefined, + screenOptions: undefined, +}; + +function ResponsiveStackNavigator(props) { + const {isSmallScreenWidth} = useWindowDimensions(); + + const isSmallScreenWidthRef = useRef(isSmallScreenWidth); + + isSmallScreenWidthRef.current = isSmallScreenWidth; + + const {navigation, state, descriptors, NavigationContent} = useNavigationBuilder(CustomRouter, { + children: props.children, + screenOptions: props.screenOptions, + initialRouteName: props.initialRouteName, + // Options for useNavigationBuilder won't update on prop change, so we need to pass a getter for the router to have the current state of isSmallScreenWidth. + getIsSmallScreenWidth: () => isSmallScreenWidthRef.current, + }); + + return ( + + + + ); +} + +ResponsiveStackNavigator.defaultProps = defaultProps; +ResponsiveStackNavigator.propTypes = propTypes; +ResponsiveStackNavigator.displayName = 'ResponsiveStackNavigator'; + +export default createNavigatorFactory(ResponsiveStackNavigator); From 09e0d7400c47e79d39210a50433ede79684b1461 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Tue, 24 Oct 2023 18:29:11 +0200 Subject: [PATCH 125/449] Add FormProvider in EditRequestDescriptionPage --- src/pages/EditRequestDescriptionPage.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/EditRequestDescriptionPage.js b/src/pages/EditRequestDescriptionPage.js index 0c0fcad7f60b..2a8bb8785124 100644 --- a/src/pages/EditRequestDescriptionPage.js +++ b/src/pages/EditRequestDescriptionPage.js @@ -5,13 +5,14 @@ import {useFocusEffect} from '@react-navigation/native'; import TextInput from '../components/TextInput'; import ScreenWrapper from '../components/ScreenWrapper'; import HeaderWithBackButton from '../components/HeaderWithBackButton'; -import Form from '../components/Form'; import ONYXKEYS from '../ONYXKEYS'; import styles from '../styles/styles'; import CONST from '../CONST'; import useLocalize from '../hooks/useLocalize'; import * as Browser from '../libs/Browser'; import updateMultilineInputRange from '../libs/UpdateMultilineInputRange'; +import FormProvider from '../components/Form/FormProvider'; +import InputWrapper from '../components/Form/InputWrapper'; const propTypes = { /** Transaction default description value */ @@ -49,7 +50,7 @@ function EditRequestDescriptionPage({defaultDescription, onSubmit}) { testID={EditRequestDescriptionPage.displayName} > -
- -
+
); } From f7d19008dfa0ca6f68986396776ed57a533c5fba Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 24 Oct 2023 19:15:19 +0200 Subject: [PATCH 126/449] update options on transition end --- src/pages/SearchPage.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 272fb30de858..f0796ffb7000 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -65,15 +65,13 @@ class SearchPage extends Component { this.searchRendered = this.searchRendered.bind(this); this.selectReport = this.selectReport.bind(this); this.onChangeText = this.onChangeText.bind(this); + this.updateOptions = this.updateOptions.bind(this); this.debouncedUpdateOptions = _.debounce(this.updateOptions.bind(this), 75); - - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getSearchOptions(props.reports, props.personalDetails, '', props.betas); - this.state = { searchValue: '', - recentReports, - personalDetails, - userToInvite, + recentReports: {}, + personalDetails: {}, + userToInvite: {}, }; } @@ -186,6 +184,7 @@ class SearchPage extends Component { {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( <> From 0e4411cff508bdad6b54f855e843156699d98981 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 24 Oct 2023 19:15:20 +0200 Subject: [PATCH 127/449] remove timeout on search focus --- src/components/OptionsSelector/BaseOptionsSelector.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 0125fc8e178e..5e37db057db5 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -75,9 +75,7 @@ class BaseOptionsSelector extends Component { this.subscribeToKeyboardShortcut(); if (this.props.isFocused && this.props.autoFocus && this.textInput) { - this.focusTimeout = setTimeout(() => { - this.textInput.focus(); - }, CONST.ANIMATED_TRANSITION); + this.textInput.focus(); } this.scrollToIndex(this.props.selectedOptions.length ? 0 : this.state.focusedIndex, false); @@ -139,10 +137,6 @@ class BaseOptionsSelector extends Component { } componentWillUnmount() { - if (this.focusTimeout) { - clearTimeout(this.focusTimeout); - } - this.unSubscribeFromKeyboardShortcut(); } From b41c35a2c1c9956ae4e302fc0348a07b6edcd301 Mon Sep 17 00:00:00 2001 From: Kailash Devrari Date: Wed, 25 Oct 2023 01:59:03 +0530 Subject: [PATCH 128/449] fixed auto submit form when enter key pressed in selection modals --- .../NewDatePicker/CalendarPicker/YearPickerModal.js | 1 + src/components/SelectionList/BaseSelectionList.js | 2 ++ src/components/SelectionList/selectionListPropTypes.js | 3 +++ src/components/StatePicker/StateSelectorModal.js | 1 + src/components/ValuePicker/ValueSelectorModal.js | 1 + src/hooks/useKeyboardShortcut.js | 4 +++- src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js | 4 +++- src/libs/KeyboardShortcut/index.js | 4 +++- 8 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js b/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js index 9825109fbb63..1ceddfdc4778 100644 --- a/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js +++ b/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js @@ -82,6 +82,7 @@ function YearPickerModal(props) { onSelectRow={(option) => props.onYearChange(option.value)} initiallyFocusedOptionKey={props.currentYear.toString()} showScrollIndicator + shouldStopPropagation /> diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index fdb1f92ca73b..db590d5e5439 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -57,6 +57,7 @@ function BaseSelectionList({ inputRef = null, disableKeyboardShortcuts = false, children, + shouldStopPropagation = false, }) { const {translate} = useLocalize(); const firstLayoutRef = useRef(true); @@ -339,6 +340,7 @@ function BaseSelectionList({ useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, selectFocusedOption, { captureOnInputs: true, shouldBubble: () => !flattenedSections.allOptions[focusedIndex], + shouldStopPropagation, isActive: !disableKeyboardShortcuts && !disableEnterShortcut && isFocused, }); diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index e75335e39b23..a0fb36bee917 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -168,6 +168,9 @@ const propTypes = { /** Whether to show the default confirm button */ showConfirmButton: PropTypes.bool, + /** Whether to stop automatic form submission on pressing enter key or not */ + shouldStopPropagation : PropTypes.bool, + /** Whether to prevent default focusing of options and focus the textinput when selecting an option */ shouldPreventDefaultFocusOnSelectRow: PropTypes.bool, diff --git a/src/components/StatePicker/StateSelectorModal.js b/src/components/StatePicker/StateSelectorModal.js index 378dcc4ebc8b..21ffea5bbdf8 100644 --- a/src/components/StatePicker/StateSelectorModal.js +++ b/src/components/StatePicker/StateSelectorModal.js @@ -99,6 +99,7 @@ function StateSelectorModal({currentState, isVisible, onClose, onStateSelected, onSelectRow={onStateSelected} onChangeText={setSearchValue} initiallyFocusedOptionKey={currentState} + shouldStopPropagation />
diff --git a/src/components/ValuePicker/ValueSelectorModal.js b/src/components/ValuePicker/ValueSelectorModal.js index 23aac4839d2a..3f2a05dd170c 100644 --- a/src/components/ValuePicker/ValueSelectorModal.js +++ b/src/components/ValuePicker/ValueSelectorModal.js @@ -71,6 +71,7 @@ function ValueSelectorModal({currentValue, items, selectedItem, label, isVisible sections={[{data: sectionsData}]} onSelectRow={onItemSelected} initiallyFocusedOptionKey={currentValue} + shouldStopPropagation />
diff --git a/src/hooks/useKeyboardShortcut.js b/src/hooks/useKeyboardShortcut.js index b8e297c966ed..d42727f5fb4a 100644 --- a/src/hooks/useKeyboardShortcut.js +++ b/src/hooks/useKeyboardShortcut.js @@ -21,6 +21,7 @@ export default function useKeyboardShortcut(shortcut, callback, config = {}) { // Hence the use of CONST.EMPTY_ARRAY. excludedNodes = CONST.EMPTY_ARRAY, isActive = true, + shouldStopPropagation =false } = config; useEffect(() => { @@ -35,8 +36,9 @@ export default function useKeyboardShortcut(shortcut, callback, config = {}) { priority, shouldPreventDefault, excludedNodes, + shouldStopPropagation, ); } return () => {}; - }, [isActive, callback, captureOnInputs, excludedNodes, priority, shortcut.descriptionKey, shortcut.modifiers, shortcut.shortcutKey, shouldBubble, shouldPreventDefault]); + }, [isActive, callback, captureOnInputs, excludedNodes, priority, shortcut.descriptionKey, shortcut.modifiers, shortcut.shortcutKey, shouldBubble, shouldPreventDefault, shouldStopPropagation]); } diff --git a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js index 7b1cb00a408b..f4c5d24b0302 100644 --- a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js +++ b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js @@ -42,7 +42,9 @@ function bindHandlerToKeydownEvent(getDisplayName, eventHandlers, keycommandEven if (callback.shouldPreventDefault) { event.preventDefault(); } - + if (callback.shouldStopPropagation) { + event.stopPropagation(); + } // If the event should not bubble, short-circuit the loop return shouldBubble; }); diff --git a/src/libs/KeyboardShortcut/index.js b/src/libs/KeyboardShortcut/index.js index bce65744801c..1fc2c3b276ec 100644 --- a/src/libs/KeyboardShortcut/index.js +++ b/src/libs/KeyboardShortcut/index.js @@ -116,9 +116,10 @@ function getPlatformEquivalentForKeys(keys) { * @param {Number} [priority] The position the callback should take in the stack. 0 means top priority, and 1 means less priority than the most recently added. * @param {Boolean} [shouldPreventDefault] Should call event.preventDefault after callback? * @param {Array} [excludedNodes] Do not capture key events targeting excluded nodes (i.e. do not prevent default and let the event bubble) + * @param {Boolean} [shouldStopPropagation] Should call event.stopPropagation after callback? * @returns {Function} clean up method */ -function subscribe(key, callback, descriptionKey, modifiers = 'shift', captureOnInputs = false, shouldBubble = false, priority = 0, shouldPreventDefault = true, excludedNodes = []) { +function subscribe(key, callback, descriptionKey, modifiers = 'shift', captureOnInputs = false, shouldBubble = false, priority = 0, shouldPreventDefault = true, excludedNodes = [], shouldStopPropagation = false) { const platformAdjustedModifiers = getPlatformEquivalentForKeys(modifiers); const displayName = getDisplayName(key, platformAdjustedModifiers); if (!_.has(eventHandlers, displayName)) { @@ -133,6 +134,7 @@ function subscribe(key, callback, descriptionKey, modifiers = 'shift', captureOn shouldPreventDefault, shouldBubble, excludedNodes, + shouldStopPropagation, }); if (descriptionKey) { From 62e087b9eca0d8608f01b3a0cecb1aea8d06ff4d Mon Sep 17 00:00:00 2001 From: VH Date: Wed, 25 Oct 2023 05:54:20 +0700 Subject: [PATCH 129/449] Always show confirmation button --- .../MoneyRequestParticipantsSelector.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 7e88ebe7db48..bc6e5f02ff5e 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -248,7 +248,6 @@ function MoneyRequestParticipantsSelector({ // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); - const shouldShowConfirmButton = !(participants.length > 1 && hasPolicyExpenseChatParticipant); const isAllowedToSplit = !isDistanceRequest && iouType !== CONST.IOU.TYPE.SEND; return ( @@ -266,7 +265,6 @@ function MoneyRequestParticipantsSelector({ ref={forwardedRef} headerMessage={headerMessage} boldStyle - shouldShowConfirmButton={shouldShowConfirmButton && isAllowedToSplit} confirmButtonText={translate('iou.addToSplit')} onConfirmSelection={navigateToSplit} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} @@ -274,6 +272,7 @@ function MoneyRequestParticipantsSelector({ shouldShowOptions={isOptionsDataReady} shouldPreventDefaultFocusOnSelectRow={!Browser.isMobile()} shouldDelayFocus + footerContent />
); From 40b36e0a15029bab343047d0c5e0a0752ead876e Mon Sep 17 00:00:00 2001 From: VH Date: Wed, 25 Oct 2023 06:23:18 +0700 Subject: [PATCH 130/449] Display warning message when split bill with multiple participants including a workspace --- .../MoneyRequestParticipantsSelector.js | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index bc6e5f02ff5e..62548fb2854c 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -16,6 +16,8 @@ import CONST from '../../../../CONST'; import personalDetailsPropType from '../../../personalDetailsPropType'; import reportPropTypes from '../../../reportPropTypes'; import refPropTypes from '../../../../components/refPropTypes'; +import Button from '../../../../components/Button'; +import FormHelpMessage from '../../../../components/FormHelpMessage'; const propTypes = { /** Beta features list */ @@ -248,8 +250,27 @@ function MoneyRequestParticipantsSelector({ // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); + const shouldShowErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; const isAllowedToSplit = !isDistanceRequest && iouType !== CONST.IOU.TYPE.SEND; + const footerContent = + {shouldShowErrorMessage && ( + + )} +