From 8b78aa1fe8b1b1e032c4eb385b17c9e7fcdb4a8a Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Wed, 19 Jul 2023 16:56:17 -0700 Subject: [PATCH 001/156] Send debtor accountID in RequestMoney --- src/libs/actions/IOU.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index e480120a7323..3d66d8e2b511 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -411,6 +411,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part 'RequestMoney', { debtorEmail: payerEmail, + debtorAccountID: payerAccountID, amount, currency, comment, From 593df794004f2cf8b16eb99983c6ee9ce33cd7fc Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 25 Aug 2023 14:50:40 +0200 Subject: [PATCH 002/156] Prepare POC --- src/components/DummyComponentWithStateHook.js | 16 ++ src/hooks/form/FormWrapper.js | 206 ++++++++++++++++++ src/hooks/form/useForm.js | 112 ++++++++++ src/pages/settings/Profile/DisplayNamePage.js | 52 +++-- 4 files changed, 364 insertions(+), 22 deletions(-) create mode 100644 src/components/DummyComponentWithStateHook.js create mode 100644 src/hooks/form/FormWrapper.js create mode 100644 src/hooks/form/useForm.js diff --git a/src/components/DummyComponentWithStateHook.js b/src/components/DummyComponentWithStateHook.js new file mode 100644 index 000000000000..1ed2f8dab43b --- /dev/null +++ b/src/components/DummyComponentWithStateHook.js @@ -0,0 +1,16 @@ +import {Text} from 'react-native'; +import React, {useState} from 'react'; + +const propTypes = {}; +const defaultProps = {}; + +function DummyComponentWithStateHook() { + const [state] = useState(''); + return {state}; +} + +DummyComponentWithStateHook.propTypes = propTypes; +DummyComponentWithStateHook.defaultProps = defaultProps; +DummyComponentWithStateHook.displayName = 'DummyComponentWithStateHook'; + +export default DummyComponentWithStateHook; diff --git a/src/hooks/form/FormWrapper.js b/src/hooks/form/FormWrapper.js new file mode 100644 index 000000000000..a63d282efb7e --- /dev/null +++ b/src/hooks/form/FormWrapper.js @@ -0,0 +1,206 @@ +import React, {useCallback, useMemo, useRef} from 'react'; +import {Keyboard, ScrollView, StyleSheet} from 'react-native'; +import _ from 'underscore'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import * as ErrorUtils from '../../libs/ErrorUtils'; +import FormSubmit from '../../components/FormSubmit'; +import FormAlertWithSubmitButton from '../../components/FormAlertWithSubmitButton'; +import styles from '../../styles/styles'; +import SafeAreaConsumer from '../../components/SafeAreaConsumer'; +import ScrollViewWithContext from '../../components/ScrollViewWithContext'; + +import stylePropTypes from '../../styles/stylePropTypes'; +import networkPropTypes from '../../components/networkPropTypes'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; +import {withNetwork} from '../../components/OnyxProvider'; + +const propTypes = { + /** A unique Onyx key identifying the form */ + formID: PropTypes.string.isRequired, + + /** Text to be displayed in the submit button */ + submitButtonText: PropTypes.string.isRequired, + + /** Controls the submit button's visibility */ + isSubmitButtonVisible: PropTypes.bool, + + /** Callback to validate the form */ + validate: PropTypes.func, + + /** Callback to submit the form */ + onSubmit: PropTypes.func.isRequired, + + /** Children to render. */ + children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired, + + /* Onyx Props */ + + /** Contains the form state that must be accessed outside of the component */ + formState: PropTypes.shape({ + /** Controls the loading state of the form */ + isLoading: PropTypes.bool, + + /** Server side errors keyed by microtime */ + errors: PropTypes.objectOf(PropTypes.string), + + /** Field-specific server side errors keyed by microtime */ + errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + }), + + /** Should the button be enabled when offline */ + enabledWhenOffline: PropTypes.bool, + + /** Whether the form submit action is dangerous */ + isSubmitActionDangerous: PropTypes.bool, + + /** Whether ScrollWithContext should be used instead of regular ScrollView. + * Set to true when there's a nested Picker component in Form. + */ + scrollContextEnabled: PropTypes.bool, + + /** Container styles */ + style: stylePropTypes, + + /** Custom content to display in the footer after submit button */ + footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + + /** Information about the network */ + network: networkPropTypes.isRequired, + + ...withLocalizePropTypes, +}; + +const defaultProps = { + isSubmitButtonVisible: true, + formState: { + isLoading: false, + }, + enabledWhenOffline: false, + isSubmitActionDangerous: false, + scrollContextEnabled: false, + footerContent: null, + style: [], + validate: () => ({}), +}; + +function FormWrapper(props) { + const formRef = useRef(null); + const formContentRef = useRef(null); + const {onSubmit, children, formState, errors, inputRefs, submitButtonText, footerContent, isSubmitButtonVisible, style, enabledWhenOffline, isSubmitActionDangerous, formID} = props; + + const errorMessage = useMemo(() => { + const latestErrorMessage = ErrorUtils.getLatestErrorMessage(props.formState); + return typeof latestErrorMessage === 'string' ? latestErrorMessage : ''; + }, [formState]); + + const scrollViewContent = useCallback( + (safeAreaPaddingBottomStyle) => ( + + {children} + {isSubmitButtonVisible && ( + 0 || Boolean(errorMessage) || !_.isEmpty(formState.errorFields)} + isLoading={formState.isLoading} + message={_.isEmpty(formState.errorFields) ? errorMessage : null} + onSubmit={onSubmit} + footerContent={footerContent} + onFixTheErrorsLinkPressed={() => { + const errorFields = !_.isEmpty(errors) ? errors : formState.errorFields; + const focusKey = _.find(_.keys(inputRefs), (key) => _.keys(errorFields).includes(key)); + const focusInput = inputRefs[focusKey].current; + + // Dismiss the keyboard for non-text fields by checking if the component has the isFocused method, as only TextInput has this method. + if (typeof focusInput.isFocused !== 'function') { + Keyboard.dismiss(); + } + + // We subtract 10 to scroll slightly above the input + if (focusInput.measureLayout && typeof focusInput.measureLayout === 'function') { + // We measure relative to the content root, not the scroll view, as that gives + // consistent results across mobile and web + focusInput.measureLayout(formContentRef.current, (x, y) => + formRef.current.scrollTo({ + y: y - 10, + animated: false, + }), + ); + } + + // Focus the input after scrolling, as on the Web it gives a slightly better visual result + if (focusInput.focus && typeof focusInput.focus === 'function') { + focusInput.focus(); + } + }} + containerStyles={[styles.mh0, styles.mt5, styles.flex1]} + enabledWhenOffline={enabledWhenOffline} + isSubmitActionDangerous={isSubmitActionDangerous} + disablePressOnEnter + /> + )} + + ), + [ + style, + onSubmit, + children, + isSubmitButtonVisible, + submitButtonText, + errors, + errorMessage, + formState.errorFields, + formState.isLoading, + footerContent, + enabledWhenOffline, + isSubmitActionDangerous, + inputRefs, + ], + ); + + return ( + + {({safeAreaPaddingBottomStyle}) => + props.scrollContextEnabled ? ( + + {scrollViewContent(safeAreaPaddingBottomStyle)} + + ) : ( + + {scrollViewContent(safeAreaPaddingBottomStyle)} + + ) + } + + ); +} + +FormWrapper.displayName = 'Form'; +FormWrapper.propTypes = propTypes; +FormWrapper.defaultProps = defaultProps; + +export default compose( + withLocalize, + withNetwork(), + withOnyx({ + formState: { + key: (props) => props.formID, + }, + }), +)(FormWrapper); diff --git a/src/hooks/form/useForm.js b/src/hooks/form/useForm.js new file mode 100644 index 000000000000..5fc13f701441 --- /dev/null +++ b/src/hooks/form/useForm.js @@ -0,0 +1,112 @@ +import React, {createRef, useCallback, useRef, useState} from 'react'; +import _ from 'underscore'; +import FormWrapper from './FormWrapper'; +import Visibility from '../../libs/Visibility'; +import * as FormActions from '../../libs/actions/FormActions'; + +/* eslint-disable react/jsx-props-no-spreading */ +function useForm({validate, shouldValidateOnBlur = true, shouldValidateOnChange = true}) { + const refs = useRef({}); + const touchedInputs = useRef({}); + const [inputValues, setInputValues] = useState({}); + const [errors, setErrors] = useState([]); + + const onValidate = (values) => { + const validateErrors = validate(values); + setErrors(validateErrors); + }; + /** + * @param {String} inputID - The inputID of the input being touched + */ + const setTouchedInput = useCallback( + (inputID) => { + touchedInputs.current[inputID] = true; + }, + [touchedInputs], + ); + + const registerInput = (inputID, props = {}) => { + const newRef = props.ref || createRef(); + refs[inputID] = newRef; + + // We want to initialize the input value if it's undefined + if (_.isUndefined(inputValues[inputID])) { + inputValues[inputID] = props.defaultValue || ''; + } + + // We force the form to set the input value from the defaultValue props if there is a saved valid value + if (props.shouldUseDefaultValue) { + inputValues[inputID] = props.defaultValue; + } + + if (!_.isUndefined(props.value)) { + inputValues[inputID] = props.value; + } + + return { + ...props, + ref: newRef, + inputID, + errorText: errors[inputID], + value: inputValues[inputID], + // As the text input is controlled, we never set the defaultValue prop + // as this is already happening by the value prop. + defaultValue: undefined, + onTouched: (event) => { + setTouchedInput(inputID); + if (_.isFunction(props.onTouched)) { + props.onTouched(event); + } + }, + onBlur: (event) => { + // Only run validation when user proactively blurs the input. + if (Visibility.isVisible() && Visibility.hasFocus()) { + // We delay the validation in order to prevent Checkbox loss of focus when + // the user are focusing a TextInput and proceeds to toggle a CheckBox in + // web and mobile web platforms. + setTimeout(() => { + setTouchedInput(inputID); + if (shouldValidateOnBlur) { + onValidate(inputValues); + } + }, 200); + } + + if (_.isFunction(props.onBlur)) { + props.onBlur(event); + } + }, + onInputChange: (value, key) => { + const inputKey = key || inputID; + setInputValues((prevState) => { + const newState = { + ...prevState, + [inputKey]: value, + }; + + if (shouldValidateOnChange) { + onValidate(newState); + } + return newState; + }); + + if (props.shouldSaveDraft) { + FormActions.setDraftValues(props.formID, {[inputKey]: value}); + } + + if (_.isFunction(props.onValueChange)) { + props.onValueChange(value, inputKey); + } + }, + }; + }; + + const Form = useCallback((props) => { + const {children, ...rest} = props; + return {children}; + }, []); + + return {registerInput, Form}; +} + +export default useForm; diff --git a/src/pages/settings/Profile/DisplayNamePage.js b/src/pages/settings/Profile/DisplayNamePage.js index 31ec955ed0c6..b872e494ce39 100644 --- a/src/pages/settings/Profile/DisplayNamePage.js +++ b/src/pages/settings/Profile/DisplayNamePage.js @@ -1,11 +1,10 @@ import lodashGet from 'lodash/get'; import React from 'react'; import {View} from 'react-native'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../../components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '../../../components/withCurrentUserPersonalDetails'; import ScreenWrapper from '../../../components/ScreenWrapper'; import HeaderWithBackButton from '../../../components/HeaderWithBackButton'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -import Form from '../../../components/Form'; import ONYXKEYS from '../../../ONYXKEYS'; import CONST from '../../../CONST'; import * as ValidationUtils from '../../../libs/ValidationUtils'; @@ -13,10 +12,12 @@ import TextInput from '../../../components/TextInput'; import Text from '../../../components/Text'; import styles from '../../../styles/styles'; import * as PersonalDetails from '../../../libs/actions/PersonalDetails'; +import DummyComponentWithStateHook from '../../../components/DummyComponentWithStateHook'; import compose from '../../../libs/compose'; import * as ErrorUtils from '../../../libs/ErrorUtils'; import ROUTES from '../../../ROUTES'; import Navigation from '../../../libs/Navigation/Navigation'; +import useForm from '../../../hooks/form/useForm'; const propTypes = { ...withLocalizePropTypes, @@ -47,7 +48,8 @@ function DisplayNamePage(props) { * @returns {Object} - An object containing the errors for each inputID */ const validate = (values) => { - const errors = {}; + const requiredFields = ['firstName']; + const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); // First we validate the first name field if (!ValidationUtils.isValidDisplayName(values.firstName)) { @@ -61,10 +63,13 @@ function DisplayNamePage(props) { if (!ValidationUtils.isValidDisplayName(values.lastName)) { errors.lastName = 'personalDetails.error.hasInvalidCharacter'; } - return errors; }; + // register input requires props spreading + /* eslint-disable react/jsx-props-no-spreading */ + const {Form, registerInput} = useForm({validate}); + return ( + {props.translate('displayNamePage.isShownOnProfile')} From 32beaafcc9f01bd54a7ad779b871ad79dcf6fd33 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Tue, 5 Sep 2023 11:23:08 +0200 Subject: [PATCH 003/156] POC of Form using custom input components --- src/hooks/form/useForm.js | 112 --------- src/hooks/useForm/FormContext.js | 4 + src/hooks/useForm/FormProvider.js | 235 ++++++++++++++++++ src/hooks/{form => useForm}/FormWrapper.js | 2 +- src/hooks/useForm/InputWrapper.js | 11 + src/hooks/useForm/index.js | 11 + src/pages/settings/Profile/DisplayNamePage.js | 53 ++-- 7 files changed, 289 insertions(+), 139 deletions(-) delete mode 100644 src/hooks/form/useForm.js create mode 100644 src/hooks/useForm/FormContext.js create mode 100644 src/hooks/useForm/FormProvider.js rename src/hooks/{form => useForm}/FormWrapper.js (99%) create mode 100644 src/hooks/useForm/InputWrapper.js create mode 100644 src/hooks/useForm/index.js diff --git a/src/hooks/form/useForm.js b/src/hooks/form/useForm.js deleted file mode 100644 index 5fc13f701441..000000000000 --- a/src/hooks/form/useForm.js +++ /dev/null @@ -1,112 +0,0 @@ -import React, {createRef, useCallback, useRef, useState} from 'react'; -import _ from 'underscore'; -import FormWrapper from './FormWrapper'; -import Visibility from '../../libs/Visibility'; -import * as FormActions from '../../libs/actions/FormActions'; - -/* eslint-disable react/jsx-props-no-spreading */ -function useForm({validate, shouldValidateOnBlur = true, shouldValidateOnChange = true}) { - const refs = useRef({}); - const touchedInputs = useRef({}); - const [inputValues, setInputValues] = useState({}); - const [errors, setErrors] = useState([]); - - const onValidate = (values) => { - const validateErrors = validate(values); - setErrors(validateErrors); - }; - /** - * @param {String} inputID - The inputID of the input being touched - */ - const setTouchedInput = useCallback( - (inputID) => { - touchedInputs.current[inputID] = true; - }, - [touchedInputs], - ); - - const registerInput = (inputID, props = {}) => { - const newRef = props.ref || createRef(); - refs[inputID] = newRef; - - // We want to initialize the input value if it's undefined - if (_.isUndefined(inputValues[inputID])) { - inputValues[inputID] = props.defaultValue || ''; - } - - // We force the form to set the input value from the defaultValue props if there is a saved valid value - if (props.shouldUseDefaultValue) { - inputValues[inputID] = props.defaultValue; - } - - if (!_.isUndefined(props.value)) { - inputValues[inputID] = props.value; - } - - return { - ...props, - ref: newRef, - inputID, - errorText: errors[inputID], - value: inputValues[inputID], - // As the text input is controlled, we never set the defaultValue prop - // as this is already happening by the value prop. - defaultValue: undefined, - onTouched: (event) => { - setTouchedInput(inputID); - if (_.isFunction(props.onTouched)) { - props.onTouched(event); - } - }, - onBlur: (event) => { - // Only run validation when user proactively blurs the input. - if (Visibility.isVisible() && Visibility.hasFocus()) { - // We delay the validation in order to prevent Checkbox loss of focus when - // the user are focusing a TextInput and proceeds to toggle a CheckBox in - // web and mobile web platforms. - setTimeout(() => { - setTouchedInput(inputID); - if (shouldValidateOnBlur) { - onValidate(inputValues); - } - }, 200); - } - - if (_.isFunction(props.onBlur)) { - props.onBlur(event); - } - }, - onInputChange: (value, key) => { - const inputKey = key || inputID; - setInputValues((prevState) => { - const newState = { - ...prevState, - [inputKey]: value, - }; - - if (shouldValidateOnChange) { - onValidate(newState); - } - return newState; - }); - - if (props.shouldSaveDraft) { - FormActions.setDraftValues(props.formID, {[inputKey]: value}); - } - - if (_.isFunction(props.onValueChange)) { - props.onValueChange(value, inputKey); - } - }, - }; - }; - - const Form = useCallback((props) => { - const {children, ...rest} = props; - return {children}; - }, []); - - return {registerInput, Form}; -} - -export default useForm; diff --git a/src/hooks/useForm/FormContext.js b/src/hooks/useForm/FormContext.js new file mode 100644 index 000000000000..40edaa7cca69 --- /dev/null +++ b/src/hooks/useForm/FormContext.js @@ -0,0 +1,4 @@ +import {createContext} from 'react'; + +const FormContext = createContext({}); +export default FormContext; diff --git a/src/hooks/useForm/FormProvider.js b/src/hooks/useForm/FormProvider.js new file mode 100644 index 000000000000..0961dce91df6 --- /dev/null +++ b/src/hooks/useForm/FormProvider.js @@ -0,0 +1,235 @@ +import React, {createRef, useCallback, useRef, useState} from 'react'; +import _ from 'underscore'; +import {withOnyx} from 'react-native-onyx'; +import PropTypes from 'prop-types'; +import Visibility from '../../libs/Visibility'; +import * as FormActions from '../../libs/actions/FormActions'; +import FormContext from './FormContext'; +import FormWrapper from './FormWrapper'; +import compose from '../../libs/compose'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import {withNetwork} from '../../components/OnyxProvider'; +import stylePropTypes from '../../styles/stylePropTypes'; +import networkPropTypes from '../../components/networkPropTypes'; + +const propTypes = { + /** A unique Onyx key identifying the form */ + formID: PropTypes.string.isRequired, + + /** Text to be displayed in the submit button */ + submitButtonText: PropTypes.string.isRequired, + + /** Controls the submit button's visibility */ + isSubmitButtonVisible: PropTypes.bool, + + /** Callback to validate the form */ + validate: PropTypes.func, + + /** Callback to submit the form */ + onSubmit: PropTypes.func.isRequired, + + /** Children to render. */ + children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired, + + /* Onyx Props */ + + /** Contains the form state that must be accessed outside of the component */ + formState: PropTypes.shape({ + /** Controls the loading state of the form */ + isLoading: PropTypes.bool, + + /** Server side errors keyed by microtime */ + errors: PropTypes.objectOf(PropTypes.string), + + /** Field-specific server side errors keyed by microtime */ + errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + }), + + /** Should the button be enabled when offline */ + enabledWhenOffline: PropTypes.bool, + + /** Whether the form submit action is dangerous */ + isSubmitActionDangerous: PropTypes.bool, + + /** Whether ScrollWithContext should be used instead of regular ScrollView. + * Set to true when there's a nested Picker component in Form. + */ + scrollContextEnabled: PropTypes.bool, + + /** Container styles */ + style: stylePropTypes, + + /** Custom content to display in the footer after submit button */ + footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + + /** Information about the network */ + network: networkPropTypes.isRequired, + + ...withLocalizePropTypes, +}; + +const defaultProps = { + isSubmitButtonVisible: true, + formState: { + isLoading: false, + }, + enabledWhenOffline: false, + isSubmitActionDangerous: false, + scrollContextEnabled: false, + footerContent: null, + style: [], + validate: () => ({}), +}; + +function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, children, formState, network, enabledWhenOffline, onSubmit, ...rest}) { + const inputRefs = useRef({}); + const touchedInputs = useRef({}); + const [inputValues, setInputValues] = useState({}); + const [errors, setErrors] = useState([]); + + const onValidate = useCallback( + (values) => { + const validateErrors = validate(values); + setErrors(validateErrors); + }, + [validate], + ); + + /** + * @param {String} inputID - The inputID of the input being touched + */ + const setTouchedInput = useCallback( + (inputID) => { + touchedInputs.current[inputID] = true; + }, + [touchedInputs], + ); + + const submit = useCallback(() => { + // Return early if the form is already submitting to avoid duplicate submission + if (formState.isLoading) { + return; + } + + // Touches all form inputs so we can validate the entire form + _.each(inputRefs.current, (inputRef, inputID) => (touchedInputs.current[inputID] = true)); + + // Validate form and return early if any errors are found + if (!_.isEmpty(onValidate(inputValues))) { + return; + } + + // Do not submit form if network is offline and the form is not enabled when offline + if (network.isOffline && !enabledWhenOffline) { + return; + } + // Call submit handler + onSubmit(inputValues); + }, [enabledWhenOffline, formState.isLoading, inputValues, network.isOffline, onSubmit, onValidate]); + + const registerInput = useCallback( + (inputID, propsToParse = {}) => { + const newRef = propsToParse.ref || createRef(); + inputRefs[inputID] = newRef; + + // We want to initialize the input value if it's undefined + if (_.isUndefined(inputValues[inputID])) { + inputValues[inputID] = propsToParse.defaultValue || ''; + } + + // We force the form to set the input value from the defaultValue props if there is a saved valid value + if (propsToParse.shouldUseDefaultValue) { + inputValues[inputID] = propsToParse.defaultValue; + } + + if (!_.isUndefined(propsToParse.value)) { + inputValues[inputID] = propsToParse.value; + } + + return { + ...propsToParse, + ref: newRef, + inputID, + key: propsToParse.key || inputID, + errorText: errors[inputID], + value: inputValues[inputID], + // As the text input is controlled, we never set the defaultValue prop + // as this is already happening by the value prop. + defaultValue: undefined, + onTouched: (event) => { + setTouchedInput(inputID); + if (_.isFunction(propsToParse.onTouched)) { + propsToParse.onTouched(event); + } + }, + onBlur: (event) => { + // Only run validation when user proactively blurs the input. + if (Visibility.isVisible() && Visibility.hasFocus()) { + // We delay the validation in order to prevent Checkbox loss of focus when + // the user are focusing a TextInput and proceeds to toggle a CheckBox in + // web and mobile web platforms. + setTimeout(() => { + setTouchedInput(inputID); + if (shouldValidateOnBlur) { + onValidate(inputValues); + } + }, 200); + } + + if (_.isFunction(propsToParse.onBlur)) { + propsToParse.onBlur(event); + } + }, + onInputChange: (value, key) => { + const inputKey = key || inputID; + setInputValues((prevState) => { + const newState = { + ...prevState, + [inputKey]: value, + }; + + if (shouldValidateOnChange) { + onValidate(newState); + } + return newState; + }); + + if (propsToParse.shouldSaveDraft) { + FormActions.setDraftValues(propsToParse.formID, {[inputKey]: value}); + } + + if (_.isFunction(propsToParse.onValueChange)) { + propsToParse.onValueChange(value, inputKey); + } + }, + }; + }, + [errors, inputValues, onValidate, setTouchedInput, shouldValidateOnBlur, shouldValidateOnChange], + ); + + return ( + + {/* eslint-disable react/jsx-props-no-spreading */} + + {children} + + + ); +} + +FormProvider.displayName = 'Form'; +FormProvider.propTypes = propTypes; +FormProvider.defaultProps = defaultProps; + +export default compose( + withLocalize, + withNetwork(), + withOnyx({ + formState: { + key: (props) => props.formID, + }, + }), +)(FormProvider); diff --git a/src/hooks/form/FormWrapper.js b/src/hooks/useForm/FormWrapper.js similarity index 99% rename from src/hooks/form/FormWrapper.js rename to src/hooks/useForm/FormWrapper.js index a63d282efb7e..221eb2b152f0 100644 --- a/src/hooks/form/FormWrapper.js +++ b/src/hooks/useForm/FormWrapper.js @@ -191,7 +191,7 @@ function FormWrapper(props) { ); } -FormWrapper.displayName = 'Form'; +FormWrapper.displayName = 'FormWrapper'; FormWrapper.propTypes = propTypes; FormWrapper.defaultProps = defaultProps; diff --git a/src/hooks/useForm/InputWrapper.js b/src/hooks/useForm/InputWrapper.js new file mode 100644 index 000000000000..ad3d4a2f0fc8 --- /dev/null +++ b/src/hooks/useForm/InputWrapper.js @@ -0,0 +1,11 @@ +import React, {forwardRef, useContext} from 'react'; +import FormContext from './FormContext'; + +const InputWrapper = forwardRef((props, ref) => { + const {RenderInput, inputID, ...rest} = props; + const {registerInput} = useContext(FormContext); + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +}); + +export default InputWrapper; diff --git a/src/hooks/useForm/index.js b/src/hooks/useForm/index.js new file mode 100644 index 000000000000..5e3a2b480172 --- /dev/null +++ b/src/hooks/useForm/index.js @@ -0,0 +1,11 @@ +import React, {useContext} from 'react'; +import InputWrapper from './InputWrapper'; +import FormContext from './FormContext'; +import FormProvider from './FormProvider'; + +function useForm() { + const formContext = useContext(FormContext); + return {Input: InputWrapper, Form: FormProvider, formContext}; +} + +export default useForm; diff --git a/src/pages/settings/Profile/DisplayNamePage.js b/src/pages/settings/Profile/DisplayNamePage.js index b872e494ce39..d13920881034 100644 --- a/src/pages/settings/Profile/DisplayNamePage.js +++ b/src/pages/settings/Profile/DisplayNamePage.js @@ -17,7 +17,7 @@ import compose from '../../../libs/compose'; import * as ErrorUtils from '../../../libs/ErrorUtils'; import ROUTES from '../../../ROUTES'; import Navigation from '../../../libs/Navigation/Navigation'; -import useForm from '../../../hooks/form/useForm'; +import useForm from '../../../hooks/useForm'; const propTypes = { ...withLocalizePropTypes, @@ -66,9 +66,7 @@ function DisplayNamePage(props) { return errors; }; - // register input requires props spreading - /* eslint-disable react/jsx-props-no-spreading */ - const {Form, registerInput} = useForm({validate}); + const {Form, Input} = useForm(); return ( {props.translate('displayNamePage.isShownOnProfile')} - - From 750ac4886592330f7eef581fc96c15a4f53a47a9 Mon Sep 17 00:00:00 2001 From: Sam Hariri <137707942+samh-nl@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:14:57 +0200 Subject: [PATCH 004/156] feat: support accountid attribute in user mentions --- .../HTMLRenderers/MentionUserRenderer.js | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js index 7f7753dfea44..992f8494dc1f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js @@ -1,6 +1,8 @@ import React from 'react'; import _ from 'underscore'; import {TNodeChildrenRenderer} from 'react-native-render-html'; +import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import Text from '../../Text'; @@ -10,52 +12,63 @@ import withCurrentUserPersonalDetails from '../../withCurrentUserPersonalDetails import personalDetailsPropType from '../../../pages/personalDetailsPropType'; import * as StyleUtils from '../../../styles/StyleUtils'; import * as PersonalDetailsUtils from '../../../libs/PersonalDetailsUtils'; +import compose from '../../../libs/compose'; import TextLink from '../../TextLink'; +import ONYXKEYS from '../../../ONYXKEYS'; +import useLocalize from '../../../hooks/useLocalize'; const propTypes = { ...htmlRendererPropTypes, - /** - * Current user personal details - */ + /** Current user personal details */ currentUserPersonalDetails: personalDetailsPropType.isRequired, -}; -/** - * Navigates to user details screen based on email - * @param {String} email - * @returns {void} - * */ -const showUserDetails = (email) => Navigation.navigate(ROUTES.getDetailsRoute(email)); + /** Personal details of all users */ + personalDetails: personalDetailsPropType.isRequired, +}; function MentionUserRenderer(props) { + const {translate} = useLocalize(); const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style']); + const htmlAttribAccountID = lodashGet(props.tnode.attributes, 'accountid'); + + let accountID; + let displayNameOrLogin; + let navigationRoute; - // We need to remove the leading @ from data as it is not part of the login - const loginWithoutLeadingAt = props.tnode.data ? props.tnode.data.slice(1) : ''; + if (!_.isEmpty(htmlAttribAccountID)) { + const user = lodashGet(props.personalDetails, htmlAttribAccountID); + accountID = parseInt(htmlAttribAccountID, 10); + displayNameOrLogin = lodashGet(user, 'login', '') || lodashGet(user, 'displayName', '') || translate('common.hidden'); + navigationRoute = ROUTES.getProfileRoute(htmlAttribAccountID); + } else { + // We need to remove the leading @ from data as it is not part of the login + displayNameOrLogin = props.tnode.data ? props.tnode.data.slice(1) : ''; - const accountID = _.first(PersonalDetailsUtils.getAccountIDsByLogins([loginWithoutLeadingAt])); + accountID = _.first(PersonalDetailsUtils.getAccountIDsByLogins([displayNameOrLogin])); + navigationRoute = ROUTES.getDetailsRoute(displayNameOrLogin); + } - const isOurMention = loginWithoutLeadingAt === props.currentUserPersonalDetails.login; + const isOurMention = accountID === props.currentUserPersonalDetails.accountID; return ( showUserDetails(loginWithoutLeadingAt)} + onPress={() => Navigation.navigate(navigationRoute)} // Add testID so it is NOT selected as an anchor tag by SelectionScraper testID="span" > - + {!_.isEmpty(htmlAttribAccountID) ? `@${displayNameOrLogin}` : } @@ -65,4 +78,11 @@ function MentionUserRenderer(props) { MentionUserRenderer.propTypes = propTypes; MentionUserRenderer.displayName = 'MentionUserRenderer'; -export default withCurrentUserPersonalDetails(MentionUserRenderer); +export default compose( + withCurrentUserPersonalDetails, + withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + }), +)(MentionUserRenderer); From d1b15ea7a3e80ba4130efc936d468be1d70d622b Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Tue, 5 Sep 2023 15:31:24 -0700 Subject: [PATCH 005/156] Pass payer account id --- src/libs/actions/IOU.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 780222d3060c..8b6175625cda 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -480,6 +480,7 @@ function getMoneyRequestInformation( ); return { + payerAccountID, payerEmail, iouReport, chatReport, @@ -554,7 +555,7 @@ function createDistanceRequest(report, participant, comment, created, transactio * @param {Object} [receipt] */ function requestMoney(report, amount, currency, created, merchant, payeeEmail, payeeAccountID, participant, comment, receipt = undefined) { - const {payerEmail, iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( + const {payerAccountID, payerEmail, iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( report, participant, comment, From a55d51fb04d474a4c8bf4c4ac7efedc3a4677d4b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 6 Sep 2023 13:25:23 +0800 Subject: [PATCH 006/156] fix send button is enabled with empty comment --- .../report/ReportActionCompose/ReportActionCompose.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index c7517977aa27..a0c72cf5896f 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -36,6 +36,7 @@ import useLocalize from '../../../../hooks/useLocalize'; import getModalState from '../../../../libs/getModalState'; import useWindowDimensions from '../../../../hooks/useWindowDimensions'; import * as EmojiPickerActions from '../../../../libs/actions/EmojiPickerAction'; +import getDraftComment from '../../../../libs/ComposerUtils/getDraftComment'; const propTypes = { /** A method to call when the form is submitted */ @@ -105,7 +106,6 @@ function ReportActionCompose({ reportID, reportActions, shouldShowComposeInput, - isCommentEmpty: isCommentEmptyProp, }) { const {translate} = useLocalize(); const {isMediumScreenWidth, isSmallScreenWidth} = useWindowDimensions(); @@ -125,7 +125,10 @@ function ReportActionCompose({ * Updates the should clear state of the composer */ const [textInputShouldClear, setTextInputShouldClear] = useState(false); - const [isCommentEmpty, setIsCommentEmpty] = useState(isCommentEmptyProp); + const [isCommentEmpty, setIsCommentEmpty] = useState(() => { + const draftComment = getDraftComment(reportID); + return !draftComment || !!draftComment.match(/^(\s)*$/); + }); /** * Updates the visibility state of the menu @@ -431,10 +434,6 @@ export default compose( withNetwork(), withCurrentUserPersonalDetails, withOnyx({ - isCommentEmpty: { - key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, - selector: (comment) => _.isEmpty(comment), - }, blockedFromConcierge: { key: ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE, }, From 19faf7deb3ed7043108a3ad7162fcbd90bb45901 Mon Sep 17 00:00:00 2001 From: Sam Hariri <137707942+samh-nl@users.noreply.github.com> Date: Wed, 6 Sep 2023 12:52:57 +0200 Subject: [PATCH 007/156] feat: remove navigation and tooltip when hidden --- .../HTMLRenderers/MentionUserRenderer.js | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js index 992f8494dc1f..087334061fa2 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js @@ -10,6 +10,7 @@ import UserDetailsTooltip from '../../UserDetailsTooltip'; import htmlRendererPropTypes from './htmlRendererPropTypes'; import withCurrentUserPersonalDetails from '../../withCurrentUserPersonalDetails'; import personalDetailsPropType from '../../../pages/personalDetailsPropType'; +import styles from '../../../styles/styles'; import * as StyleUtils from '../../../styles/StyleUtils'; import * as PersonalDetailsUtils from '../../../libs/PersonalDetailsUtils'; import compose from '../../../libs/compose'; @@ -39,8 +40,13 @@ function MentionUserRenderer(props) { if (!_.isEmpty(htmlAttribAccountID)) { const user = lodashGet(props.personalDetails, htmlAttribAccountID); accountID = parseInt(htmlAttribAccountID, 10); - displayNameOrLogin = lodashGet(user, 'login', '') || lodashGet(user, 'displayName', '') || translate('common.hidden'); - navigationRoute = ROUTES.getProfileRoute(htmlAttribAccountID); + displayNameOrLogin = lodashGet(user, 'login', '') || lodashGet(user, 'displayName', ''); + + if (_.isEmpty(displayNameOrLogin)) { + displayNameOrLogin = translate('common.hidden'); + } else { + navigationRoute = ROUTES.getProfileRoute(htmlAttribAccountID); + } } else { // We need to remove the leading @ from data as it is not part of the login displayNameOrLogin = props.tnode.data ? props.tnode.data.slice(1) : ''; @@ -50,26 +56,33 @@ function MentionUserRenderer(props) { } const isOurMention = accountID === props.currentUserPersonalDetails.accountID; + const TextLinkComponent = _.isEmpty(navigationRoute) ? Text : TextLink; return ( - Navigation.navigate(navigationRoute)} + style={[ + _.omit(props.style, 'color'), + StyleUtils.getMentionStyle(isOurMention), + {color: StyleUtils.getMentionTextColor(isOurMention)}, + _.isEmpty(navigationRoute) && styles.cursorDefault, + ]} + href={_.isEmpty(navigationRoute) ? undefined : `/${navigationRoute}`} + onPress={_.isEmpty(navigationRoute) ? undefined : () => Navigation.navigate(navigationRoute)} // Add testID so it is NOT selected as an anchor tag by SelectionScraper testID="span" > {!_.isEmpty(htmlAttribAccountID) ? `@${displayNameOrLogin}` : } - + ); From 2af817dfd68a3d0b34b50a52de8c34edb58ba515 Mon Sep 17 00:00:00 2001 From: Sam Hariri <137707942+samh-nl@users.noreply.github.com> Date: Wed, 6 Sep 2023 14:28:07 +0200 Subject: [PATCH 008/156] fix: ensure proptype is adhered to --- .../HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js index 087334061fa2..155596fb23b0 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js @@ -74,7 +74,7 @@ function MentionUserRenderer(props) { _.omit(props.style, 'color'), StyleUtils.getMentionStyle(isOurMention), {color: StyleUtils.getMentionTextColor(isOurMention)}, - _.isEmpty(navigationRoute) && styles.cursorDefault, + _.isEmpty(navigationRoute) ? styles.cursorDefault : {}, ]} href={_.isEmpty(navigationRoute) ? undefined : `/${navigationRoute}`} onPress={_.isEmpty(navigationRoute) ? undefined : () => Navigation.navigate(navigationRoute)} From 118ec38f2cfc2d406a0a5ead0339bae50c4aa335 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 6 Sep 2023 21:00:18 +0200 Subject: [PATCH 009/156] ref: inital migration to TS --- .../{Transaction.js => Transaction.ts} | 50 ++++++++----------- src/types/onyx/Transaction.ts | 5 ++ 2 files changed, 26 insertions(+), 29 deletions(-) rename src/libs/actions/{Transaction.js => Transaction.ts} (81%) diff --git a/src/libs/actions/Transaction.js b/src/libs/actions/Transaction.ts similarity index 81% rename from src/libs/actions/Transaction.js rename to src/libs/actions/Transaction.ts index b16e8c8753ac..f58db4915e77 100644 --- a/src/libs/actions/Transaction.js +++ b/src/libs/actions/Transaction.ts @@ -5,14 +5,15 @@ import lodashHas from 'lodash/has'; import ONYXKEYS from '../../ONYXKEYS'; import * as CollectionUtils from '../CollectionUtils'; import * as API from '../API'; +import {RecentWaypoints, Transaction} from '../../types/onyx'; -let recentWaypoints = []; +let recentWaypoints: RecentWaypoints[] = []; Onyx.connect({ key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val || []), + callback: (val) => (recentWaypoints = val ?? []), }); -const allTransactions = {}; +const allTransactions: Record = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, callback: (transaction, key) => { @@ -24,10 +25,7 @@ Onyx.connect({ }, }); -/** - * @param {String} transactionID - */ -function createInitialWaypoints(transactionID) { +function createInitialWaypoints(transactionID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { comment: { waypoints: { @@ -40,14 +38,11 @@ function createInitialWaypoints(transactionID) { /** * Add a stop to the transaction - * - * @param {String} transactionID - * @param {Number} newLastIndex */ -function addStop(transactionID) { - const transaction = lodashGet(allTransactions, transactionID, {}); - const existingWaypoints = lodashGet(transaction, 'comment.waypoints', {}); - const newLastIndex = _.size(existingWaypoints); +function addStop(transactionID: string) { + const transaction = allTransactions?.[transactionID] ?? {}; + const existingWaypoints = transaction?.comment?.waypoints ?? {}; + const newLastIndex = Object.keys(existingWaypoints).length; Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { comment: { @@ -69,11 +64,8 @@ function addStop(transactionID) { /** * Saves the selected waypoint to the transaction - * @param {String} transactionID - * @param {String} index - * @param {Object} waypoint */ -function saveWaypoint(transactionID, index, waypoint) { +function saveWaypoint(transactionID: string, index: string, waypoint: RecentWaypoints) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { comment: { waypoints: { @@ -82,7 +74,8 @@ function saveWaypoint(transactionID, index, waypoint) { }, // Empty out errors when we're saving a new waypoint as this indicates the user is updating their input errorFields: { - route: null, + // TODO: check if its ok to put undefined + route: undefined, }, // Clear the existing route so that we don't show an old route @@ -102,20 +95,21 @@ function saveWaypoint(transactionID, index, waypoint) { return; } - const recentWaypointAlreadyExists = _.find(recentWaypoints, (recentWaypoint) => recentWaypoint.address === waypoint.address); + const recentWaypointAlreadyExists = recentWaypoints.find((recentWaypoint) => recentWaypoint.address === waypoint.address); if (!recentWaypointAlreadyExists) { + // TODO: Should we leave this? const clonedWaypoints = _.clone(recentWaypoints); clonedWaypoints.unshift(waypoint); Onyx.merge(ONYXKEYS.NVP_RECENT_WAYPOINTS, clonedWaypoints.slice(0, 5)); } } -function removeWaypoint(transactionID, currentIndex) { +function removeWaypoint(transactionID: string, currentIndex: string) { // Index comes from the route params and is a string const index = Number(currentIndex); - const transaction = lodashGet(allTransactions, transactionID, {}); - const existingWaypoints = lodashGet(transaction, 'comment.waypoints', {}); - const totalWaypoints = _.size(existingWaypoints); + const transaction = allTransactions?.[transactionID] ?? {}; + const existingWaypoints = transaction?.comment?.waypoints ?? {}; + const totalWaypoints = Object.keys(existingWaypoints).length; // Prevents removing the starting or ending waypoint but clear the stored address only if there are only two waypoints if (totalWaypoints === 2 && (index === 0 || index === totalWaypoints - 1)) { @@ -123,10 +117,10 @@ function removeWaypoint(transactionID, currentIndex) { return; } - const waypointValues = _.values(existingWaypoints); + const waypointValues = Object.values(existingWaypoints); waypointValues.splice(index, 1); - const reIndexedWaypoints = {}; + const reIndexedWaypoints: Record = {}; waypointValues.forEach((waypoint, idx) => { reIndexedWaypoints[`waypoint${idx}`] = waypoint; }); @@ -155,10 +149,8 @@ function removeWaypoint(transactionID, currentIndex) { /** * Gets the route for a set of waypoints * Used so we can generate a map view of the provided waypoints - * @param {String} transactionID - * @param {Object} waypoints */ -function getRoute(transactionID, waypoints) { +function getRoute(transactionID: string, waypoints: RecentWaypoints) { API.read( 'GetRoute', { diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 9e6cd603472f..2314b6230506 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -1,9 +1,11 @@ import {ValueOf} from 'type-fest'; import * as OnyxCommon from './OnyxCommon'; import CONST from '../../CONST'; +import RecentWaypoints from './RecentWaypoints'; type Comment = { comment?: string; + waypoints?: Record; }; type Transaction = { @@ -25,6 +27,9 @@ type Transaction = { source?: string; state?: ValueOf; }; + // TODO: fix unknown type + routes: Record; + errorFields: OnyxCommon.ErrorFields; }; export default Transaction; From 51b1ab3ec537d0586f22abbb4ba99e3187eb4db6 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 7 Sep 2023 13:16:44 +0200 Subject: [PATCH 010/156] Cleanup input wrapper --- src/hooks/useForm/InputWrapper.js | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/hooks/useForm/InputWrapper.js b/src/hooks/useForm/InputWrapper.js index ad3d4a2f0fc8..009bb762f270 100644 --- a/src/hooks/useForm/InputWrapper.js +++ b/src/hooks/useForm/InputWrapper.js @@ -1,11 +1,32 @@ import React, {forwardRef, useContext} from 'react'; +import PropTypes from 'prop-types'; import FormContext from './FormContext'; -const InputWrapper = forwardRef((props, ref) => { - const {RenderInput, inputID, ...rest} = props; +const propTypes = { + RenderInput: PropTypes.node.isRequired, + inputID: PropTypes.string.isRequired, + forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), +}; + +const defaultProps = { + forwardedRef: undefined, +}; + +function InputWrapper(props) { + const {RenderInput, inputID, forwardedRef, ...rest} = props; const {registerInput} = useContext(FormContext); // eslint-disable-next-line react/jsx-props-no-spreading - return ; -}); + return ; +} + +InputWrapper.propTypes = propTypes; +InputWrapper.defaultProps = defaultProps; +InputWrapper.displayName = 'InputWrapper'; -export default InputWrapper; +export default forwardRef((props, ref) => ( + +)); From 620b48c6975494e7c3a350acd325e83bbb70f1f6 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 7 Sep 2023 13:17:50 +0200 Subject: [PATCH 011/156] Remove redundant testing component --- src/components/DummyComponentWithStateHook.js | 16 ---------------- src/pages/settings/Profile/DisplayNamePage.js | 2 -- 2 files changed, 18 deletions(-) delete mode 100644 src/components/DummyComponentWithStateHook.js diff --git a/src/components/DummyComponentWithStateHook.js b/src/components/DummyComponentWithStateHook.js deleted file mode 100644 index 1ed2f8dab43b..000000000000 --- a/src/components/DummyComponentWithStateHook.js +++ /dev/null @@ -1,16 +0,0 @@ -import {Text} from 'react-native'; -import React, {useState} from 'react'; - -const propTypes = {}; -const defaultProps = {}; - -function DummyComponentWithStateHook() { - const [state] = useState(''); - return {state}; -} - -DummyComponentWithStateHook.propTypes = propTypes; -DummyComponentWithStateHook.defaultProps = defaultProps; -DummyComponentWithStateHook.displayName = 'DummyComponentWithStateHook'; - -export default DummyComponentWithStateHook; diff --git a/src/pages/settings/Profile/DisplayNamePage.js b/src/pages/settings/Profile/DisplayNamePage.js index d13920881034..4eb9e44f30ae 100644 --- a/src/pages/settings/Profile/DisplayNamePage.js +++ b/src/pages/settings/Profile/DisplayNamePage.js @@ -12,7 +12,6 @@ import TextInput from '../../../components/TextInput'; import Text from '../../../components/Text'; import styles from '../../../styles/styles'; import * as PersonalDetails from '../../../libs/actions/PersonalDetails'; -import DummyComponentWithStateHook from '../../../components/DummyComponentWithStateHook'; import compose from '../../../libs/compose'; import * as ErrorUtils from '../../../libs/ErrorUtils'; import ROUTES from '../../../ROUTES'; @@ -87,7 +86,6 @@ function DisplayNamePage(props) { shouldValidateOnBlur shouldValidateOnChange > - {props.translate('displayNamePage.isShownOnProfile')} Date: Thu, 7 Sep 2023 14:55:10 +0200 Subject: [PATCH 012/156] Cleanup form wrapper --- src/hooks/useForm/FormWrapper.js | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/hooks/useForm/FormWrapper.js b/src/hooks/useForm/FormWrapper.js index 221eb2b152f0..9ec4492f97aa 100644 --- a/src/hooks/useForm/FormWrapper.js +++ b/src/hooks/useForm/FormWrapper.js @@ -11,10 +11,8 @@ import SafeAreaConsumer from '../../components/SafeAreaConsumer'; import ScrollViewWithContext from '../../components/ScrollViewWithContext'; import stylePropTypes from '../../styles/stylePropTypes'; -import networkPropTypes from '../../components/networkPropTypes'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import compose from '../../libs/compose'; -import {withNetwork} from '../../components/OnyxProvider'; const propTypes = { /** A unique Onyx key identifying the form */ @@ -26,9 +24,6 @@ const propTypes = { /** Controls the submit button's visibility */ isSubmitButtonVisible: PropTypes.bool, - /** Callback to validate the form */ - validate: PropTypes.func, - /** Callback to submit the form */ onSubmit: PropTypes.func.isRequired, @@ -66,9 +61,6 @@ const propTypes = { /** Custom content to display in the footer after submit button */ footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), - /** Information about the network */ - network: networkPropTypes.isRequired, - ...withLocalizePropTypes, }; @@ -82,7 +74,6 @@ const defaultProps = { scrollContextEnabled: false, footerContent: null, style: [], - validate: () => ({}), }; function FormWrapper(props) { @@ -91,7 +82,7 @@ function FormWrapper(props) { const {onSubmit, children, formState, errors, inputRefs, submitButtonText, footerContent, isSubmitButtonVisible, style, enabledWhenOffline, isSubmitActionDangerous, formID} = props; const errorMessage = useMemo(() => { - const latestErrorMessage = ErrorUtils.getLatestErrorMessage(props.formState); + const latestErrorMessage = ErrorUtils.getLatestErrorMessage(formState); return typeof latestErrorMessage === 'string' ? latestErrorMessage : ''; }, [formState]); @@ -148,19 +139,20 @@ function FormWrapper(props) { ), [ - style, - onSubmit, children, - isSubmitButtonVisible, - submitButtonText, - errors, + enabledWhenOffline, errorMessage, + errors, + footerContent, + formID, formState.errorFields, formState.isLoading, - footerContent, - enabledWhenOffline, - isSubmitActionDangerous, inputRefs, + isSubmitActionDangerous, + isSubmitButtonVisible, + onSubmit, + style, + submitButtonText, ], ); @@ -197,7 +189,6 @@ FormWrapper.defaultProps = defaultProps; export default compose( withLocalize, - withNetwork(), withOnyx({ formState: { key: (props) => props.formID, From 59652ce37082ee122e8cc3de704e922474b6aa56 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 7 Sep 2023 16:14:59 +0200 Subject: [PATCH 013/156] Cleanup DisplayNamePage --- src/pages/settings/Profile/DisplayNamePage.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/settings/Profile/DisplayNamePage.js b/src/pages/settings/Profile/DisplayNamePage.js index 8f031d87e162..ec8f96cd210e 100644 --- a/src/pages/settings/Profile/DisplayNamePage.js +++ b/src/pages/settings/Profile/DisplayNamePage.js @@ -47,8 +47,7 @@ function DisplayNamePage(props) { * @returns {Object} - An object containing the errors for each inputID */ const validate = (values) => { - const requiredFields = ['firstName']; - const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); + const errors = {}; // First we validate the first name field if (!ValidationUtils.isValidDisplayName(values.firstName)) { @@ -92,7 +91,6 @@ function DisplayNamePage(props) { RenderInput={TextInput} inputID="firstName" name="fname" - key="iuadgbkj" label={props.translate('common.firstName')} accessibilityLabel={props.translate('common.firstName')} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} From 0491bc3e3b92797ea2a65df650c158d60274d3e4 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 8 Sep 2023 13:46:38 +0200 Subject: [PATCH 014/156] Cleanup FormProvider --- src/hooks/useForm/FormProvider.js | 33 +++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/hooks/useForm/FormProvider.js b/src/hooks/useForm/FormProvider.js index 0961dce91df6..03095d8f74f5 100644 --- a/src/hooks/useForm/FormProvider.js +++ b/src/hooks/useForm/FormProvider.js @@ -1,4 +1,4 @@ -import React, {createRef, useCallback, useRef, useState} from 'react'; +import React, {createRef, useCallback, useMemo, useRef, useState} from 'react'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; @@ -11,6 +11,8 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize import {withNetwork} from '../../components/OnyxProvider'; import stylePropTypes from '../../styles/stylePropTypes'; import networkPropTypes from '../../components/networkPropTypes'; +import * as ErrorUtils from '../../libs/ErrorUtils'; +import lodashGet from 'lodash/get'; const propTypes = { /** A unique Onyx key identifying the form */ @@ -81,6 +83,19 @@ const defaultProps = { validate: () => ({}), }; +function getInitialValueByType(valueType) { + switch (valueType) { + case 'string': + return ''; + case 'boolean': + return false; + case 'date': + return new Date(); + default: + return ''; + } +} + function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, children, formState, network, enabledWhenOffline, onSubmit, ...rest}) { const inputRefs = useRef({}); const touchedInputs = useRef({}); @@ -134,7 +149,7 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c // We want to initialize the input value if it's undefined if (_.isUndefined(inputValues[inputID])) { - inputValues[inputID] = propsToParse.defaultValue || ''; + inputValues[inputID] = propsToParse.defaultValue || getInitialValueByType(propsToParse.valueType); } // We force the form to set the input value from the defaultValue props if there is a saved valid value @@ -146,12 +161,22 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c inputValues[inputID] = propsToParse.value; } + const errorFields = lodashGet(formState, 'errorFields', {}); + const fieldErrorMessage = + _.chain(errorFields[inputID]) + .keys() + .sortBy() + .reverse() + .map((key) => errorFields[inputID][key]) + .first() + .value() || ''; + return { ...propsToParse, ref: newRef, inputID, key: propsToParse.key || inputID, - errorText: errors[inputID], + errorText: errors[inputID] || fieldErrorMessage, value: inputValues[inputID], // As the text input is controlled, we never set the defaultValue prop // as this is already happening by the value prop. @@ -204,7 +229,7 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c }, }; }, - [errors, inputValues, onValidate, setTouchedInput, shouldValidateOnBlur, shouldValidateOnChange], + [errors, formState, inputValues, onValidate, setTouchedInput, shouldValidateOnBlur, shouldValidateOnChange], ); return ( From 74f69a552ca2c288f60e1ab3076eb0bd8b0f97e2 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 8 Sep 2023 17:12:03 +0200 Subject: [PATCH 015/156] Fix lint --- src/hooks/useForm/FormProvider.js | 3 +-- src/hooks/useForm/index.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/hooks/useForm/FormProvider.js b/src/hooks/useForm/FormProvider.js index 03095d8f74f5..e28c8543f6ab 100644 --- a/src/hooks/useForm/FormProvider.js +++ b/src/hooks/useForm/FormProvider.js @@ -1,4 +1,4 @@ -import React, {createRef, useCallback, useMemo, useRef, useState} from 'react'; +import React, {createRef, useCallback, useRef, useState} from 'react'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; @@ -11,7 +11,6 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize import {withNetwork} from '../../components/OnyxProvider'; import stylePropTypes from '../../styles/stylePropTypes'; import networkPropTypes from '../../components/networkPropTypes'; -import * as ErrorUtils from '../../libs/ErrorUtils'; import lodashGet from 'lodash/get'; const propTypes = { diff --git a/src/hooks/useForm/index.js b/src/hooks/useForm/index.js index 5e3a2b480172..faf102e5fd16 100644 --- a/src/hooks/useForm/index.js +++ b/src/hooks/useForm/index.js @@ -1,4 +1,4 @@ -import React, {useContext} from 'react'; +import {useContext} from 'react'; import InputWrapper from './InputWrapper'; import FormContext from './FormContext'; import FormProvider from './FormProvider'; From b74d73f18ceec1db82a733d669101a2f440d2e3b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 11 Sep 2023 12:58:02 +0200 Subject: [PATCH 016/156] ref: small refactors --- src/libs/actions/Transaction.ts | 10 ++++------ src/types/onyx/RecentWaypoints.ts | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index f58db4915e77..2d250cfc3902 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -1,7 +1,6 @@ -import _ from 'underscore'; import Onyx from 'react-native-onyx'; -import lodashGet from 'lodash/get'; import lodashHas from 'lodash/has'; +import lodashClone from 'lodash/clone'; import ONYXKEYS from '../../ONYXKEYS'; import * as CollectionUtils from '../CollectionUtils'; import * as API from '../API'; @@ -95,10 +94,9 @@ function saveWaypoint(transactionID: string, index: string, waypoint: RecentWayp return; } - const recentWaypointAlreadyExists = recentWaypoints.find((recentWaypoint) => recentWaypoint.address === waypoint.address); + const recentWaypointAlreadyExists = recentWaypoints.find((recentWaypoint) => recentWaypoint?.address === waypoint?.address); if (!recentWaypointAlreadyExists) { - // TODO: Should we leave this? - const clonedWaypoints = _.clone(recentWaypoints); + const clonedWaypoints = lodashClone(recentWaypoints); clonedWaypoints.unshift(waypoint); Onyx.merge(ONYXKEYS.NVP_RECENT_WAYPOINTS, clonedWaypoints.slice(0, 5)); } @@ -113,7 +111,7 @@ function removeWaypoint(transactionID: string, currentIndex: string) { // Prevents removing the starting or ending waypoint but clear the stored address only if there are only two waypoints if (totalWaypoints === 2 && (index === 0 || index === totalWaypoints - 1)) { - saveWaypoint(transactionID, index, null); + saveWaypoint(transactionID, index.toString(), null); return; } diff --git a/src/types/onyx/RecentWaypoints.ts b/src/types/onyx/RecentWaypoints.ts index 75780ef861e5..2b33997982c5 100644 --- a/src/types/onyx/RecentWaypoints.ts +++ b/src/types/onyx/RecentWaypoints.ts @@ -7,6 +7,6 @@ type RecentWaypoints = { /** The longitude of the waypoint */ lng: number; -}; +} | null; export default RecentWaypoints; From b6c8a6212582cad7d9df5f1eb1b1666dda40820c Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Mon, 11 Sep 2023 15:39:54 +0200 Subject: [PATCH 017/156] Cleanup --- src/hooks/useForm/FormProvider.js | 2 +- src/hooks/useForm/InputWrapper.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/useForm/FormProvider.js b/src/hooks/useForm/FormProvider.js index e28c8543f6ab..c29597f48028 100644 --- a/src/hooks/useForm/FormProvider.js +++ b/src/hooks/useForm/FormProvider.js @@ -2,6 +2,7 @@ import React, {createRef, useCallback, useRef, useState} from 'react'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; import Visibility from '../../libs/Visibility'; import * as FormActions from '../../libs/actions/FormActions'; import FormContext from './FormContext'; @@ -11,7 +12,6 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize import {withNetwork} from '../../components/OnyxProvider'; import stylePropTypes from '../../styles/stylePropTypes'; import networkPropTypes from '../../components/networkPropTypes'; -import lodashGet from 'lodash/get'; const propTypes = { /** A unique Onyx key identifying the form */ diff --git a/src/hooks/useForm/InputWrapper.js b/src/hooks/useForm/InputWrapper.js index 009bb762f270..31dc3edb02e8 100644 --- a/src/hooks/useForm/InputWrapper.js +++ b/src/hooks/useForm/InputWrapper.js @@ -5,11 +5,13 @@ import FormContext from './FormContext'; const propTypes = { RenderInput: PropTypes.node.isRequired, inputID: PropTypes.string.isRequired, + valueType: PropTypes.string, forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), }; const defaultProps = { forwardedRef: undefined, + valueType: 'string', }; function InputWrapper(props) { From 0b9ca6ddc4513dba1f49223dc960fba9077810e8 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 11 Sep 2023 15:59:22 +0200 Subject: [PATCH 018/156] fix: added a errorFields field into Transaction type --- src/types/onyx/Transaction.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 6011120d2d45..398f808842b0 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -31,6 +31,7 @@ type Transaction = { created: string; pendingAction: OnyxCommon.PendingAction; errors: OnyxCommon.Errors; + errorFields: OnyxCommon.ErrorFields; modifiedAmount?: number; modifiedCreated?: string; modifiedCurrency?: string; From 1b995a9495829526209d2163e9b8a015fe876466 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 11 Sep 2023 16:41:59 +0200 Subject: [PATCH 019/156] fix: added null to coordinates type --- src/types/onyx/Transaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 398f808842b0..85fe51e58b4f 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -9,7 +9,7 @@ type Comment = { }; type Geometry = { - coordinates: number[][]; + coordinates: number[][] | null; type: 'LineString'; }; From 61009460ef6fbea15fea9462f9b74e8afd406397 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 11 Sep 2023 17:35:49 +0200 Subject: [PATCH 020/156] fix: added needed fields into newTransaction object --- src/libs/actions/Transaction.ts | 4 +++- src/types/onyx/Transaction.ts | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 2d250cfc3902..6d2387143e99 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -126,7 +126,7 @@ function removeWaypoint(transactionID: string, currentIndex: string) { // Onyx.merge won't remove the null nested object values, this is a workaround // to remove nested keys while also preserving other object keys // Doing a deep clone of the transaction to avoid mutating the original object and running into a cache issue when using Onyx.set - const newTransaction = { + const newTransaction: Transaction = { ...transaction, comment: { ...transaction.comment, @@ -135,8 +135,10 @@ function removeWaypoint(transactionID: string, currentIndex: string) { // Clear the existing route so that we don't show an old route routes: { route0: { + distance: null, geometry: { coordinates: null, + type: '', }, }, }, diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 85fe51e58b4f..3a7bf3ac2eb7 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -8,13 +8,15 @@ type Comment = { waypoints?: Record; }; +type GeometryType = 'LineString' | ''; + type Geometry = { coordinates: number[][] | null; - type: 'LineString'; + type: GeometryType; }; type Route = { - distance: number; + distance: number | null; geometry: Geometry; }; From 54f479b4b42993379797b7d411eee6ba6021e1d8 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 12 Sep 2023 10:09:29 +0200 Subject: [PATCH 021/156] fix: resolved review comments --- src/libs/actions/Transaction.ts | 5 ++--- src/types/onyx/OnyxCommon.ts | 2 +- src/types/onyx/Transaction.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 6d2387143e99..1e4d85bc126e 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -73,8 +73,7 @@ function saveWaypoint(transactionID: string, index: string, waypoint: RecentWayp }, // Empty out errors when we're saving a new waypoint as this indicates the user is updating their input errorFields: { - // TODO: check if its ok to put undefined - route: undefined, + route: null, }, // Clear the existing route so that we don't show an old route @@ -150,7 +149,7 @@ function removeWaypoint(transactionID: string, currentIndex: string) { * Gets the route for a set of waypoints * Used so we can generate a map view of the provided waypoints */ -function getRoute(transactionID: string, waypoints: RecentWaypoints) { +function getRoute(transactionID: string, waypoints: Record) { API.read( 'GetRoute', { diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index 36e9c6ae74ea..db82edcb98ff 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -4,7 +4,7 @@ import CONST from '../../CONST'; type PendingAction = ValueOf; -type ErrorFields = Record>; +type ErrorFields = Record | null>; type Errors = Record; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 3a7bf3ac2eb7..8e1449906cbf 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -33,7 +33,7 @@ type Transaction = { created: string; pendingAction: OnyxCommon.PendingAction; errors: OnyxCommon.Errors; - errorFields: OnyxCommon.ErrorFields; + errorFields?: OnyxCommon.ErrorFields; modifiedAmount?: number; modifiedCreated?: string; modifiedCurrency?: string; From f7c72f115740a969a10d030b0d98b051892a28f4 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 12 Sep 2023 16:11:42 +0800 Subject: [PATCH 022/156] add three dot menu to confirmation page --- src/pages/iou/steps/MoneyRequestConfirmPage.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 8db5140d1092..3d1a10adc967 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -26,6 +26,7 @@ import * as Policy from '../../../libs/actions/Policy'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; import * as StyleUtils from '../../../styles/StyleUtils'; import {iouPropTypes, iouDefaultProps} from '../propTypes'; +import * as Expensicons from '../../../components/Icon/Expensicons'; const propTypes = { /** React Navigation route */ @@ -254,6 +255,16 @@ function MoneyRequestConfirmPage(props) { {}, + }, { + icon: Expensicons.Gallery, + text: props.translate('attachmentPicker.chooseFromGallery'), + onSelected: () => {}, + }]} /> {/* * The MoneyRequestConfirmationList component uses a SectionList which uses a VirtualizedList internally. From 5ca20acb2961b43705f6b677425d65dc99234328 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 12 Sep 2023 16:24:40 +0800 Subject: [PATCH 023/156] add attachment picker support --- .../iou/steps/MoneyRequestConfirmPage.js | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 3d1a10adc967..e82771b9bc31 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -27,6 +27,7 @@ import useWindowDimensions from '../../../hooks/useWindowDimensions'; import * as StyleUtils from '../../../styles/StyleUtils'; import {iouPropTypes, iouDefaultProps} from '../propTypes'; import * as Expensicons from '../../../components/Icon/Expensicons'; +import AttachmentPicker from '../../../components/AttachmentPicker'; const propTypes = { /** React Navigation route */ @@ -252,20 +253,26 @@ function MoneyRequestConfirmPage(props) { {({safeAreaPaddingBottomStyle}) => ( - {}, - }, { - icon: Expensicons.Gallery, - text: props.translate('attachmentPicker.chooseFromGallery'), - onSelected: () => {}, - }]} - /> + + {({openPicker}) => ( + {}, + }, { + icon: Expensicons.Gallery, + text: props.translate('attachmentPicker.chooseFromGallery'), + onSelected: () => openPicker({ + onPicked: (file) => {}, + }), + }]} + /> + )} + {/* * The MoneyRequestConfirmationList component uses a SectionList which uses a VirtualizedList internally. * VirtualizedList cannot be directly nested within ScrollViews of the same orientation. From 513c5fc3ac3ee5f3fe4ecbde29f4a32bb19b28c7 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 12 Sep 2023 16:34:23 +0800 Subject: [PATCH 024/156] fix dropdown position anchor --- src/pages/iou/steps/MoneyRequestConfirmPage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index e82771b9bc31..1383bff1214d 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -61,7 +61,7 @@ const defaultProps = { }; function MoneyRequestConfirmPage(props) { - const {windowHeight} = useWindowDimensions(); + const {windowHeight, windowWidth} = useWindowDimensions(); const prevMoneyRequestId = useRef(props.iou.id); const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType.current, props.selectedTab); @@ -259,6 +259,7 @@ function MoneyRequestConfirmPage(props) { title={isDistanceRequest ? props.translate('common.distance') : props.translate('iou.cash')} onBackButtonPress={navigateBack} shouldShowThreeDotsButton={!isDistanceRequest} + threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} threeDotsMenuItems={[{ icon: Expensicons.Camera, text: props.translate('receipt.takePhoto'), From 37d9bc27dfab5a0641dd319c4904f728bd46f5ca Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 12 Sep 2023 16:48:01 +0800 Subject: [PATCH 025/156] add logic to select receipt file --- src/pages/iou/steps/MoneyRequestConfirmPage.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 1383bff1214d..6a1898aa358f 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -28,6 +28,7 @@ import * as StyleUtils from '../../../styles/StyleUtils'; import {iouPropTypes, iouDefaultProps} from '../propTypes'; import * as Expensicons from '../../../components/Icon/Expensicons'; import AttachmentPicker from '../../../components/AttachmentPicker'; +import * as ReceiptUtils from '../../../libs/ReceiptUtils'; const propTypes = { /** React Navigation route */ @@ -268,7 +269,14 @@ function MoneyRequestConfirmPage(props) { icon: Expensicons.Gallery, text: props.translate('attachmentPicker.chooseFromGallery'), onSelected: () => openPicker({ - onPicked: (file) => {}, + onPicked: (file) => { + if (!ReceiptUtils.validateReceipt(file)) { + return; + } + + const filePath = URL.createObjectURL(file); + IOU.setMoneyRequestReceipt(filePath, file.name); + }, }), }]} /> From 05a4da1fe106987c5a4bfa5af9641cc5b3def57a Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 12 Sep 2023 17:43:36 +0800 Subject: [PATCH 026/156] use only add receipt option --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/pages/iou/steps/MoneyRequestConfirmPage.js | 18 ++---------------- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index bd518f1fdda9..e13d0461fd4b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -481,6 +481,7 @@ export default { flash: 'flash', shutter: 'shutter', gallery: 'gallery', + addReceipt: 'Add receipt', }, iou: { amount: 'Amount', diff --git a/src/languages/es.ts b/src/languages/es.ts index 3c5d98b724d2..a4b131d8f74d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -482,6 +482,7 @@ export default { flash: 'flash', shutter: 'obturador', gallery: 'galería', + addReceipt: 'Añadir recibo', }, iou: { amount: 'Importe', diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 6a1898aa358f..038e22417472 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -28,7 +28,6 @@ import * as StyleUtils from '../../../styles/StyleUtils'; import {iouPropTypes, iouDefaultProps} from '../propTypes'; import * as Expensicons from '../../../components/Icon/Expensicons'; import AttachmentPicker from '../../../components/AttachmentPicker'; -import * as ReceiptUtils from '../../../libs/ReceiptUtils'; const propTypes = { /** React Navigation route */ @@ -262,22 +261,9 @@ function MoneyRequestConfirmPage(props) { shouldShowThreeDotsButton={!isDistanceRequest} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} threeDotsMenuItems={[{ - icon: Expensicons.Camera, - text: props.translate('receipt.takePhoto'), + icon: Expensicons.Receipt, + text: props.translate('receipt.addReceipt'), onSelected: () => {}, - }, { - icon: Expensicons.Gallery, - text: props.translate('attachmentPicker.chooseFromGallery'), - onSelected: () => openPicker({ - onPicked: (file) => { - if (!ReceiptUtils.validateReceipt(file)) { - return; - } - - const filePath = URL.createObjectURL(file); - IOU.setMoneyRequestReceipt(filePath, file.name); - }, - }), }]} /> )} From 287566678976c197418c13c774552150dcf06fb7 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 12 Sep 2023 17:50:02 +0800 Subject: [PATCH 027/156] create edit receipt route, navigator --- src/ROUTES.ts | 2 ++ src/libs/Navigation/AppNavigator/ModalStackNavigators.js | 7 +++++++ src/libs/Navigation/linkingConfig.js | 1 + src/pages/iou/steps/MoneyRequestConfirmPage.js | 2 +- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ed4fbb97a41a..c65327aca285 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -103,6 +103,7 @@ export default { MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', MONEY_REQUEST_DISTANCE_TAB: ':iouType/new/:reportID?/distance', MONEY_REQUEST_WAYPOINT: ':iouType/new/waypoint/:waypointIndex', + MONEY_REQUEST_RECEIPT: ':iouType/new/receipt/:reportID?', IOU_SEND_ADD_BANK_ACCOUNT: `${IOU_SEND}/add-bank-account`, IOU_SEND_ADD_DEBIT_CARD: `${IOU_SEND}/add-debit-card`, IOU_SEND_ENABLE_PAYMENTS: `${IOU_SEND}/enable-payments`, @@ -117,6 +118,7 @@ export default { getMoneyRequestMerchantRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}`, getMoneyRequestDistanceTabRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance`, getMoneyRequestWaypointRoute: (iouType: string, waypointIndex: number) => `${iouType}/new/waypoint/${waypointIndex}`, + getMoneyRequestReceiptRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}`, SPLIT_BILL_DETAILS: `r/:reportID/split/:reportActionID`, getSplitBillDetailsRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}`, getNewTaskRoute: (reportID: string) => `${NEW_TASK}/${reportID}`, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 2adaf0397a2c..eff5de37cee1 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -125,6 +125,13 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator([ }, name: 'Money_Request_Waypoint', }, + { + getComponent: () => { + const EditRequestReceiptPage = require('../../../pages/iou/EditRequestReceiptPage').default; + return EditRequestReceiptPage; + }, + name: 'Money_Request_Receipt', + }, ]); const SplitDetailsModalStackNavigator = createModalStackNavigator([ diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index ee3054e02f96..268bf409fff5 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -320,6 +320,7 @@ export default { Money_Request_Category: ROUTES.MONEY_REQUEST_CATEGORY, Money_Request_Merchant: ROUTES.MONEY_REQUEST_MERCHANT, Money_Request_Waypoint: ROUTES.MONEY_REQUEST_WAYPOINT, + Money_Request_Receipt: ROUTES.MONEY_REQUEST_RECEIPT, IOU_Send_Enable_Payments: ROUTES.IOU_SEND_ENABLE_PAYMENTS, IOU_Send_Add_Bank_Account: ROUTES.IOU_SEND_ADD_BANK_ACCOUNT, IOU_Send_Add_Debit_Card: ROUTES.IOU_SEND_ADD_DEBIT_CARD, diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 038e22417472..e11608c94462 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -263,7 +263,7 @@ function MoneyRequestConfirmPage(props) { threeDotsMenuItems={[{ icon: Expensicons.Receipt, text: props.translate('receipt.addReceipt'), - onSelected: () => {}, + onSelected: () => Navigation.navigate(ROUTES.getMoneyRequestReceiptRoute(props.iouType, reportID)), }]} /> )} From 85a12fa4f260eee0d86e3dba590a545d344a70fc Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 12 Sep 2023 12:13:46 +0200 Subject: [PATCH 028/156] fix: rename type to RecentWaypoint --- src/ONYXKEYS.ts | 2 +- src/libs/actions/Transaction.ts | 11 ++++++----- .../onyx/{RecentWaypoints.ts => RecentWaypoint.ts} | 6 +++--- src/types/onyx/Transaction.ts | 6 ++++-- src/types/onyx/index.ts | 4 ++-- 5 files changed, 16 insertions(+), 13 deletions(-) rename src/types/onyx/{RecentWaypoints.ts => RecentWaypoint.ts} (73%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b978f06dbfd9..cacdc9545ca8 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -330,7 +330,7 @@ type OnyxValues = { [ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE]: OnyxTypes.BlockedFromConcierge; [ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID]: string; [ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: Record; - [ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoints[]; + [ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[]; [ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED]: boolean; [ONYXKEYS.PLAID_DATA]: OnyxTypes.PlaidData; [ONYXKEYS.IS_PLAID_DISABLED]: boolean; diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 1e4d85bc126e..47010ba275b3 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -4,9 +4,10 @@ import lodashClone from 'lodash/clone'; import ONYXKEYS from '../../ONYXKEYS'; import * as CollectionUtils from '../CollectionUtils'; import * as API from '../API'; -import {RecentWaypoints, Transaction} from '../../types/onyx'; +import {RecentWaypoint, Transaction} from '../../types/onyx'; +import {WaypointCollection} from '../../types/onyx/Transaction'; -let recentWaypoints: RecentWaypoints[] = []; +let recentWaypoints: RecentWaypoint[] = []; Onyx.connect({ key: ONYXKEYS.NVP_RECENT_WAYPOINTS, callback: (val) => (recentWaypoints = val ?? []), @@ -64,7 +65,7 @@ function addStop(transactionID: string) { /** * Saves the selected waypoint to the transaction */ -function saveWaypoint(transactionID: string, index: string, waypoint: RecentWaypoints) { +function saveWaypoint(transactionID: string, index: string, waypoint: RecentWaypoint | null) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { comment: { waypoints: { @@ -117,7 +118,7 @@ function removeWaypoint(transactionID: string, currentIndex: string) { const waypointValues = Object.values(existingWaypoints); waypointValues.splice(index, 1); - const reIndexedWaypoints: Record = {}; + const reIndexedWaypoints: WaypointCollection = {}; waypointValues.forEach((waypoint, idx) => { reIndexedWaypoints[`waypoint${idx}`] = waypoint; }); @@ -149,7 +150,7 @@ function removeWaypoint(transactionID: string, currentIndex: string) { * Gets the route for a set of waypoints * Used so we can generate a map view of the provided waypoints */ -function getRoute(transactionID: string, waypoints: Record) { +function getRoute(transactionID: string, waypoints: WaypointCollection) { API.read( 'GetRoute', { diff --git a/src/types/onyx/RecentWaypoints.ts b/src/types/onyx/RecentWaypoint.ts similarity index 73% rename from src/types/onyx/RecentWaypoints.ts rename to src/types/onyx/RecentWaypoint.ts index 2b33997982c5..79aded8ede98 100644 --- a/src/types/onyx/RecentWaypoints.ts +++ b/src/types/onyx/RecentWaypoint.ts @@ -1,4 +1,4 @@ -type RecentWaypoints = { +type RecentWaypoint = { /** The full address of the waypoint */ address: string; @@ -7,6 +7,6 @@ type RecentWaypoints = { /** The longitude of the waypoint */ lng: number; -} | null; +}; -export default RecentWaypoints; +export default RecentWaypoint; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 8e1449906cbf..4df5377d309b 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -1,11 +1,12 @@ import {ValueOf} from 'type-fest'; import * as OnyxCommon from './OnyxCommon'; import CONST from '../../CONST'; -import RecentWaypoints from './RecentWaypoints'; +import RecentWaypoint from './RecentWaypoint'; +type WaypointCollection = Record; type Comment = { comment?: string; - waypoints?: Record; + waypoints?: WaypointCollection; }; type GeometryType = 'LineString' | ''; @@ -46,3 +47,4 @@ type Transaction = { }; export default Transaction; +export type {WaypointCollection}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index d908c0b36ce1..97909da5fb47 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -43,7 +43,7 @@ import ReportActionReactions from './ReportActionReactions'; import SecurityGroup from './SecurityGroup'; import Transaction from './Transaction'; import Form, {AddDebitCardForm} from './Form'; -import RecentWaypoints from './RecentWaypoints'; +import RecentWaypoint from './RecentWaypoint'; import RecentlyUsedCategories from './RecentlyUsedCategories'; export type { @@ -93,6 +93,6 @@ export type { Form, AddDebitCardForm, OnyxUpdatesFromServer, - RecentWaypoints, + RecentWaypoint, RecentlyUsedCategories, }; From ef7fb9e0c3a653aaa99c9c235b95dc87adc3d4e4 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 12 Sep 2023 18:14:52 +0800 Subject: [PATCH 029/156] create edit receipt page, navigate to edit page --- .../AppNavigator/ModalStackNavigators.js | 2 +- src/pages/EditRequestReceiptPage.js | 57 +++++++++++++++++++ .../iou/steps/MoneyRequestConfirmPage.js | 2 +- 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/pages/EditRequestReceiptPage.js diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index eff5de37cee1..b9b57b3262eb 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -127,7 +127,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator([ }, { getComponent: () => { - const EditRequestReceiptPage = require('../../../pages/iou/EditRequestReceiptPage').default; + const EditRequestReceiptPage = require('../../../pages/EditRequestReceiptPage').default; return EditRequestReceiptPage; }, name: 'Money_Request_Receipt', diff --git a/src/pages/EditRequestReceiptPage.js b/src/pages/EditRequestReceiptPage.js new file mode 100644 index 000000000000..8c4067713705 --- /dev/null +++ b/src/pages/EditRequestReceiptPage.js @@ -0,0 +1,57 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ScreenWrapper from '../components/ScreenWrapper'; +import HeaderWithBackButton from '../components/HeaderWithBackButton'; +import Navigation from '../libs/Navigation/Navigation'; +import useLocalize from '../hooks/useLocalize'; +import ReceiptSelector from './iou/ReceiptSelector'; +import DragAndDropProvider from '../components/DragAndDrop/Provider'; + +const propTypes = { + /** React Navigation route */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** The type of IOU report, i.e. bill, request, send */ + iouType: PropTypes.string, + + /** The report ID of the IOU */ + reportID: PropTypes.string, + }), + }).isRequired, + + /** The callback fired when we confirm to replace the receipt */ + replaceReceipt: PropTypes.func, +}; + +const defaultProps = { + replaceReceipt: () => {} +} + +function EditRequestReceiptPage({route, replaceReceipt}) { + const {translate} = useLocalize(); + + return ( + + + + + + + ); +} + +EditRequestReceiptPage.propTypes = propTypes; +EditRequestReceiptPage.defaultProps = defaultProps; +EditRequestReceiptPage.displayName = 'EditRequestReceiptPage'; + +export default EditRequestReceiptPage; diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index e11608c94462..d0b558984d97 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -263,7 +263,7 @@ function MoneyRequestConfirmPage(props) { threeDotsMenuItems={[{ icon: Expensicons.Receipt, text: props.translate('receipt.addReceipt'), - onSelected: () => Navigation.navigate(ROUTES.getMoneyRequestReceiptRoute(props.iouType, reportID)), + onSelected: () => Navigation.navigate(ROUTES.getMoneyRequestReceiptRoute(iouType.current, reportID.current)), }]} /> )} From 0ab130ce7e0d5243b55f84d6652e59a0c762c2a2 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 12 Sep 2023 18:29:46 +0800 Subject: [PATCH 030/156] update logic in navigateToNextPage --- src/libs/actions/IOU.js | 9 ++++++++- src/pages/iou/ReceiptSelector/index.js | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 2102ed9223c8..f8eeaa96fd0a 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1863,8 +1863,9 @@ function createEmptyTransaction() { * @param {String} iouType * @param {String} reportID * @param {Object} report + * @param {Object} route */ -function navigateToNextPage(iou, iouType, reportID, report) { +function navigateToNextPage(iou, iouType, reportID, report, route = '') { const moneyRequestID = `${iouType}${reportID}`; const shouldReset = iou.id !== moneyRequestID; @@ -1874,6 +1875,12 @@ function navigateToNextPage(iou, iouType, reportID, report) { resetMoneyRequestInfo(moneyRequestID); } + // If we're adding a receipt, that means the user came from the confirmation page and we navigate back to it. + if (route.slice(1) === ROUTES.getMoneyRequestReceiptRoute(iouType, reportID)) { + Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); + return; + } + // If a request is initiated on a report, skip the participants selection step and navigate to the confirmation page. if (report.reportID) { // Reinitialize the participants when the money request ID in Onyx does not match the ID from params diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 94246b1e6fd1..847b68c79242 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -83,7 +83,7 @@ function ReceiptSelector(props) { const filePath = URL.createObjectURL(file); IOU.setMoneyRequestReceipt(filePath, file.name); - IOU.navigateToNextPage(iou, iouType, reportID, report); + IOU.navigateToNextPage(iou, iouType, reportID, report, props.route.path); }; return ( From 34760eb7852235bee549808bb6cb38d7eb19ae78 Mon Sep 17 00:00:00 2001 From: Thiago Brezinski Date: Tue, 12 Sep 2023 13:08:23 +0100 Subject: [PATCH 031/156] fix(selection-list): highlight on lists with 1 section --- .../SelectionList/BaseSelectionList.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index b76ded8a542f..1b45f0d42e5c 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -167,15 +167,13 @@ function BaseSelectionList({ listRef.current.scrollToLocation({sectionIndex: adjustedSectionIndex, itemIndex, animated, viewOffset: variables.contentHeaderHeight}); }; - const selectRow = (item, index) => { + const selectRow = (item) => { // In single-selection lists we don't care about updating the focused index, because the list is closed after selecting an item if (canSelectMultiple) { - if (sections.length === 1) { - // If the list has only 1 section (e.g. Workspace Members list), we always focus the next available item - const nextAvailableIndex = _.findIndex(flattenedSections.allOptions, (option, i) => i > index && !option.isDisabled); - setFocusedIndex(nextAvailableIndex); - } else { - // If the list has multiple sections (e.g. Workspace Invite list), we focus the first one after all the selected (selected items are always at the top) + if (sections.length > 1) { + // If the list has only 1 section (e.g. Workspace Members list), we do nothing. + // If the list has multiple sections (e.g. Workspace Invite list), we focus the first one after all the selected (selected items are always at the top). + const selectedOptionsCount = item.isSelected ? flattenedSections.selectedOptions.length - 1 : flattenedSections.selectedOptions.length + 1; setFocusedIndex(selectedOptionsCount); @@ -196,7 +194,7 @@ function BaseSelectionList({ return; } - selectRow(focusedOption, focusedIndex); + selectRow(focusedOption); }; /** @@ -250,7 +248,7 @@ function BaseSelectionList({ selectRow(item, index)} + onSelectRow={() => selectRow(item)} onDismissError={onDismissError} /> ); @@ -261,7 +259,7 @@ function BaseSelectionList({ item={item} isFocused={isFocused} isDisabled={isDisabled} - onSelectRow={() => selectRow(item, index)} + onSelectRow={() => selectRow(item)} /> ); }; From 67c2a5f7fe67c2dfe2574af00056dc07885418a2 Mon Sep 17 00:00:00 2001 From: Thiago Brezinski Date: Tue, 12 Sep 2023 13:59:40 +0100 Subject: [PATCH 032/156] chore: make function call concise --- src/components/SelectionList/BaseSelectionList.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 1b45f0d42e5c..230de8e75dcc 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -248,7 +248,7 @@ function BaseSelectionList({ selectRow(item)} + onSelectRow={selectRow} onDismissError={onDismissError} /> ); @@ -259,7 +259,7 @@ function BaseSelectionList({ item={item} isFocused={isFocused} isDisabled={isDisabled} - onSelectRow={() => selectRow(item)} + onSelectRow={selectRow} /> ); }; From 6a86285381622159f3f91239c212945cf52752f4 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 12 Sep 2023 21:15:30 +0200 Subject: [PATCH 033/156] fix: check if waypoint is not null --- src/libs/actions/Transaction.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 47010ba275b3..16a974db25ff 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -93,9 +93,8 @@ function saveWaypoint(transactionID: string, index: string, waypoint: RecentWayp if (!lodashHas(waypoint, 'lat') || !lodashHas(waypoint, 'lng')) { return; } - const recentWaypointAlreadyExists = recentWaypoints.find((recentWaypoint) => recentWaypoint?.address === waypoint?.address); - if (!recentWaypointAlreadyExists) { + if (!recentWaypointAlreadyExists && waypoint !== null) { const clonedWaypoints = lodashClone(recentWaypoints); clonedWaypoints.unshift(waypoint); Onyx.merge(ONYXKEYS.NVP_RECENT_WAYPOINTS, clonedWaypoints.slice(0, 5)); @@ -108,7 +107,6 @@ function removeWaypoint(transactionID: string, currentIndex: string) { const transaction = allTransactions?.[transactionID] ?? {}; const existingWaypoints = transaction?.comment?.waypoints ?? {}; const totalWaypoints = Object.keys(existingWaypoints).length; - // Prevents removing the starting or ending waypoint but clear the stored address only if there are only two waypoints if (totalWaypoints === 2 && (index === 0 || index === totalWaypoints - 1)) { saveWaypoint(transactionID, index.toString(), null); From 63e92eed9a23e66c6f386ebb03bba63bf750b237 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 09:51:31 +0800 Subject: [PATCH 034/156] update native implementation --- src/pages/iou/ReceiptSelector/index.js | 3 +++ src/pages/iou/ReceiptSelector/index.native.js | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 847b68c79242..43f1b965d33c 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -43,6 +43,9 @@ const propTypes = { /** The report ID of the IOU */ reportID: PropTypes.string, }), + + /** The current route path */ + path: PropTypes.string, }).isRequired, /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index 4ff32d940c9f..e96109deba01 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -35,6 +35,9 @@ const propTypes = { /** The report ID of the IOU */ reportID: PropTypes.string, }), + + /** The current route path */ + path: PropTypes.string, }).isRequired, /** The report on which the request is initiated on */ @@ -196,7 +199,7 @@ function ReceiptSelector(props) { }) .then((photo) => { IOU.setMoneyRequestReceipt(`file://${photo.path}`, photo.path); - IOU.navigateToNextPage(props.iou, iouType, reportID, props.report); + IOU.navigateToNextPage(props.iou, iouType, reportID, props.report, props.route.path); }) .catch((error) => { showCameraAlert(); @@ -261,7 +264,7 @@ function ReceiptSelector(props) { showImagePicker(launchImageLibrary) .then((receiptImage) => { IOU.setMoneyRequestReceipt(receiptImage[0].uri, receiptImage[0].fileName); - IOU.navigateToNextPage(props.iou, iouType, reportID, props.report); + IOU.navigateToNextPage(props.iou, iouType, reportID, props.report, props.route.path); }) .catch(() => { Log.info('User did not select an image from gallery'); From 2ba6f182454704d4bb04c957d6387fa284924b50 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 10:18:42 +0800 Subject: [PATCH 035/156] use correct optimistic receipt state --- src/pages/iou/steps/MoneyRequestConfirmPage.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index d0b558984d97..8c9a93663581 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -195,6 +195,9 @@ function MoneyRequestConfirmPage(props) { if (props.iou.receiptPath && props.iou.receiptSource) { FileUtils.readFileAsync(props.iou.receiptPath, props.iou.receiptSource).then((receipt) => { + if (receipt && iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST) { + receipt.state = CONST.IOU.RECEIPT_STATE.OPEN; + } requestMoney(selectedParticipants, trimmedComment, receipt); }); return; From 4cf4ffd4095d498cb1f04a9d198404e3da54b98f Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 10:33:13 +0800 Subject: [PATCH 036/156] pass receipt state to API --- src/libs/actions/IOU.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index f8eeaa96fd0a..836ae9e55acc 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -596,6 +596,7 @@ function requestMoney(report, amount, currency, created, merchant, payeeEmail, p createdIOUReportActionID, reportPreviewReportActionID: reportPreviewAction.reportActionID, receipt, + receiptState: receipt.state, }, onyxData, ); From b3df1cb48207ef5ed5b74fad142d6fc07bb88663 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 11:26:24 +0800 Subject: [PATCH 037/156] rm attachmentpicker --- .../iou/steps/MoneyRequestConfirmPage.js | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 8c9a93663581..7ea23db3a45e 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -27,7 +27,6 @@ import useWindowDimensions from '../../../hooks/useWindowDimensions'; import * as StyleUtils from '../../../styles/StyleUtils'; import {iouPropTypes, iouDefaultProps} from '../propTypes'; import * as Expensicons from '../../../components/Icon/Expensicons'; -import AttachmentPicker from '../../../components/AttachmentPicker'; const propTypes = { /** React Navigation route */ @@ -256,21 +255,17 @@ function MoneyRequestConfirmPage(props) { {({safeAreaPaddingBottomStyle}) => ( - - {({openPicker}) => ( - Navigation.navigate(ROUTES.getMoneyRequestReceiptRoute(iouType.current, reportID.current)), - }]} - /> - )} - + Navigation.navigate(ROUTES.getMoneyRequestReceiptRoute(iouType.current, reportID.current)), + }]} + /> {/* * The MoneyRequestConfirmationList component uses a SectionList which uses a VirtualizedList internally. * VirtualizedList cannot be directly nested within ScrollViews of the same orientation. From c85695bef0348aa0bccb87d1d40653a2169068f5 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 11:27:42 +0800 Subject: [PATCH 038/156] fix condition to display threeDotMenu --- src/pages/iou/steps/MoneyRequestConfirmPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 7ea23db3a45e..5ec933ab47ea 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -258,7 +258,7 @@ function MoneyRequestConfirmPage(props) { Date: Wed, 13 Sep 2023 13:51:22 +0800 Subject: [PATCH 039/156] add receipt state --- src/pages/iou/steps/MoneyRequestConfirmPage.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 5ec933ab47ea..3c5094a28817 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -64,6 +64,7 @@ function MoneyRequestConfirmPage(props) { const prevMoneyRequestId = useRef(props.iou.id); const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType.current, props.selectedTab); + const isManualRequest = props.selectedTab === CONST.TAB.MANUAL; const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); const participants = useMemo( () => @@ -193,10 +194,9 @@ function MoneyRequestConfirmPage(props) { } if (props.iou.receiptPath && props.iou.receiptSource) { - FileUtils.readFileAsync(props.iou.receiptPath, props.iou.receiptSource).then((receipt) => { - if (receipt && iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST) { - receipt.state = CONST.IOU.RECEIPT_STATE.OPEN; - } + FileUtils.readFileAsync(props.iou.receiptPath, props.iou.receiptSource).then((file) => { + const receipt = file; + receipt.state = (file && isManualRequest) ? CONST.IOU.RECEIPT_STATE.OPEN : CONST.IOU.RECEIPT_STATE.SCANREADY; requestMoney(selectedParticipants, trimmedComment, receipt); }); return; @@ -220,6 +220,7 @@ function MoneyRequestConfirmPage(props) { isDistanceRequest, requestMoney, createDistanceRequest, + isManualRequest, ], ); @@ -258,7 +259,7 @@ function MoneyRequestConfirmPage(props) { Date: Wed, 13 Sep 2023 13:55:43 +0800 Subject: [PATCH 040/156] update whisper logic --- 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 8b84555ca2a9..658f07156e36 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2141,7 +2141,7 @@ function buildOptimisticIOUReportAction( created: DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, receipt, - whisperedToAccountIDs: !_.isEmpty(receipt) ? [currentUserAccountID] : [], + whisperedToAccountIDs: lodashGet(receipt, 'state', '') === CONST.IOU.RECEIPT_STATE.SCANREADY ? [currentUserAccountID] : [], }; } /** From db82a3e0f0e4cabda5baa7cab0373fc4397bbc2f Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 14:00:39 +0800 Subject: [PATCH 041/156] add safe object access --- src/libs/actions/IOU.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 836ae9e55acc..4df5395a59bb 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -596,7 +596,7 @@ function requestMoney(report, amount, currency, created, merchant, payeeEmail, p createdIOUReportActionID, reportPreviewReportActionID: reportPreviewAction.reportActionID, receipt, - receiptState: receipt.state, + receiptState: lodashGet(receipt, 'state'), }, onyxData, ); From 0836a758e9a6efff43eb668c948426e28cbf893b Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 14:23:14 +0800 Subject: [PATCH 042/156] add attach option to transaction thread --- src/components/MoneyRequestHeader.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index f04a41ae1153..28b0ed2f89e3 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -13,6 +13,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimen import compose from '../libs/compose'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; +import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; import * as IOU from '../libs/actions/IOU'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; @@ -83,6 +84,11 @@ function MoneyRequestHeader(props) { shouldShowPinButton={false} shouldShowThreeDotsButton={isActionOwner && !isSettled} threeDotsMenuItems={[ + ...(TransactionUtils.hasReceipt(transaction) ? [] : [{ + icon: Expensicons.Receipt, + text: translate('receipt.addReceipt'), + onSelected: () => Navigation.navigate(ROUTES.getEditRequestRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)), + }]), { icon: Expensicons.Trashcan, text: translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), From 698a17a6e0307f7653961ccb4f430de2a162de9e Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 15:10:16 +0800 Subject: [PATCH 043/156] fix style --- src/components/MoneyRequestHeader.js | 14 +++++++++----- src/pages/EditRequestReceiptPage.js | 4 ++-- src/pages/iou/steps/MoneyRequestConfirmPage.js | 14 ++++++++------ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 28b0ed2f89e3..f72e7a395b85 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -84,11 +84,15 @@ function MoneyRequestHeader(props) { shouldShowPinButton={false} shouldShowThreeDotsButton={isActionOwner && !isSettled} threeDotsMenuItems={[ - ...(TransactionUtils.hasReceipt(transaction) ? [] : [{ - icon: Expensicons.Receipt, - text: translate('receipt.addReceipt'), - onSelected: () => Navigation.navigate(ROUTES.getEditRequestRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)), - }]), + ...(TransactionUtils.hasReceipt(transaction) + ? [] + : [ + { + icon: Expensicons.Receipt, + text: translate('receipt.addReceipt'), + onSelected: () => Navigation.navigate(ROUTES.getEditRequestRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)), + }, + ]), { icon: Expensicons.Trashcan, text: translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), diff --git a/src/pages/EditRequestReceiptPage.js b/src/pages/EditRequestReceiptPage.js index 8c4067713705..a901f8d919b3 100644 --- a/src/pages/EditRequestReceiptPage.js +++ b/src/pages/EditRequestReceiptPage.js @@ -25,8 +25,8 @@ const propTypes = { }; const defaultProps = { - replaceReceipt: () => {} -} + replaceReceipt: () => {}, +}; function EditRequestReceiptPage({route, replaceReceipt}) { const {translate} = useLocalize(); diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 62e82d814b39..953e670e0919 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -207,7 +207,7 @@ function MoneyRequestConfirmPage(props) { if (props.iou.receiptPath && props.iou.receiptSource) { FileUtils.readFileAsync(props.iou.receiptPath, props.iou.receiptSource).then((file) => { const receipt = file; - receipt.state = (file && isManualRequest) ? CONST.IOU.RECEIPT_STATE.OPEN : CONST.IOU.RECEIPT_STATE.SCANREADY; + receipt.state = file && isManualRequest ? CONST.IOU.RECEIPT_STATE.OPEN : CONST.IOU.RECEIPT_STATE.SCANREADY; requestMoney(selectedParticipants, trimmedComment, receipt); }); return; @@ -272,11 +272,13 @@ function MoneyRequestConfirmPage(props) { onBackButtonPress={navigateBack} shouldShowThreeDotsButton={isManualRequest} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} - threeDotsMenuItems={[{ - icon: Expensicons.Receipt, - text: props.translate('receipt.addReceipt'), - onSelected: () => Navigation.navigate(ROUTES.getMoneyRequestReceiptRoute(iouType.current, reportID.current)), - }]} + threeDotsMenuItems={[ + { + icon: Expensicons.Receipt, + text: props.translate('receipt.addReceipt'), + onSelected: () => Navigation.navigate(ROUTES.getMoneyRequestReceiptRoute(iouType.current, reportID.current)), + }, + ]} /> {/* * The MoneyRequestConfirmationList component uses a SectionList which uses a VirtualizedList internally. From 492c4a54a043f04c840fc5e4b3adea2ee2170dfe Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 15:16:45 +0800 Subject: [PATCH 044/156] fix whisper for report preview --- src/libs/ReportUtils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 9b35d1b8a20a..74ebf853fe1c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2198,7 +2198,7 @@ function buildOptimisticApprovedReportAction(amount, currency, expenseReportID) * @returns {Object} */ function buildOptimisticReportPreview(chatReport, iouReport, comment = '', transaction = undefined) { - const hasReceipt = TransactionUtils.hasReceipt(transaction); + const isReceiptBeingScanned = TransactionUtils.isReceiptBeingScanned(transaction); const message = getReportPreviewMessage(iouReport); return { reportActionID: NumberUtils.rand64(), @@ -2219,11 +2219,11 @@ function buildOptimisticReportPreview(chatReport, iouReport, comment = '', trans created: DateUtils.getDBTime(), accountID: iouReport.managerID || 0, // The preview is initially whispered if created with a receipt, so the actor is the current user as well - actorAccountID: hasReceipt ? currentUserAccountID : iouReport.managerID || 0, + actorAccountID: isReceiptBeingScanned ? currentUserAccountID : iouReport.managerID || 0, childMoneyRequestCount: 1, childLastMoneyRequestComment: comment, childLastReceiptTransactionIDs: hasReceipt ? transaction.transactionID : '', - whisperedToAccountIDs: hasReceipt ? [currentUserAccountID] : [], + whisperedToAccountIDs: isReceiptBeingScanned ? [currentUserAccountID] : [], }; } From dcf2bb62c96583fd024077f02a252fa053b3d766 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 15:32:40 +0800 Subject: [PATCH 045/156] fix undefined variable --- src/libs/ReportUtils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 74ebf853fe1c..0d562ca60c03 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2198,6 +2198,7 @@ function buildOptimisticApprovedReportAction(amount, currency, expenseReportID) * @returns {Object} */ function buildOptimisticReportPreview(chatReport, iouReport, comment = '', transaction = undefined) { + const hasReceipt = TransactionUtils.hasReceipt(transaction); const isReceiptBeingScanned = TransactionUtils.isReceiptBeingScanned(transaction); const message = getReportPreviewMessage(iouReport); return { @@ -2219,7 +2220,7 @@ function buildOptimisticReportPreview(chatReport, iouReport, comment = '', trans created: DateUtils.getDBTime(), accountID: iouReport.managerID || 0, // The preview is initially whispered if created with a receipt, so the actor is the current user as well - actorAccountID: isReceiptBeingScanned ? currentUserAccountID : iouReport.managerID || 0, + actorAccountID: hasReceipt ? currentUserAccountID : iouReport.managerID || 0, childMoneyRequestCount: 1, childLastMoneyRequestComment: comment, childLastReceiptTransactionIDs: hasReceipt ? transaction.transactionID : '', From 076f7c8b3555e64e9829e3315666f27571429f35 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 15:56:04 +0800 Subject: [PATCH 046/156] fix tests --- 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 0d562ca60c03..8ef210542e55 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2199,7 +2199,7 @@ function buildOptimisticApprovedReportAction(amount, currency, expenseReportID) */ function buildOptimisticReportPreview(chatReport, iouReport, comment = '', transaction = undefined) { const hasReceipt = TransactionUtils.hasReceipt(transaction); - const isReceiptBeingScanned = TransactionUtils.isReceiptBeingScanned(transaction); + const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); const message = getReportPreviewMessage(iouReport); return { reportActionID: NumberUtils.rand64(), From ba0232c664ad8b253c53dfefc81e46414b6aa6f8 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 13 Sep 2023 16:05:46 +0800 Subject: [PATCH 047/156] add useCallback dependency --- src/pages/iou/ReceiptSelector/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index e96109deba01..3aa6da4415c3 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -205,7 +205,7 @@ function ReceiptSelector(props) { showCameraAlert(); Log.warn('Error taking photo', error); }); - }, [flash, iouType, props.iou, props.report, reportID, translate]); + }, [flash, iouType, props.iou, props.report, reportID, translate, props.route.path]); CameraPermission.getCameraPermissionStatus().then((permissionStatus) => { setPermissions(permissionStatus); From 43d8080e810b61155f1096f9cd6e6fb9e3c348be Mon Sep 17 00:00:00 2001 From: Kamil Owczarz <91068263+kowczarz@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:59:30 +0200 Subject: [PATCH 048/156] Apply suggestions from code review Co-authored-by: Carlos Martins --- src/hooks/useForm/FormProvider.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hooks/useForm/FormProvider.js b/src/hooks/useForm/FormProvider.js index c29597f48028..2709f68b906d 100644 --- a/src/hooks/useForm/FormProvider.js +++ b/src/hooks/useForm/FormProvider.js @@ -79,7 +79,7 @@ const defaultProps = { scrollContextEnabled: false, footerContent: null, style: [], - validate: () => ({}), + validate: () => {}, }; function getInitialValueByType(valueType) { @@ -137,6 +137,7 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c if (network.isOffline && !enabledWhenOffline) { return; } + // Call submit handler onSubmit(inputValues); }, [enabledWhenOffline, formState.isLoading, inputValues, network.isOffline, onSubmit, onValidate]); @@ -190,7 +191,7 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c // Only run validation when user proactively blurs the input. if (Visibility.isVisible() && Visibility.hasFocus()) { // We delay the validation in order to prevent Checkbox loss of focus when - // the user are focusing a TextInput and proceeds to toggle a CheckBox in + // the user is focusing a TextInput and proceeds to toggle a CheckBox in // web and mobile web platforms. setTimeout(() => { setTouchedInput(inputID); From 01a455f38706530a7589398708f037960d66f5ee Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Wed, 13 Sep 2023 18:26:16 +0200 Subject: [PATCH 049/156] Code review changes --- src/hooks/useForm/FormProvider.js | 24 ++++++++++++++---------- src/hooks/useForm/FormWrapper.js | 19 ++++++++----------- src/hooks/useForm/InputWrapper.js | 2 +- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/hooks/useForm/FormProvider.js b/src/hooks/useForm/FormProvider.js index 2709f68b906d..ddc59b63bf0b 100644 --- a/src/hooks/useForm/FormProvider.js +++ b/src/hooks/useForm/FormProvider.js @@ -8,7 +8,6 @@ import * as FormActions from '../../libs/actions/FormActions'; import FormContext from './FormContext'; import FormWrapper from './FormWrapper'; import compose from '../../libs/compose'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import {withNetwork} from '../../components/OnyxProvider'; import stylePropTypes from '../../styles/stylePropTypes'; import networkPropTypes from '../../components/networkPropTypes'; @@ -66,7 +65,9 @@ const propTypes = { /** Information about the network */ network: networkPropTypes.isRequired, - ...withLocalizePropTypes, + shouldValidateOnBlur: PropTypes.bool, + + shouldValidateOnChange: PropTypes.bool, }; const defaultProps = { @@ -80,6 +81,8 @@ const defaultProps = { footerContent: null, style: [], validate: () => {}, + shouldValidateOnBlur: false, + shouldValidateOnChange: false, }; function getInitialValueByType(valueType) { @@ -99,7 +102,7 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c const inputRefs = useRef({}); const touchedInputs = useRef({}); const [inputValues, setInputValues] = useState({}); - const [errors, setErrors] = useState([]); + const [errors, setErrors] = useState({}); const onValidate = useCallback( (values) => { @@ -147,18 +150,18 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c const newRef = propsToParse.ref || createRef(); inputRefs[inputID] = newRef; - // We want to initialize the input value if it's undefined - if (_.isUndefined(inputValues[inputID])) { - inputValues[inputID] = propsToParse.defaultValue || getInitialValueByType(propsToParse.valueType); + if (!_.isUndefined(propsToParse.value)) { + inputValues[inputID] = propsToParse.value; } // We force the form to set the input value from the defaultValue props if there is a saved valid value - if (propsToParse.shouldUseDefaultValue) { + else if (propsToParse.shouldUseDefaultValue) { inputValues[inputID] = propsToParse.defaultValue; } - if (!_.isUndefined(propsToParse.value)) { - inputValues[inputID] = propsToParse.value; + // We want to initialize the input value if it's undefined + else if (_.isUndefined(inputValues[inputID])) { + inputValues[inputID] = _.isUndefined(propsToParse.defaultValue) ? getInitialValueByType(propsToParse.valueType) : propsToParse.defaultValue; } const errorFields = lodashGet(formState, 'errorFields', {}); @@ -238,6 +241,8 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c {children} @@ -250,7 +255,6 @@ FormProvider.propTypes = propTypes; FormProvider.defaultProps = defaultProps; export default compose( - withLocalize, withNetwork(), withOnyx({ formState: { diff --git a/src/hooks/useForm/FormWrapper.js b/src/hooks/useForm/FormWrapper.js index 9ec4492f97aa..b8d1dc999ef9 100644 --- a/src/hooks/useForm/FormWrapper.js +++ b/src/hooks/useForm/FormWrapper.js @@ -11,8 +11,6 @@ import SafeAreaConsumer from '../../components/SafeAreaConsumer'; import ScrollViewWithContext from '../../components/ScrollViewWithContext'; import stylePropTypes from '../../styles/stylePropTypes'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import compose from '../../libs/compose'; const propTypes = { /** A unique Onyx key identifying the form */ @@ -61,7 +59,9 @@ const propTypes = { /** Custom content to display in the footer after submit button */ footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), - ...withLocalizePropTypes, + errors: PropTypes.objectOf(PropTypes.string).isRequired, + + inputRefs: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(Element)})])).isRequired, }; const defaultProps = { @@ -187,11 +187,8 @@ FormWrapper.displayName = 'FormWrapper'; FormWrapper.propTypes = propTypes; FormWrapper.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - formState: { - key: (props) => props.formID, - }, - }), -)(FormWrapper); +export default withOnyx({ + formState: { + key: (props) => props.formID, + }, +})(FormWrapper); diff --git a/src/hooks/useForm/InputWrapper.js b/src/hooks/useForm/InputWrapper.js index 31dc3edb02e8..83fede5a0a89 100644 --- a/src/hooks/useForm/InputWrapper.js +++ b/src/hooks/useForm/InputWrapper.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import FormContext from './FormContext'; const propTypes = { - RenderInput: PropTypes.node.isRequired, + RenderInput: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired, inputID: PropTypes.string.isRequired, valueType: PropTypes.string, forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), From 9f28189ca9b747ea3c4052c63bca006d5589929c Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Wed, 13 Sep 2023 18:27:44 +0200 Subject: [PATCH 050/156] Code review changes --- src/hooks/useForm/FormProvider.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/useForm/FormProvider.js b/src/hooks/useForm/FormProvider.js index ddc59b63bf0b..3ebda7edecae 100644 --- a/src/hooks/useForm/FormProvider.js +++ b/src/hooks/useForm/FormProvider.js @@ -141,7 +141,6 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c return; } - // Call submit handler onSubmit(inputValues); }, [enabledWhenOffline, formState.isLoading, inputValues, network.isOffline, onSubmit, onValidate]); From 74a8de34a7aa01a55e428ce5cdb503a9cb9bbaf4 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 14 Sep 2023 12:47:16 +0800 Subject: [PATCH 051/156] use default style --- src/components/AvatarWithDisplayName.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index e82dbe05a6d0..6151775aaf3f 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -112,7 +112,6 @@ function AvatarWithDisplayName(props) { )} From 007ff215ff5d0b2f7de5fa42d1655b065274262a Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 14 Sep 2023 13:51:15 +0800 Subject: [PATCH 052/156] remove unused import --- src/components/AvatarWithDisplayName.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 6151775aaf3f..a6c630b775f7 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -17,7 +17,6 @@ import DisplayNames from './DisplayNames'; import compose from '../libs/compose'; import * as OptionsListUtils from '../libs/OptionsListUtils'; import Text from './Text'; -import * as StyleUtils from '../styles/StyleUtils'; import ParentNavigationSubtitle from './ParentNavigationSubtitle'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Navigation from '../libs/Navigation/Navigation'; From 7ee8ee39fa072917bee1cb7ac1bfc21e7125ca57 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 14 Sep 2023 10:45:15 +0200 Subject: [PATCH 053/156] Code style fixes --- src/hooks/useForm/FormProvider.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/hooks/useForm/FormProvider.js b/src/hooks/useForm/FormProvider.js index 3ebda7edecae..545916c1d71f 100644 --- a/src/hooks/useForm/FormProvider.js +++ b/src/hooks/useForm/FormProvider.js @@ -151,15 +151,11 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c if (!_.isUndefined(propsToParse.value)) { inputValues[inputID] = propsToParse.value; - } - - // We force the form to set the input value from the defaultValue props if there is a saved valid value - else if (propsToParse.shouldUseDefaultValue) { + } else if (propsToParse.shouldUseDefaultValue) { + // We force the form to set the input value from the defaultValue props if there is a saved valid value inputValues[inputID] = propsToParse.defaultValue; - } - - // We want to initialize the input value if it's undefined - else if (_.isUndefined(inputValues[inputID])) { + } else if (_.isUndefined(inputValues[inputID])) { + // We want to initialize the input value if it's undefined inputValues[inputID] = _.isUndefined(propsToParse.defaultValue) ? getInitialValueByType(propsToParse.valueType) : propsToParse.defaultValue; } From cc0c7e2d1eabc828a0abb61fc9cbebc8692cc1e2 Mon Sep 17 00:00:00 2001 From: Thiago Brezinski Date: Thu, 14 Sep 2023 11:29:26 +0100 Subject: [PATCH 054/156] fix(selection-list): remove active item highlight --- src/components/SelectionList/RadioListItem.js | 1 - src/components/SelectionList/UserListItem.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/components/SelectionList/RadioListItem.js b/src/components/SelectionList/RadioListItem.js index 92e3e84b66c8..4adf582b30d4 100644 --- a/src/components/SelectionList/RadioListItem.js +++ b/src/components/SelectionList/RadioListItem.js @@ -17,7 +17,6 @@ function RadioListItem({item, isFocused = false, isDisabled = false, onSelectRow accessibilityRole="button" hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} - focusStyle={styles.hoveredComponentBG} > diff --git a/src/components/SelectionList/UserListItem.js b/src/components/SelectionList/UserListItem.js index dd90fc750510..f701b1873cbe 100644 --- a/src/components/SelectionList/UserListItem.js +++ b/src/components/SelectionList/UserListItem.js @@ -62,7 +62,6 @@ function UserListItem({item, isFocused = false, showTooltip, onSelectRow, onDism accessibilityState={{checked: item.isSelected}} hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} - focusStyle={styles.hoveredComponentBG} > Date: Thu, 14 Sep 2023 21:40:37 +0800 Subject: [PATCH 055/156] fix second avatar border color does not match the background if the option is focused --- src/components/OptionRow.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index 50aff23dc9d0..8c4d52f79c13 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -206,18 +206,14 @@ class OptionRow extends Component { ) : ( ))} From a8cc185eb6a164979a08371dc2548433d653dc1f Mon Sep 17 00:00:00 2001 From: Thiago Brezinski Date: Thu, 14 Sep 2023 16:15:15 +0100 Subject: [PATCH 056/156] fix(selection-list): remove active item highlight --- src/components/Button/index.js | 1 + src/components/SelectionList/BaseSelectionList.js | 5 ++++- src/components/SelectionList/RadioListItem.js | 1 - src/components/SelectionList/UserListItem.js | 1 - src/hooks/useActiveElement/index.js | 6 ++++++ src/hooks/useActiveElement/index.native.js | 5 +++++ 6 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/Button/index.js b/src/components/Button/index.js index bfde528a4750..0d19691d1256 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -305,6 +305,7 @@ class Button extends Component { ]} nativeID={this.props.nativeID} accessibilityLabel={this.props.accessibilityLabel} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} hoverDimmingValue={1} > {this.renderContent()} diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 6699e56e54f6..895272ba18ef 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -135,6 +135,9 @@ function BaseSelectionList({ }; }, [canSelectMultiple, sections]); + // Disable `Enter` hotkey if the active element is a button or checkbox + const shouldDisableHotkeys = activeElement && [CONST.ACCESSIBILITY_ROLE.BUTTON, CONST.ACCESSIBILITY_ROLE.CHECKBOX].includes(activeElement.role); + // If `initiallyFocusedOptionKey` is not passed, we fall back to `-1`, to avoid showing the highlight on the first member const [focusedIndex, setFocusedIndex] = useState(() => _.findIndex(flattenedSections.allOptions, (option) => option.keyForList === initiallyFocusedOptionKey)); @@ -287,7 +290,7 @@ function BaseSelectionList({ useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, selectFocusedOption, { captureOnInputs: true, shouldBubble: () => !flattenedSections.allOptions[focusedIndex], - isActive: !activeElement, + isActive: !shouldDisableHotkeys, }); /** Calls confirm action when pressing CTRL (CMD) + Enter */ diff --git a/src/components/SelectionList/RadioListItem.js b/src/components/SelectionList/RadioListItem.js index 92e3e84b66c8..4adf582b30d4 100644 --- a/src/components/SelectionList/RadioListItem.js +++ b/src/components/SelectionList/RadioListItem.js @@ -17,7 +17,6 @@ function RadioListItem({item, isFocused = false, isDisabled = false, onSelectRow accessibilityRole="button" hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} - focusStyle={styles.hoveredComponentBG} > diff --git a/src/components/SelectionList/UserListItem.js b/src/components/SelectionList/UserListItem.js index dd90fc750510..f701b1873cbe 100644 --- a/src/components/SelectionList/UserListItem.js +++ b/src/components/SelectionList/UserListItem.js @@ -62,7 +62,6 @@ function UserListItem({item, isFocused = false, showTooltip, onSelectRow, onDism accessibilityState={{checked: item.isSelected}} hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} - focusStyle={styles.hoveredComponentBG} > Date: Fri, 15 Sep 2023 14:00:02 +0200 Subject: [PATCH 057/156] fix focus on different scenarios --- src/components/CategoryPicker/index.js | 26 ++++++++++--------- .../OptionsSelector/BaseOptionsSelector.js | 4 +-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js index 91c7e82e7887..4ee429a2e389 100644 --- a/src/components/CategoryPicker/index.js +++ b/src/components/CategoryPicker/index.js @@ -34,21 +34,23 @@ function CategoryPicker({policyCategories, reportID, iouType, iou, policyRecentl ]; }, [iou.category]); + const sections = useMemo(() => { + const {categoryOptions} = OptionsListUtils.getNewChatOptions({}, {}, [], searchValue, selectedOptions, [], false, false, true, policyCategories, policyRecentlyUsedCategories, false); + + return categoryOptions; + }, [policyCategories, policyRecentlyUsedCategories, searchValue, selectedOptions]); + const initialFocusedIndex = useMemo(() => { - if (isCategoriesCountBelowThreshold && selectedOptions.length > 0) { - return _.chain(policyCategories) - .values() - .findIndex((category) => category.name === selectedOptions[0].name, true) - .value(); - } + let categoryInitialFocusedIndex = 0; - return 0; - }, [policyCategories, selectedOptions, isCategoriesCountBelowThreshold]); + if (!_.isEmpty(searchValue) || isCategoriesCountBelowThreshold) { + const index = _.findIndex(lodashGet(sections, '[0].data', []), (category) => category.searchText === iou.category); - const sections = useMemo( - () => OptionsListUtils.getNewChatOptions({}, {}, [], searchValue, selectedOptions, [], false, false, true, policyCategories, policyRecentlyUsedCategories, false).categoryOptions, - [policyCategories, policyRecentlyUsedCategories, searchValue, selectedOptions], - ); + categoryInitialFocusedIndex = index === -1 ? 0 : index; + } + + return categoryInitialFocusedIndex; + }, [iou.category, searchValue, isCategoriesCountBelowThreshold, sections]); const headerMessage = OptionsListUtils.getHeaderMessage(lodashGet(sections, '[0].data.length', 0) > 0, false, searchValue); const shouldShowTextInput = !isCategoriesCountBelowThreshold; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index bff9f8b6d7d0..aa02701b1c98 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -107,13 +107,13 @@ class BaseOptionsSelector extends Component { }); return; } - const newFocusedIndex = this.props.selectedOptions.length; + const newFocusedIndex = this.props.selectedOptions.length; // eslint-disable-next-line react/no-did-update-set-state this.setState( { allOptions: newOptions, - focusedIndex: newFocusedIndex, + focusedIndex: _.isNumber(this.props.initialFocusedIndex) ? this.props.initialFocusedIndex : newFocusedIndex, }, () => { // If we just toggled an option on a multi-selection page or cleared the search input, scroll to top From ac3ffe3fb2d3d3fece48fd9fb11eb6d13484fbef Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 15 Sep 2023 14:26:46 +0200 Subject: [PATCH 058/156] ref: migrate ReportActions to TS --- .../actions/{ReportActions.js => ReportActions.ts} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename src/libs/actions/{ReportActions.js => ReportActions.ts} (86%) diff --git a/src/libs/actions/ReportActions.js b/src/libs/actions/ReportActions.ts similarity index 86% rename from src/libs/actions/ReportActions.js rename to src/libs/actions/ReportActions.ts index d270876840ac..9a1e27b010cd 100644 --- a/src/libs/actions/ReportActions.js +++ b/src/libs/actions/ReportActions.ts @@ -3,14 +3,14 @@ import ONYXKEYS from '../../ONYXKEYS'; import CONST from '../../CONST'; import * as ReportActionUtils from '../ReportActionsUtils'; import * as ReportUtils from '../ReportUtils'; +import {ReportAction} from '../../types/onyx'; -/** - * @param {String} reportID - * @param {Object} reportAction - */ -function clearReportActionErrors(reportID, reportAction) { +function clearReportActionErrors(reportID: string, reportAction: ReportAction) { const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); + if (!reportAction.reportActionID) { + return; + } if (reportAction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { // Delete the optimistic action Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${originalReportID}`, { From e052a02f65675d2a98e1632aa923fa44802194f9 Mon Sep 17 00:00:00 2001 From: Dustin Stringer Date: Fri, 15 Sep 2023 10:34:55 -0400 Subject: [PATCH 059/156] Fix: portion of text shown when changing IOU value --- src/components/TextInput/BaseTextInput.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 9d122fb7ccf1..7487efe4c3eb 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -398,11 +398,10 @@ function BaseTextInput(props) { This Text component is intentionally positioned out of the screen. */} {(props.autoGrow || props.autoGrowHeight) && ( - // Add +2 to width so that the first digit of amount do not cut off on mWeb - https://github.com/Expensify/App/issues/8158. { - setTextInputWidth(e.nativeEvent.layout.width + 2); + setTextInputWidth(e.nativeEvent.layout.width); setTextInputHeight(e.nativeEvent.layout.height); }} > From 8abc7dd30504483df5134224ec98f0e357b6b0a4 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 16 Sep 2023 16:28:19 +0800 Subject: [PATCH 060/156] use different border color for anonymous report footer --- src/components/AvatarWithDisplayName.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index a6c630b775f7..97ef5ac741f5 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -17,6 +17,7 @@ import DisplayNames from './DisplayNames'; import compose from '../libs/compose'; import * as OptionsListUtils from '../libs/OptionsListUtils'; import Text from './Text'; +import * as StyleUtils from '../styles/StyleUtils'; import ParentNavigationSubtitle from './ParentNavigationSubtitle'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Navigation from '../libs/Navigation/Navigation'; @@ -90,6 +91,7 @@ function AvatarWithDisplayName(props) { const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(props.report); const isExpenseRequest = ReportUtils.isExpenseRequest(props.report); const defaultSubscriptSize = isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : props.size; + const avatarBorderColor = props.isAnonymous ? themeColors.highlightBG : themeColors.componentBG; return ( @@ -102,7 +104,7 @@ function AvatarWithDisplayName(props) { > {shouldShowSubscriptAvatar ? ( )} From 55d4bc978260e01089a4325d959d7f81bb70cf48 Mon Sep 17 00:00:00 2001 From: Majid Date: Sat, 16 Sep 2023 22:21:11 +0200 Subject: [PATCH 061/156] Uses policyID to correctly generate bankAccountRout --- src/pages/ReimbursementAccount/BankAccountStep.js | 7 ++++++- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 8e718e193efe..64caf08d4027 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -28,6 +28,7 @@ import ScreenWrapper from '../../components/ScreenWrapper'; import StepPropTypes from './StepPropTypes'; import PressableWithoutFeedback from '../../components/Pressable/PressableWithoutFeedback'; import * as Link from '../../libs/actions/Link'; +import { getBankAccountRoute } from '../../libs/ReportUtils'; const propTypes = { ...StepPropTypes, @@ -49,6 +50,9 @@ const propTypes = { /* The workspace name */ policyName: PropTypes.string, + + /* The workspace ID */ + policyID: PropTypes.string, }; const defaultProps = { @@ -57,6 +61,7 @@ const defaultProps = { user: {}, isPlaidDisabled: false, policyName: '', + policyID: '', }; function BankAccountStep(props) { @@ -66,7 +71,7 @@ function BankAccountStep(props) { subStep = CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID; } const plaidDesktopMessage = getPlaidDesktopMessage(); - const bankAccountRoute = `${CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL}${ROUTES.BANK_ACCOUNT}`; + const bankAccountRoute = `${CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL}${ROUTES.getBankAccountRoute('new', props.policyID, ROUTES.getWorkspaceInitialRoute(props.policyID))}`; if (subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL) { return ( diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 3160ad590c50..a31f9ae8037a 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -421,6 +421,7 @@ class ReimbursementAccountPage extends React.Component { plaidLinkOAuthToken={this.props.plaidLinkToken} getDefaultStateForField={this.getDefaultStateForField} policyName={policyName} + policyID={policyID} /> ); } From e252d750b674bb4ec61de838ca066f3bcc65ea03 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Sun, 17 Sep 2023 22:32:43 +0700 Subject: [PATCH 062/156] fix hightlight button when hovering --- src/components/AvatarCropModal/AvatarCropModal.js | 1 + src/components/DistanceRequest.js | 1 + src/components/MoneyRequestConfirmationList.js | 1 + src/components/PDFView/PDFPasswordForm.js | 1 + 4 files changed, 4 insertions(+) diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index baa958106f84..71cdc350ca38 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -426,6 +426,7 @@ function AvatarCropModal(props) { iconFill={themeColors.inverse} iconStyles={[styles.mr0]} onPress={rotateImage} + shouldUseDefaultHover /> diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index bf5a4cb9548b..f3f1085291d8 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -264,6 +264,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, text={translate('distance.addStop')} isDisabled={numberOfWaypoints === MAX_WAYPOINTS} innerStyles={[styles.ph10]} + shouldUseDefaultHover /> diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index da98d324681e..3c3f788bf240 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -474,6 +474,7 @@ function MoneyRequestConfirmationList(props) { iconRight={Expensicons.DownArrow} iconFill={themeColors.icon} style={styles.mh0} + shouldUseDefaultHover /> diff --git a/src/components/PDFView/PDFPasswordForm.js b/src/components/PDFView/PDFPasswordForm.js index 29e6e2c49ec6..4ac79ab8d8b8 100644 --- a/src/components/PDFView/PDFPasswordForm.js +++ b/src/components/PDFView/PDFPasswordForm.js @@ -139,6 +139,7 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat style={styles.mt4} isLoading={shouldShowLoadingIndicator} pressOnEnter + shouldUseDefaultHover /> ) : ( From 858e8fc03bc8c0fab32b654cdfe70a69a9708dbe Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Mon, 18 Sep 2023 11:22:41 +0200 Subject: [PATCH 063/156] Fix crashing issues --- src/hooks/useForm/FormWrapper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useForm/FormWrapper.js b/src/hooks/useForm/FormWrapper.js index b8d1dc999ef9..9b3159059c59 100644 --- a/src/hooks/useForm/FormWrapper.js +++ b/src/hooks/useForm/FormWrapper.js @@ -61,7 +61,7 @@ const propTypes = { errors: PropTypes.objectOf(PropTypes.string).isRequired, - inputRefs: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(Element)})])).isRequired, + inputRefs: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.element})])).isRequired, }; const defaultProps = { From 14d9d155bbdd3b66780349f9e55a18ac5639f533 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 18 Sep 2023 14:57:19 +0200 Subject: [PATCH 064/156] [TS migration] Migrate 'AddEncryptedAuthToken.js' lib to TypeScript --- src/libs/migrateOnyx.js | 2 - src/libs/migrations/AddEncryptedAuthToken.js | 42 -------------------- 2 files changed, 44 deletions(-) delete mode 100644 src/libs/migrations/AddEncryptedAuthToken.js diff --git a/src/libs/migrateOnyx.js b/src/libs/migrateOnyx.js index 9389a9b66fbc..0bc2f1d3c04b 100644 --- a/src/libs/migrateOnyx.js +++ b/src/libs/migrateOnyx.js @@ -1,6 +1,5 @@ import _ from 'underscore'; import Log from './Log'; -import AddEncryptedAuthToken from './migrations/AddEncryptedAuthToken'; import RenameActiveClientsKey from './migrations/RenameActiveClientsKey'; import RenamePriorityModeKey from './migrations/RenamePriorityModeKey'; import MoveToIndexedDB from './migrations/MoveToIndexedDB'; @@ -19,7 +18,6 @@ export default function () { MoveToIndexedDB, RenameActiveClientsKey, RenamePriorityModeKey, - AddEncryptedAuthToken, RenameExpensifyNewsStatus, AddLastVisibleActionCreated, KeyReportActionsByReportActionID, diff --git a/src/libs/migrations/AddEncryptedAuthToken.js b/src/libs/migrations/AddEncryptedAuthToken.js deleted file mode 100644 index 1c8d6a1e2a43..000000000000 --- a/src/libs/migrations/AddEncryptedAuthToken.js +++ /dev/null @@ -1,42 +0,0 @@ -/* eslint-disable rulesdir/no-api-in-views */ -import _ from 'underscore'; -import Onyx from 'react-native-onyx'; -import Log from '../Log'; -import ONYXKEYS from '../../ONYXKEYS'; -import * as Authentication from '../Authentication'; - -/** - * This migration adds an encryptedAuthToken to the SESSION key, if it is not present. - * - * @returns {Promise} - */ -export default function () { - return new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.SESSION, - callback: (session) => { - Onyx.disconnect(connectionID); - - if (session && !_.isEmpty(session.encryptedAuthToken)) { - Log.info('[Migrate Onyx] Skipped migration AddEncryptedAuthToken'); - return resolve(); - } - - if (!session || !session.authToken) { - Log.info('[Migrate Onyx] Skipped migration AddEncryptedAuthToken'); - return resolve(); - } - - // If there is an auth token but no encrypted auth token, reauthenticate. - if (session.authToken && _.isUndefined(session.encryptedAuthToken)) { - return Authentication.reauthenticate('Onyx_Migration_AddEncryptedAuthToken').then(() => { - Log.info('[Migrate Onyx] Ran migration AddEncryptedAuthToken'); - return resolve(); - }); - } - - return resolve(); - }, - }); - }); -} From 942b3268fca17392e5d71fc73c046c2eb48234ce Mon Sep 17 00:00:00 2001 From: Sam Hariri <137707942+samh-nl@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:45:57 +0200 Subject: [PATCH 065/156] Revert "feat: remove navigation and tooltip when hidden" This reverts commit 19faf7deb3ed7043108a3ad7162fcbd90bb45901. --- .../HTMLRenderers/MentionUserRenderer.js | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js index 155596fb23b0..992f8494dc1f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js @@ -10,7 +10,6 @@ import UserDetailsTooltip from '../../UserDetailsTooltip'; import htmlRendererPropTypes from './htmlRendererPropTypes'; import withCurrentUserPersonalDetails from '../../withCurrentUserPersonalDetails'; import personalDetailsPropType from '../../../pages/personalDetailsPropType'; -import styles from '../../../styles/styles'; import * as StyleUtils from '../../../styles/StyleUtils'; import * as PersonalDetailsUtils from '../../../libs/PersonalDetailsUtils'; import compose from '../../../libs/compose'; @@ -40,13 +39,8 @@ function MentionUserRenderer(props) { if (!_.isEmpty(htmlAttribAccountID)) { const user = lodashGet(props.personalDetails, htmlAttribAccountID); accountID = parseInt(htmlAttribAccountID, 10); - displayNameOrLogin = lodashGet(user, 'login', '') || lodashGet(user, 'displayName', ''); - - if (_.isEmpty(displayNameOrLogin)) { - displayNameOrLogin = translate('common.hidden'); - } else { - navigationRoute = ROUTES.getProfileRoute(htmlAttribAccountID); - } + displayNameOrLogin = lodashGet(user, 'login', '') || lodashGet(user, 'displayName', '') || translate('common.hidden'); + navigationRoute = ROUTES.getProfileRoute(htmlAttribAccountID); } else { // We need to remove the leading @ from data as it is not part of the login displayNameOrLogin = props.tnode.data ? props.tnode.data.slice(1) : ''; @@ -56,33 +50,26 @@ function MentionUserRenderer(props) { } const isOurMention = accountID === props.currentUserPersonalDetails.accountID; - const TextLinkComponent = _.isEmpty(navigationRoute) ? Text : TextLink; return ( - Navigation.navigate(navigationRoute)} + href={`/${navigationRoute}`} + style={[_.omit(props.style, 'color'), StyleUtils.getMentionStyle(isOurMention), {color: StyleUtils.getMentionTextColor(isOurMention)}]} + onPress={() => Navigation.navigate(navigationRoute)} // Add testID so it is NOT selected as an anchor tag by SelectionScraper testID="span" > {!_.isEmpty(htmlAttribAccountID) ? `@${displayNameOrLogin}` : } - + ); From 68c2c92147fece7f5c314123ad683edf825d3ee9 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 18 Sep 2023 15:51:57 +0200 Subject: [PATCH 066/156] ref: resolve comment --- src/libs/actions/ReportActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 9a1e27b010cd..3db23e5c9af7 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -11,6 +11,7 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction) { if (!reportAction.reportActionID) { return; } + if (reportAction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { // Delete the optimistic action Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${originalReportID}`, { From 8e5aba99f32d40c7db244dec9f1bdd6ce5140dfb Mon Sep 17 00:00:00 2001 From: Sam Hariri <137707942+samh-nl@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:47:48 +0200 Subject: [PATCH 067/156] feat: don't render when no account id or email --- .../HTMLRenderers/MentionUserRenderer.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js index 992f8494dc1f..ccace94142d5 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js @@ -41,12 +41,15 @@ function MentionUserRenderer(props) { accountID = parseInt(htmlAttribAccountID, 10); displayNameOrLogin = lodashGet(user, 'login', '') || lodashGet(user, 'displayName', '') || translate('common.hidden'); navigationRoute = ROUTES.getProfileRoute(htmlAttribAccountID); - } else { + } else if (!_.isEmpty(props.tnode.data)) { // We need to remove the leading @ from data as it is not part of the login - displayNameOrLogin = props.tnode.data ? props.tnode.data.slice(1) : ''; + displayNameOrLogin = props.tnode.data.slice(1); accountID = _.first(PersonalDetailsUtils.getAccountIDsByLogins([displayNameOrLogin])); navigationRoute = ROUTES.getDetailsRoute(displayNameOrLogin); + } else { + // If neither an account ID or email is provided, don't render anything + return null; } const isOurMention = accountID === props.currentUserPersonalDetails.accountID; From e5c1033707e2b3b095f10a6251395bbde76a3190 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Tue, 19 Sep 2023 10:15:25 +0700 Subject: [PATCH 068/156] fix web focus border is not disappearing on click --- src/components/BaseMiniContextMenuItem.js | 7 ++++++- src/libs/ReportActionComposeFocusManager.ts | 6 ++++++ src/pages/home/report/ReportActionItemMessageEdit.js | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/BaseMiniContextMenuItem.js b/src/components/BaseMiniContextMenuItem.js index acf5d165d7c7..3070012a9294 100644 --- a/src/components/BaseMiniContextMenuItem.js +++ b/src/components/BaseMiniContextMenuItem.js @@ -8,6 +8,7 @@ import getButtonState from '../libs/getButtonState'; import variables from '../styles/variables'; import Tooltip from './Tooltip'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; +import ReportActionComposeFocusManager from '../libs/ReportActionComposeFocusManager'; const propTypes = { /** @@ -53,7 +54,11 @@ function BaseMiniContextMenuItem(props) { e.preventDefault()} + onMouseDown={(e) => { + if (ReportActionComposeFocusManager.isFocused() || ReportActionComposeFocusManager.isEditFocused()) { + e.preventDefault(); + } + }} accessibilityLabel={props.tooltipText} style={({hovered, pressed}) => [ styles.reportActionContextMenuMiniButton, diff --git a/src/libs/ReportActionComposeFocusManager.ts b/src/libs/ReportActionComposeFocusManager.ts index 83493ed71fc5..0271bd5979ec 100644 --- a/src/libs/ReportActionComposeFocusManager.ts +++ b/src/libs/ReportActionComposeFocusManager.ts @@ -4,6 +4,7 @@ import {TextInput} from 'react-native'; type FocusCallback = () => void; const composerRef = React.createRef(); +const editComposerRef = React.createRef(); // There are two types of composer: general composer (edit composer) and main composer. // The general composer callback will take priority if it exists. let focusCallback: FocusCallback | null = null; @@ -56,6 +57,9 @@ function clear(isMainComposer = false) { function isFocused(): boolean { return !!composerRef.current?.isFocused(); } +function isEditFocused(): boolean { + return !!editComposerRef.current?.isFocused(); +} export default { composerRef, @@ -63,4 +67,6 @@ export default { focus, clear, isFocused, + editComposerRef, + isEditFocused, }; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 43ff5c00a4d5..6ce826a2a34c 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -370,6 +370,7 @@ function ReportActionItemMessageEdit(props) { { + ReportActionComposeFocusManager.editComposerRef.current = el; textInputRef.current = el; // eslint-disable-next-line no-param-reassign props.forwardedRef.current = el; From 927d1a7e536eec7de13a563a044e8ea4d23e1e3c Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Tue, 19 Sep 2023 10:27:20 +0700 Subject: [PATCH 069/156] fix lint issue --- src/components/BaseMiniContextMenuItem.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/BaseMiniContextMenuItem.js b/src/components/BaseMiniContextMenuItem.js index 3070012a9294..64a24f526f54 100644 --- a/src/components/BaseMiniContextMenuItem.js +++ b/src/components/BaseMiniContextMenuItem.js @@ -55,9 +55,11 @@ function BaseMiniContextMenuItem(props) { ref={props.innerRef} onPress={props.onPress} onMouseDown={(e) => { - if (ReportActionComposeFocusManager.isFocused() || ReportActionComposeFocusManager.isEditFocused()) { - e.preventDefault(); + if (!ReportActionComposeFocusManager.isFocused() && !ReportActionComposeFocusManager.isEditFocused()) { + return; } + + e.preventDefault(); }} accessibilityLabel={props.tooltipText} style={({hovered, pressed}) => [ From f5eb8354061e49631d21d2f5266f65ae44cb7507 Mon Sep 17 00:00:00 2001 From: Dustin Stringer Date: Tue, 19 Sep 2023 00:35:39 -0400 Subject: [PATCH 070/156] Fixed rendering of BaseTextInput using autoGrow --- src/components/TextInput/BaseTextInput.js | 5 ++++- src/styles/styles.js | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 0fd2ad935f4c..cce8bda03591 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -399,10 +399,13 @@ function BaseTextInput(props) { This Text component is intentionally positioned out of the screen. */} {(props.autoGrow || props.autoGrowHeight) && ( + // Add +32 to width so that text is not cut off due to the cursor or when changing the value + // https://github.com/Expensify/App/issues/8158 + // https://github.com/Expensify/App/issues/26628 { - setTextInputWidth(e.nativeEvent.layout.width); + setTextInputWidth(e.nativeEvent.layout.width + 32); setTextInputHeight(e.nativeEvent.layout.height); }} > diff --git a/src/styles/styles.js b/src/styles/styles.js index 8ff1731945b2..46240542cede 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2680,6 +2680,9 @@ const styles = (theme) => ({ fontSize: variables.iouAmountTextSize, color: theme.heading, lineHeight: variables.inputHeight, + // This margin counteracts the additional size given to the autoGrow text in BaseTextInput.js + // It fixes issue https://github.com/Expensify/App/issues/26628 + marginLeft: 32 }, iouAmountTextInput: addOutlineWidth( From 9f07209bf9a808ec53e11058150f7e48493a2ffa Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Tue, 19 Sep 2023 10:58:19 +0530 Subject: [PATCH 071/156] trim privateNote value before API call --- src/pages/PrivateNotes/PrivateNotesEditPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.js b/src/pages/PrivateNotes/PrivateNotesEditPage.js index 4cada83941ac..a5b8b137da5f 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.js +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.js @@ -67,7 +67,7 @@ function PrivateNotesEditPage({route, personalDetailsList, session, report}) { const privateNotesInput = useRef(null); const savePrivateNote = () => { - const editedNote = parser.replace(privateNote); + const editedNote = parser.replace(privateNote.trim()); Report.updatePrivateNotes(report.reportID, route.params.accountID, editedNote); Keyboard.dismiss(); From ccbc4063910cf88f2c90c3c9fa0c9a28dcb75b09 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Tue, 19 Sep 2023 15:39:13 +0800 Subject: [PATCH 072/156] Prettier --- src/libs/actions/IOU.js | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 4cc17591aca1..e6af7b1fce05 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -610,21 +610,8 @@ function requestMoney(report, amount, currency, created, merchant, payeeEmail, p // If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report; - const {payerAccountID, payerEmail, iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( - currentChatReport, - participant, - comment, - amount, - currency, - created, - merchant, - payeeAccountID, - payeeEmail, - receipt, - undefined, - category, - billable, - ); + const {payerAccountID, payerEmail, iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = + getMoneyRequestInformation(currentChatReport, participant, comment, amount, currency, created, merchant, payeeAccountID, payeeEmail, receipt, undefined, category, billable); API.write( 'RequestMoney', From 51b93b242fe6840b8d8f68a3ca3fdda9aa9ed6c0 Mon Sep 17 00:00:00 2001 From: Majid Date: Tue, 19 Sep 2023 09:42:16 +0200 Subject: [PATCH 073/156] Removes unused import --- src/pages/ReimbursementAccount/BankAccountStep.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 64caf08d4027..c5a50fde8958 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -28,7 +28,6 @@ import ScreenWrapper from '../../components/ScreenWrapper'; import StepPropTypes from './StepPropTypes'; import PressableWithoutFeedback from '../../components/Pressable/PressableWithoutFeedback'; import * as Link from '../../libs/actions/Link'; -import { getBankAccountRoute } from '../../libs/ReportUtils'; const propTypes = { ...StepPropTypes, From e3a47e470e170b45bd15549c3badae7810609ca4 Mon Sep 17 00:00:00 2001 From: Sam Hariri <137707942+samh-nl@users.noreply.github.com> Date: Tue, 19 Sep 2023 21:27:53 +0200 Subject: [PATCH 074/156] fix: use common function to display rate --- .../MoneyRequestConfirmationList.js | 6 +-- src/libs/DistanceRequestUtils.js | 6 ++- src/libs/PolicyUtils.js | 38 +++++++++++++++++++ .../reimburse/WorkspaceRateAndUnitPage.js | 25 ++---------- .../reimburse/WorkspaceReimburseView.js | 29 ++------------ 5 files changed, 51 insertions(+), 53 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index da4e8d69682a..b085da27a2a6 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -184,7 +184,7 @@ function MoneyRequestConfirmationList(props) { // Destructure functions from props to pass it as a dependecy to useCallback/useMemo hooks. // Prop functions pass props itself as a "this" value to the function which means they change every time props change. const {onSendMoney, onConfirm, onSelectParticipant, transaction} = props; - const {translate} = useLocalize(); + const {translate, toLocaleDigit} = useLocalize(); // A flag and a toggler for showing the rest of the form fields const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); @@ -328,9 +328,9 @@ function MoneyRequestConfirmationList(props) { if (!props.isDistanceRequest) { return; } - const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate, currency, translate); + const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate, currency, translate, toLocaleDigit); IOU.setMoneyRequestMerchant(distanceMerchant); - }, [hasRoute, distance, unit, rate, currency, translate, props.isDistanceRequest]); + }, [hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, props.isDistanceRequest]); /** * @param {Object} option diff --git a/src/libs/DistanceRequestUtils.js b/src/libs/DistanceRequestUtils.js index 34fa14163835..7744ee3d8bb1 100644 --- a/src/libs/DistanceRequestUtils.js +++ b/src/libs/DistanceRequestUtils.js @@ -1,6 +1,7 @@ import _ from 'underscore'; import CONST from '../CONST'; import * as CurrencyUtils from './CurrencyUtils'; +import * as PolicyUtils from './PolicyUtils'; /** * Retrieves the default mileage rate based on a given policy. @@ -79,16 +80,17 @@ const getRoundedDistanceInUnits = (distanceInMeters, unit) => { * @param {Number} rate Expensable amount allowed per unit * @param {String} currency The currency associated with the rate * @param {Function} translate Translate function + * @param {Function} toLocaleDigit Function to convert to localized digit * @returns {String} A string that describes the distance traveled and the rate used for expense calculation */ -const getDistanceMerchant = (hasRoute, distanceInMeters, unit, rate, currency, translate) => { +const getDistanceMerchant = (hasRoute, distanceInMeters, unit, rate, currency, translate, toLocaleDigit) => { const distanceInUnits = hasRoute ? getRoundedDistanceInUnits(distanceInMeters, unit) : translate('common.tbd'); const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); const unitString = distanceInUnits === 1 ? singularDistanceUnit : distanceUnit; - const ratePerUnit = rate * 0.01; + const ratePerUnit = PolicyUtils.getUnitRateValue({rate}, toLocaleDigit); const currencySymbol = CurrencyUtils.getCurrencySymbol(currency) || `${currency} `; return `${distanceInUnits} ${unitString} @ ${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; diff --git a/src/libs/PolicyUtils.js b/src/libs/PolicyUtils.js index 164f284a4ef5..85031c0464eb 100644 --- a/src/libs/PolicyUtils.js +++ b/src/libs/PolicyUtils.js @@ -57,6 +57,42 @@ function hasCustomUnitsError(policy) { return !_.isEmpty(_.pick(lodashGet(policy, 'customUnits', {}), 'errors')); } +/** + * @param {Number} value + * @param {Function} toLocaleDigit + * @returns {Number} + */ +function getNumericValue(value, toLocaleDigit) { + const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')); + if (Number.isNaN(numValue)) { + return NaN; + } + return numValue.toFixed(3); +} + +/** + * @param {Number} value + * @param {Function} toLocaleDigit + * @returns {String} + */ +function getRateDisplayValue(value, toLocaleDigit) { + const numValue = getNumericValue(value, toLocaleDigit); + if (Number.isNaN(numValue)) { + return ''; + } + return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.length); +} + +/** + * @param {Object} customUnitRate + * @param {Number} customUnitRate.rate + * @param {Function} toLocaleDigit + * @returns {String} + */ +function getUnitRateValue(customUnitRate, toLocaleDigit) { + return getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, toLocaleDigit); +} + /** * Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error. * @@ -172,6 +208,8 @@ export { hasPolicyError, hasPolicyErrorFields, hasCustomUnitsError, + getNumericValue, + getUnitRateValue, getPolicyBrickRoadIndicatorStatus, shouldShowPolicy, isExpensifyTeam, diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js index e551e0d6d1b9..fc862234515a 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js @@ -7,6 +7,7 @@ import withLocalize, {withLocalizePropTypes} from '../../../components/withLocal import styles from '../../../styles/styles'; import compose from '../../../libs/compose'; import * as Policy from '../../../libs/actions/Policy'; +import * as PolicyUtils from '../../../libs/PolicyUtils'; import CONST from '../../../CONST'; import Picker from '../../../components/Picker'; import TextInput from '../../../components/TextInput'; @@ -35,10 +36,6 @@ class WorkspaceRateAndUnitPage extends React.Component { this.validate = this.validate.bind(this); } - getUnitRateValue(customUnitRate) { - return this.getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET); - } - getUnitItems() { return [ {label: this.props.translate('workspace.reimburse.kilometers'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS}, @@ -46,22 +43,6 @@ class WorkspaceRateAndUnitPage extends React.Component { ]; } - getRateDisplayValue(value) { - const numValue = this.getNumericValue(value); - if (Number.isNaN(numValue)) { - return ''; - } - return numValue.toString().replace('.', this.props.toLocaleDigit('.')).substring(0, value.length); - } - - getNumericValue(value) { - const numValue = parseFloat(value.toString().replace(',', '.')); - if (Number.isNaN(numValue)) { - return NaN; - } - return numValue.toFixed(3); - } - saveUnitAndRate(unit, rate) { const distanceCustomUnit = _.find(lodashGet(this.props, 'policy.customUnits', {}), (u) => u.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); if (!distanceCustomUnit) { @@ -70,7 +51,7 @@ class WorkspaceRateAndUnitPage extends React.Component { const currentCustomUnitRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (r) => r.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); const unitID = lodashGet(distanceCustomUnit, 'customUnitID', ''); const unitName = lodashGet(distanceCustomUnit, 'name', ''); - const rateNumValue = this.getNumericValue(rate); + const rateNumValue = PolicyUtils.getNumericValue(rate, this.props.toLocaleDigit); const newCustomUnit = { customUnitID: unitID, @@ -137,7 +118,7 @@ class WorkspaceRateAndUnitPage extends React.Component { accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} inputID="rate" containerStyles={[styles.mt4]} - defaultValue={this.getUnitRateValue(distanceCustomRate)} + defaultValue={PolicyUtils.getUnitRateValue(distanceCustomRate, this.props.toLocaleDigit)} label={this.props.translate('workspace.reimburse.trackDistanceRate')} accessibilityLabel={this.props.translate('workspace.reimburse.trackDistanceRate')} placeholder={lodashGet(this.props, 'policy.outputCurrency', CONST.CURRENCY.USD)} diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js index 6a4a3ecc207b..206d627e72bd 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js @@ -16,6 +16,7 @@ import CopyTextToClipboard from '../../../components/CopyTextToClipboard'; import * as Link from '../../../libs/actions/Link'; import compose from '../../../libs/compose'; import * as Policy from '../../../libs/actions/Policy'; +import * as PolicyUtils from '../../../libs/PolicyUtils'; import CONST from '../../../CONST'; import ROUTES from '../../../ROUTES'; import ONYXKEYS from '../../../ONYXKEYS'; @@ -71,39 +72,15 @@ function WorkspaceReimburseView(props) { const distanceCustomRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); const {translate, toLocaleDigit} = props; - const getNumericValue = useCallback( - (value) => { - const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')); - if (Number.isNaN(numValue)) { - return NaN; - } - return numValue.toFixed(3); - }, - [toLocaleDigit], - ); - - const getRateDisplayValue = useCallback( - (value) => { - const numValue = getNumericValue(value); - if (Number.isNaN(numValue)) { - return ''; - } - return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.length); - }, - [getNumericValue, toLocaleDigit], - ); - - const getRateLabel = useCallback((customUnitRate) => getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET), [getRateDisplayValue]); - const getUnitLabel = useCallback((value) => translate(`common.${value}`), [translate]); const getCurrentRatePerUnitLabel = useCallback(() => { const customUnitRate = _.find(lodashGet(distanceCustomUnit, 'rates', '{}'), (rate) => rate && rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); const currentUnit = getUnitLabel(lodashGet(distanceCustomUnit, 'attributes.unit', CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES)); - const currentRate = getRateLabel(customUnitRate); + const currentRate = PolicyUtils.getUnitRateValue(customUnitRate, toLocaleDigit); const perWord = translate('common.per'); return `${currentRate} ${perWord} ${currentUnit}`; - }, [translate, distanceCustomUnit, getUnitLabel, getRateLabel]); + }, [translate, distanceCustomUnit, toLocaleDigit, getUnitLabel]); const fetchData = useCallback(() => { // Instead of setting the reimbursement account loading within the optimistic data of the API command, use a separate action so that the Onyx value is updated right away. From b283e7891741232b2c6bb7bcc670954d9dff85a6 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 20 Sep 2023 08:22:55 +0200 Subject: [PATCH 075/156] fix: remove deprecated getChatByParticipantsByLoginList method --- src/libs/ReportUtils.js | 23 ----------------------- src/libs/actions/Report.js | 7 ++++--- src/libs/actions/Task.js | 2 +- 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8e9cea908f74..6d1e2eda77d3 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2928,28 +2928,6 @@ function getChatByParticipants(newParticipantList) { }); } -/** - * Attempts to find a report in onyx with the provided email list of participants. Does not include threads - * This is temporary function while migrating from PersonalDetails to PersonalDetailsList - * - * @deprecated - use getChatByParticipants() - * - * @param {Array} participantsLoginList - * @returns {Array|undefined} - */ -function getChatByParticipantsByLoginList(participantsLoginList) { - participantsLoginList.sort(); - return _.find(allReports, (report) => { - // If the report has been deleted, or there are no participants (like an empty #admins room) then skip it - if (!report || _.isEmpty(report.participantAccountIDs) || isThread(report)) { - return false; - } - - // Only return the room if it has all the participants and is not a policy room - return !isUserCreatedPolicyRoom(report) && _.isEqual(participantsLoginList, _.sortBy(report.participants)); - }); -} - /** * Attempts to find a report in onyx with the provided list of participants in given policy * @param {Array} newParticipantList @@ -3605,7 +3583,6 @@ export { getOptimisticDataForParentReportAction, shouldReportBeInOptionList, getChatByParticipants, - getChatByParticipantsByLoginList, getChatByParticipantsAndPolicy, getAllPolicyReports, getIOUReportActionMessage, diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 2a34c839a94e..bbc3e6ff854f 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -559,10 +559,11 @@ function openReport(reportID, participantLoginList = [], newReportObject = {}, p */ function navigateToAndOpenReport(userLogins, shouldDismissModal = true) { let newChat = {}; - const formattedUserLogins = _.map(userLogins, (login) => OptionsListUtils.addSMSDomainIfPhoneNumber(login).toLowerCase()); - const chat = ReportUtils.getChatByParticipantsByLoginList(formattedUserLogins); + + const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins(userLogins); + const chat = ReportUtils.getChatByParticipants(participantAccountIDs); + if (!chat) { - const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins(userLogins); newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs); } const reportID = chat ? chat.reportID : newChat.reportID; diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index f1fb4d96f523..bc43c18dadb0 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -641,7 +641,7 @@ function setAssigneeValue(assigneeEmail, assigneeAccountID, shareDestination, is let chatReport; if (!isCurrentUser) { - chatReport = ReportUtils.getChatByParticipantsByLoginList([assigneeEmail]) || ReportUtils.getChatByParticipants([assigneeAccountID]); + chatReport = ReportUtils.getChatByParticipants([assigneeAccountID]); if (!chatReport) { chatReport = ReportUtils.buildOptimisticChatReport([assigneeAccountID]); chatReport.isOptimisticReport = true; From f59327892bf5b83ecb36efdd68aa6555daaf7950 Mon Sep 17 00:00:00 2001 From: Hans Date: Wed, 20 Sep 2023 14:16:04 +0700 Subject: [PATCH 076/156] prevent showing greendot with archived parent report --- src/libs/ReportUtils.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 7c807112613c..dfb2f721b47c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1171,6 +1171,17 @@ function getDisplayNamesWithTooltips(personalDetailsList, isMultipleParticipantR }); } +/** + * Get the report given a reportID + * + * @param {String} reportID + * @returns {Object} + */ +function getReport(reportID) { + // Deleted reports are set to null and lodashGet will still return null in that case, so we need to add an extra check + return lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {}) || {}; +} + /** * 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) * @@ -1182,6 +1193,10 @@ function isWaitingForIOUActionFromCurrentUser(report) { return false; } + if (isArchivedRoom(getReport(report.parentReportID))) { + 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. @@ -1299,17 +1314,6 @@ function getMoneyRequestReportName(report, policy = undefined) { return payerPaidAmountMesssage; } -/** - * Get the report given a reportID - * - * @param {String} reportID - * @returns {Object} - */ -function getReport(reportID) { - // Deleted reports are set to null and lodashGet will still return null in that case, so we need to add an extra check - return lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {}) || {}; -} - /** * Gets transaction created, amount, currency and comment * From 6f1320b6cd8fa888bba1a03793f74c3d36e80c67 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 20 Sep 2023 09:27:22 +0200 Subject: [PATCH 077/156] fix: fix lint error --- src/libs/actions/Report.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index bbc3e6ff854f..4d0147fe7663 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -25,7 +25,6 @@ import * as ErrorUtils from '../ErrorUtils'; import * as UserUtils from '../UserUtils'; import * as Welcome from './Welcome'; import * as PersonalDetailsUtils from '../PersonalDetailsUtils'; -import * as OptionsListUtils from '../OptionsListUtils'; import * as Environment from '../Environment/Environment'; import * as Session from './Session'; From 3f5b81465a22c1795d66eaed9b80fd511e428a68 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 20 Sep 2023 16:03:42 +0800 Subject: [PATCH 078/156] pass missing param, add comment --- src/libs/actions/IOU.js | 2 +- src/pages/iou/ReceiptSelector/index.native.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index e98b17a72ae4..e2351c47bda2 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -2009,7 +2009,7 @@ function navigateToNextPage(iou, iouType, reportID, report, route = '') { resetMoneyRequestInfo(moneyRequestID); } - // If we're adding a receipt, that means the user came from the confirmation page and we navigate back to it. + // If we're adding a receipt, that means the user came from the confirmation page and we need to navigate back to it. if (route.slice(1) === ROUTES.getMoneyRequestReceiptRoute(iouType, reportID)) { Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); return; diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index b3f2033616c0..bd5e620027f5 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -216,7 +216,7 @@ function ReceiptSelector({route, report, iou, transactionID}) { return; } - IOU.navigateToNextPage(iou, iouType, reportID, report, ); + IOU.navigateToNextPage(iou, iouType, reportID, report, route.path); }) .catch((error) => { showCameraAlert(); @@ -229,7 +229,7 @@ function ReceiptSelector({route, report, iou, transactionID}) { }); return ( - + {permissions !== RESULTS.GRANTED && ( Date: Wed, 20 Sep 2023 16:09:18 +0800 Subject: [PATCH 079/156] fix propTypes --- src/pages/EditRequestReceiptPage.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/EditRequestReceiptPage.js b/src/pages/EditRequestReceiptPage.js index 47aa23a93432..8cee632916f0 100644 --- a/src/pages/EditRequestReceiptPage.js +++ b/src/pages/EditRequestReceiptPage.js @@ -21,9 +21,13 @@ const propTypes = { }).isRequired, /** The id of the transaction we're editing */ - transactionID: PropTypes.string.isRequired, + transactionID: PropTypes.string, }; +const defaultProps = { + transactionID: '', +} + function EditRequestReceiptPage({route, transactionID}) { const {translate} = useLocalize(); @@ -47,6 +51,7 @@ function EditRequestReceiptPage({route, transactionID}) { } EditRequestReceiptPage.propTypes = propTypes; +EditRequestReceiptPage.defaultProps = defaultProps; EditRequestReceiptPage.displayName = 'EditRequestReceiptPage'; export default EditRequestReceiptPage; From 8cc320d16bf0412ed1daee048aabf51012684044 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 20 Sep 2023 16:11:14 +0800 Subject: [PATCH 080/156] fix crash --- src/components/MoneyRequestHeader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index fd963a1393fb..61c1cacffefb 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -92,7 +92,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, { icon: Expensicons.Receipt, text: translate('receipt.addReceipt'), - onSelected: () => Navigation.navigate(ROUTES.getEditRequestRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)), + onSelected: () => Navigation.navigate(ROUTES.getEditRequestRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)), }, ]), { From 56a6d2df3ba45c9e2d63a12e1fb95f27025d0d23 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 20 Sep 2023 16:21:13 +0800 Subject: [PATCH 081/156] fix style --- src/pages/EditRequestReceiptPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditRequestReceiptPage.js b/src/pages/EditRequestReceiptPage.js index 8cee632916f0..90409b24e4a4 100644 --- a/src/pages/EditRequestReceiptPage.js +++ b/src/pages/EditRequestReceiptPage.js @@ -26,7 +26,7 @@ const propTypes = { const defaultProps = { transactionID: '', -} +}; function EditRequestReceiptPage({route, transactionID}) { const {translate} = useLocalize(); From fa1ebca31e0031fcc7478bef55f797608cfbb4d5 Mon Sep 17 00:00:00 2001 From: someone-here Date: Wed, 20 Sep 2023 15:48:20 +0530 Subject: [PATCH 082/156] Added policyIDto report while creating task --- src/libs/ReportUtils.js | 3 ++- src/libs/actions/Task.js | 4 ++-- src/pages/tasks/NewTaskPage.js | 10 +++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 2475dd40488c..d5c5356f1ed0 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2646,7 +2646,7 @@ function buildOptimisticWorkspaceChats(policyID, policyName) { * @returns {Object} */ -function buildOptimisticTaskReport(ownerAccountID, assigneeAccountID = 0, parentReportID, title, description) { +function buildOptimisticTaskReport(ownerAccountID, assigneeAccountID = 0, parentReportID, title, description, policyID) { return { reportID: generateReportID(), reportName: title, @@ -2656,6 +2656,7 @@ function buildOptimisticTaskReport(ownerAccountID, assigneeAccountID = 0, parent managerID: assigneeAccountID, type: CONST.REPORT.TYPE.TASK, parentReportID, + policyID, stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS.OPEN, }; diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index f1fb4d96f523..6d1c3386c459 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -60,8 +60,8 @@ function clearOutTaskInfo() { * @param {Number} assigneeAccountID * @param {Object} assigneeChatReport - The chat report between you and the assignee */ -function createTaskAndNavigate(parentReportID, title, description, assigneeEmail, assigneeAccountID = 0, assigneeChatReport = null) { - const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(currentUserAccountID, assigneeAccountID, parentReportID, title, description); +function createTaskAndNavigate(parentReportID, title, description, assigneeEmail, assigneeAccountID = 0, assigneeChatReport = null, policyID = CONST.POLICY.OWNER_ACCOUNT_ID_FAKE) { + const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(currentUserAccountID, assigneeAccountID, parentReportID, title, description, policyID); const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : 0; const taskReportID = optimisticTaskReport.reportID; diff --git a/src/pages/tasks/NewTaskPage.js b/src/pages/tasks/NewTaskPage.js index 9fb600e40753..e4aa0bcb08e1 100644 --- a/src/pages/tasks/NewTaskPage.js +++ b/src/pages/tasks/NewTaskPage.js @@ -126,7 +126,15 @@ function NewTaskPage(props) { return; } - Task.createTaskAndNavigate(parentReport.reportID, props.task.title, props.task.description, props.task.assignee, props.task.assigneeAccountID, props.task.assigneeChatReport); + Task.createTaskAndNavigate( + parentReport.reportID, + props.task.title, + props.task.description, + props.task.assignee, + props.task.assigneeAccountID, + props.task.assigneeChatReport, + parentReport.policyID, + ); } if (!Permissions.canUseTasks(props.betas)) { From 805aca90639d27658265f708fb63e4dafa51a2f0 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Sep 2023 12:47:36 +0200 Subject: [PATCH 083/156] fix: resolve comment --- src/libs/actions/ReportActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 3db23e5c9af7..3faa1dbe3574 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -3,7 +3,7 @@ import ONYXKEYS from '../../ONYXKEYS'; import CONST from '../../CONST'; import * as ReportActionUtils from '../ReportActionsUtils'; import * as ReportUtils from '../ReportUtils'; -import {ReportAction} from '../../types/onyx'; +import ReportAction from '../../types/onyx/ReportAction'; function clearReportActionErrors(reportID: string, reportAction: ReportAction) { const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); From f91c354ba259f226d1859fdf23e5a4d814d28bf4 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Sep 2023 13:02:20 +0200 Subject: [PATCH 084/156] fix: resolve comment --- src/libs/actions/Transaction.ts | 1 - src/types/onyx/Transaction.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 4196fb4c3281..89e7fc06cafa 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -133,7 +133,6 @@ function removeWaypoint(transactionID: string, currentIndex: string) { route0: { distance: null, geometry: { - type: '', coordinates: null, }, }, diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 4df5377d309b..8ed915cea4a4 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -9,11 +9,11 @@ type Comment = { waypoints?: WaypointCollection; }; -type GeometryType = 'LineString' | ''; +type GeometryType = 'LineString'; type Geometry = { coordinates: number[][] | null; - type: GeometryType; + type?: GeometryType; }; type Route = { From 305903975abb64b243b7651c57d82665eccce77b Mon Sep 17 00:00:00 2001 From: someone-here Date: Wed, 20 Sep 2023 16:41:23 +0530 Subject: [PATCH 085/156] Fix Lint --- src/libs/ReportUtils.js | 1 + src/libs/actions/Task.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index d5c5356f1ed0..f85b7beefbb4 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2642,6 +2642,7 @@ function buildOptimisticWorkspaceChats(policyID, policyName) { * @param {String} parentReportID - Report ID of the chat where the Task is. * @param {String} title - Task title. * @param {String} description - Task description. + * @param {Number} policyID - Policy ID of the share destination report. * * @returns {Object} */ diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 6d1c3386c459..5a8cb9917df3 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -59,6 +59,7 @@ function clearOutTaskInfo() { * @param {String} assigneeEmail * @param {Number} assigneeAccountID * @param {Object} assigneeChatReport - The chat report between you and the assignee + * @param {Number} policyID - Policy ID of the share destination report */ function createTaskAndNavigate(parentReportID, title, description, assigneeEmail, assigneeAccountID = 0, assigneeChatReport = null, policyID = CONST.POLICY.OWNER_ACCOUNT_ID_FAKE) { const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(currentUserAccountID, assigneeAccountID, parentReportID, title, description, policyID); From 4f884265545d89d7a0a98fd7d56153dc92779d97 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 20 Sep 2023 18:24:35 +0700 Subject: [PATCH 086/156] fix add comment to function isEditFocused --- src/libs/ReportActionComposeFocusManager.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/ReportActionComposeFocusManager.ts b/src/libs/ReportActionComposeFocusManager.ts index 0271bd5979ec..ca4f9d77898b 100644 --- a/src/libs/ReportActionComposeFocusManager.ts +++ b/src/libs/ReportActionComposeFocusManager.ts @@ -57,6 +57,10 @@ function clear(isMainComposer = false) { function isFocused(): boolean { return !!composerRef.current?.isFocused(); } + +/** + * Exposes the current focus state of the edit message composer. + */ function isEditFocused(): boolean { return !!editComposerRef.current?.isFocused(); } From 7070dfe0471dfa60e862b9adb9946019b3bf6a5e Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 20 Sep 2023 19:12:01 +0700 Subject: [PATCH 087/156] fix edge case clicking on current active emoji --- src/components/BaseMiniContextMenuItem.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/BaseMiniContextMenuItem.js b/src/components/BaseMiniContextMenuItem.js index 64a24f526f54..0e9085b54c17 100644 --- a/src/components/BaseMiniContextMenuItem.js +++ b/src/components/BaseMiniContextMenuItem.js @@ -9,6 +9,7 @@ import variables from '../styles/variables'; import Tooltip from './Tooltip'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import ReportActionComposeFocusManager from '../libs/ReportActionComposeFocusManager'; +import DomUtils from '../libs/DomUtils'; const propTypes = { /** @@ -56,6 +57,7 @@ function BaseMiniContextMenuItem(props) { onPress={props.onPress} onMouseDown={(e) => { if (!ReportActionComposeFocusManager.isFocused() && !ReportActionComposeFocusManager.isEditFocused()) { + DomUtils.getActiveElement().blur(); return; } From 106afb724b24a95018879af5ecfd163c354e1b77 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Wed, 20 Sep 2023 14:52:27 +0200 Subject: [PATCH 088/156] Fix PropTypes errors --- src/hooks/useForm/FormProvider.js | 2 +- src/hooks/useForm/FormWrapper.js | 5 ++--- src/hooks/useForm/InputWrapper.js | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/hooks/useForm/FormProvider.js b/src/hooks/useForm/FormProvider.js index 545916c1d71f..9cbfdd3189a7 100644 --- a/src/hooks/useForm/FormProvider.js +++ b/src/hooks/useForm/FormProvider.js @@ -99,7 +99,7 @@ function getInitialValueByType(valueType) { } function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, children, formState, network, enabledWhenOffline, onSubmit, ...rest}) { - const inputRefs = useRef({}); + const inputRefs = useRef(null); const touchedInputs = useRef({}); const [inputValues, setInputValues] = useState({}); const [errors, setErrors] = useState({}); diff --git a/src/hooks/useForm/FormWrapper.js b/src/hooks/useForm/FormWrapper.js index 9b3159059c59..dc350b78983d 100644 --- a/src/hooks/useForm/FormWrapper.js +++ b/src/hooks/useForm/FormWrapper.js @@ -61,7 +61,7 @@ const propTypes = { errors: PropTypes.objectOf(PropTypes.string).isRequired, - inputRefs: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.element})])).isRequired, + inputRefs: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object])).isRequired, }; const defaultProps = { @@ -77,10 +77,9 @@ const defaultProps = { }; function FormWrapper(props) { + const {onSubmit, children, formState, errors, inputRefs, submitButtonText, footerContent, isSubmitButtonVisible, style, enabledWhenOffline, isSubmitActionDangerous, formID} = props; const formRef = useRef(null); const formContentRef = useRef(null); - const {onSubmit, children, formState, errors, inputRefs, submitButtonText, footerContent, isSubmitButtonVisible, style, enabledWhenOffline, isSubmitActionDangerous, formID} = props; - const errorMessage = useMemo(() => { const latestErrorMessage = ErrorUtils.getLatestErrorMessage(formState); return typeof latestErrorMessage === 'string' ? latestErrorMessage : ''; diff --git a/src/hooks/useForm/InputWrapper.js b/src/hooks/useForm/InputWrapper.js index 83fede5a0a89..3d118fa6f3f1 100644 --- a/src/hooks/useForm/InputWrapper.js +++ b/src/hooks/useForm/InputWrapper.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import FormContext from './FormContext'; const propTypes = { - RenderInput: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired, + RenderInput: PropTypes.oneOfType([PropTypes.func, PropTypes.elementType]).isRequired, inputID: PropTypes.string.isRequired, valueType: PropTypes.string, forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), From 7bdb3755f438c74538823e2fdc84268859b578d2 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 20 Sep 2023 15:55:58 +0200 Subject: [PATCH 089/156] Fix prettier --- src/libs/migrateOnyx.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/libs/migrateOnyx.js b/src/libs/migrateOnyx.js index a74c2a237e53..7394de694de1 100644 --- a/src/libs/migrateOnyx.js +++ b/src/libs/migrateOnyx.js @@ -13,14 +13,7 @@ export default function () { return new Promise((resolve) => { // Add all migrations to an array so they are executed in order - const migrationPromises = [ - MoveToIndexedDB, - RenameActiveClientsKey, - RenamePriorityModeKey, - RenameExpensifyNewsStatus, - AddLastVisibleActionCreated, - PersonalDetailsByAccountID, - ]; + const migrationPromises = [MoveToIndexedDB, RenameActiveClientsKey, RenamePriorityModeKey, RenameExpensifyNewsStatus, AddLastVisibleActionCreated, PersonalDetailsByAccountID]; // Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the // previous promise to finish before moving onto the next one. From 232c6b32d79f7c374e7dcfcf1ea80a12f5aa1d28 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Wed, 20 Sep 2023 21:32:51 +0700 Subject: [PATCH 090/156] Fix issue of workspace distance rate when offline Signed-off-by: Tsaqif --- src/libs/actions/Policy.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 93b46f2e53da..b6fe0975d152 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -714,13 +714,14 @@ function updateWorkspaceCustomUnitAndRate(policyID, currentCustomUnit, newCustom }, ]; + const customUnitRateParam = _.omit(newCustomUnit.rates, ['pendingAction', 'errors']); API.write( 'UpdateWorkspaceCustomUnitAndRate', { policyID, lastModified, customUnit: JSON.stringify(newCustomUnit), - customUnitRate: JSON.stringify(newCustomUnit.rates), + customUnitRate: JSON.stringify(customUnitRateParam), }, {optimisticData, successData, failureData}, ); From fbe8b0d8e91886b588791ad2a7b40c9aaa3b9192 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Wed, 20 Sep 2023 22:01:48 +0700 Subject: [PATCH 091/156] remove space and revert naming variable Signed-off-by: Tsaqif --- src/libs/actions/Policy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index b6fe0975d152..fa12d5d84b2f 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -714,14 +714,14 @@ function updateWorkspaceCustomUnitAndRate(policyID, currentCustomUnit, newCustom }, ]; - const customUnitRateParam = _.omit(newCustomUnit.rates, ['pendingAction', 'errors']); + const newCustomUnitRateParam = _.omit(newCustomUnit.rates, ['pendingAction', 'errors']); API.write( 'UpdateWorkspaceCustomUnitAndRate', { policyID, lastModified, customUnit: JSON.stringify(newCustomUnit), - customUnitRate: JSON.stringify(customUnitRateParam), + customUnitRate: JSON.stringify(newCustomUnitRateParam), }, {optimisticData, successData, failureData}, ); From 573338515ddd8129102c61fca9277cbee96212b3 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Wed, 20 Sep 2023 22:28:19 +0700 Subject: [PATCH 092/156] omiting some fields on customUnit too... Signed-off-by: Tsaqif --- src/libs/actions/Policy.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index fa12d5d84b2f..ce4341fafb76 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -714,13 +714,14 @@ function updateWorkspaceCustomUnitAndRate(policyID, currentCustomUnit, newCustom }, ]; + const newCustomUnitParam = _.omit(newCustomUnit, ['pendingAction', 'errors']); const newCustomUnitRateParam = _.omit(newCustomUnit.rates, ['pendingAction', 'errors']); API.write( 'UpdateWorkspaceCustomUnitAndRate', { policyID, lastModified, - customUnit: JSON.stringify(newCustomUnit), + customUnit: JSON.stringify(newCustomUnitParam), customUnitRate: JSON.stringify(newCustomUnitRateParam), }, {optimisticData, successData, failureData}, From b30d0cb64968f85b340d1e170a4c025c9eedddd6 Mon Sep 17 00:00:00 2001 From: Cristi Paval Date: Wed, 20 Sep 2023 21:58:21 +0300 Subject: [PATCH 093/156] Remove SaaStr and SBE routes from the App. --- src/ROUTES.ts | 4 ---- src/libs/Navigation/linkingConfig.js | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2c37116db395..4f88605785d3 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -210,10 +210,6 @@ export default { getWorkspaceTravelRoute: (policyID: string) => `workspace/${policyID}/travel`, getWorkspaceMembersRoute: (policyID: string) => `workspace/${policyID}/members`, - // These are some on-off routes that will be removed once they're no longer needed (see GH issues for details) - SAASTR: 'saastr', - SBE: 'sbe', - parseReportRouteParams: (route: string): ParseReportRouteParams => { let parsingRoute = route; if (parsingRoute.at(0) === '/') { diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 11d21d6d005c..b68f32be7fbe 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -18,10 +18,6 @@ export default { [SCREENS.DESKTOP_SIGN_IN_REDIRECT]: ROUTES.DESKTOP_SIGN_IN_REDIRECT, [SCREENS.REPORT_ATTACHMENTS]: ROUTES.REPORT_ATTACHMENTS, - // Demo routes - [CONST.DEMO_PAGES.SAASTR]: ROUTES.SAASTR, - [CONST.DEMO_PAGES.SBE]: ROUTES.SBE, - // Sidebar [SCREENS.HOME]: { path: ROUTES.HOME, From 5420435104a2515e87ed61d87844a1a5770cbdc8 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Thu, 21 Sep 2023 06:01:56 +0700 Subject: [PATCH 094/156] clone newCustomUnit... Signed-off-by: Tsaqif --- src/libs/actions/Policy.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index ce4341fafb76..e42ef1ac4823 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -714,15 +714,15 @@ function updateWorkspaceCustomUnitAndRate(policyID, currentCustomUnit, newCustom }, ]; - const newCustomUnitParam = _.omit(newCustomUnit, ['pendingAction', 'errors']); - const newCustomUnitRateParam = _.omit(newCustomUnit.rates, ['pendingAction', 'errors']); + const newCustomUnitParam = _.clone(newCustomUnit); + newCustomUnitParam.rates = _.omit(newCustomUnitParam.rates, ['pendingAction', 'errors']); API.write( 'UpdateWorkspaceCustomUnitAndRate', { policyID, lastModified, customUnit: JSON.stringify(newCustomUnitParam), - customUnitRate: JSON.stringify(newCustomUnitRateParam), + customUnitRate: JSON.stringify(newCustomUnitParam.rates), }, {optimisticData, successData, failureData}, ); From d6c18c8bd8491dca90316aa39f4b7e49a275d9ea Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 21 Sep 2023 10:53:15 +0800 Subject: [PATCH 095/156] only allow attaching receipts to dms --- .../iou/steps/MoneyRequestConfirmPage.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index fe349109af3b..4df2277741e1 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -66,17 +66,17 @@ function MoneyRequestConfirmPage(props) { const prevMoneyRequestId = useRef(props.iou.id); const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType.current, props.selectedTab); - const isManualRequest = props.selectedTab === CONST.TAB.MANUAL; const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); const participants = useMemo( () => - _.map(props.iou.participants, (participant) => { - const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); - return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, props.personalDetails); - }), + _.map(props.iou.participants, (participant) => { + const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); + return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, props.personalDetails); + }), [props.iou.participants, props.personalDetails], - ); - + ); + const isManualRequestDM = props.selectedTab === CONST.TAB.MANUAL && participants.length < 2; + useEffect(() => { const policyExpenseChat = _.find(participants, (participant) => participant.isPolicyExpenseChat); if (policyExpenseChat) { @@ -215,7 +215,7 @@ function MoneyRequestConfirmPage(props) { if (props.iou.receiptPath && props.iou.receiptSource) { FileUtils.readFileAsync(props.iou.receiptPath, props.iou.receiptSource).then((file) => { const receipt = file; - receipt.state = file && isManualRequest ? CONST.IOU.RECEIPT_STATE.OPEN : CONST.IOU.RECEIPT_STATE.SCANREADY; + receipt.state = file && isManualRequestDM ? CONST.IOU.RECEIPT_STATE.OPEN : CONST.IOU.RECEIPT_STATE.SCANREADY; requestMoney(selectedParticipants, trimmedComment, receipt); }); return; @@ -239,7 +239,7 @@ function MoneyRequestConfirmPage(props) { isDistanceRequest, requestMoney, createDistanceRequest, - isManualRequest, + isManualRequestDM, ], ); @@ -285,7 +285,7 @@ function MoneyRequestConfirmPage(props) { Date: Thu, 21 Sep 2023 11:05:29 +0800 Subject: [PATCH 096/156] address comments --- src/libs/ReportUtils.js | 3 ++- src/libs/actions/IOU.js | 6 +++--- src/pages/iou/steps/MoneyRequestConfirmPage.js | 12 ++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 5e144c81d98c..1b861b9d916c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2174,6 +2174,7 @@ function buildOptimisticIOUReportAction( } } + const transaction = TransactionUtils.getTransaction(transactionID); return { actionName: CONST.REPORT.ACTIONS.TYPE.IOU, actorAccountID: currentUserAccountID, @@ -2194,7 +2195,7 @@ function buildOptimisticIOUReportAction( created: DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, receipt, - whisperedToAccountIDs: lodashGet(receipt, 'state', '') === CONST.IOU.RECEIPT_STATE.SCANREADY ? [currentUserAccountID] : [], + whisperedToAccountIDs: TransactionUtils.isReceiptBeingScanned(transaction) ? [currentUserAccountID] : [], }; } /** diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index e2351c47bda2..0165e5c95aaa 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1997,9 +1997,9 @@ function createEmptyTransaction() { * @param {String} iouType * @param {String} reportID * @param {Object} report - * @param {Object} route + * @param {String} path */ -function navigateToNextPage(iou, iouType, reportID, report, route = '') { +function navigateToNextPage(iou, iouType, reportID, report, path = '') { const moneyRequestID = `${iouType}${reportID}`; const shouldReset = iou.id !== moneyRequestID; @@ -2010,7 +2010,7 @@ function navigateToNextPage(iou, iouType, reportID, report, route = '') { } // If we're adding a receipt, that means the user came from the confirmation page and we need to navigate back to it. - if (route.slice(1) === ROUTES.getMoneyRequestReceiptRoute(iouType, reportID)) { + if (path.slice(1) === ROUTES.getMoneyRequestReceiptRoute(iouType, reportID)) { Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); return; } diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 4df2277741e1..64e180636849 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -69,14 +69,14 @@ function MoneyRequestConfirmPage(props) { const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); const participants = useMemo( () => - _.map(props.iou.participants, (participant) => { - const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); - return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, props.personalDetails); - }), + _.map(props.iou.participants, (participant) => { + const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); + return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, props.personalDetails); + }), [props.iou.participants, props.personalDetails], - ); + ); const isManualRequestDM = props.selectedTab === CONST.TAB.MANUAL && participants.length < 2; - + useEffect(() => { const policyExpenseChat = _.find(participants, (participant) => participant.isPolicyExpenseChat); if (policyExpenseChat) { From 665740f0f828a3c319103d8ac8bcd4032a551c63 Mon Sep 17 00:00:00 2001 From: Dustin Stringer Date: Wed, 20 Sep 2023 23:10:34 -0400 Subject: [PATCH 097/156] Removed 32 pixel margin on iouAmountText --- src/styles/styles.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/styles/styles.js b/src/styles/styles.js index 46240542cede..8ff1731945b2 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2680,9 +2680,6 @@ const styles = (theme) => ({ fontSize: variables.iouAmountTextSize, color: theme.heading, lineHeight: variables.inputHeight, - // This margin counteracts the additional size given to the autoGrow text in BaseTextInput.js - // It fixes issue https://github.com/Expensify/App/issues/26628 - marginLeft: 32 }, iouAmountTextInput: addOutlineWidth( From f8550327c3ed20c47a68f8dccc3f1429b0cac665 Mon Sep 17 00:00:00 2001 From: Dustin Stringer Date: Wed, 20 Sep 2023 23:11:15 -0400 Subject: [PATCH 098/156] Added additional autoGrow width for Safari browser --- src/components/TextInput/BaseTextInput.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index cce8bda03591..3fae15a99ee1 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -21,6 +21,7 @@ import isInputAutoFilled from '../../libs/isInputAutoFilled'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import withLocalize from '../withLocalize'; import useNativeDriver from '../../libs/useNativeDriver'; +import * as Browser from '../../libs/Browser'; function BaseTextInput(props) { const inputValue = props.value || props.defaultValue || ''; @@ -399,13 +400,17 @@ function BaseTextInput(props) { This Text component is intentionally positioned out of the screen. */} {(props.autoGrow || props.autoGrowHeight) && ( - // Add +32 to width so that text is not cut off due to the cursor or when changing the value + // Add +2 to width on Safari browsers so that text is not cut off due to the cursor or when changing the value // https://github.com/Expensify/App/issues/8158 // https://github.com/Expensify/App/issues/26628 { - setTextInputWidth(e.nativeEvent.layout.width + 32); + let additionalWidth = 0 + if (Browser.isMobileSafari() || Browser.isSafari()) { + additionalWidth = 2; + } + setTextInputWidth(e.nativeEvent.layout.width + additionalWidth); setTextInputHeight(e.nativeEvent.layout.height); }} > From 2c9910c4f297a1d19cf56206e7a7389b488b4afa Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 21 Sep 2023 11:16:19 +0800 Subject: [PATCH 099/156] use receipt instead of transaction --- src/libs/ReportUtils.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 1b861b9d916c..14ed1053c507 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2174,7 +2174,6 @@ function buildOptimisticIOUReportAction( } } - const transaction = TransactionUtils.getTransaction(transactionID); return { actionName: CONST.REPORT.ACTIONS.TYPE.IOU, actorAccountID: currentUserAccountID, @@ -2195,7 +2194,7 @@ function buildOptimisticIOUReportAction( created: DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, receipt, - whisperedToAccountIDs: TransactionUtils.isReceiptBeingScanned(transaction) ? [currentUserAccountID] : [], + whisperedToAccountIDs: _.contains([CONST.IOU.RECEIPT_STATE.SCANREADY, CONST.IOU.RECEIPT_STATE.SCANNING], receipt.state) ? [currentUserAccountID] : [], }; } /** From c5cc5be814a760c0e5156dbdf060af127c6352b6 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 21 Sep 2023 13:10:51 +0530 Subject: [PATCH 100/156] fix overlay background color for right hand modal --- src/CONST.ts | 2 +- src/styles/colors.js | 2 ++ src/styles/styles.js | 4 ++-- src/styles/themes/default.js | 1 + src/styles/themes/light.js | 1 + 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index dcd5ac1a8db7..e6ef4a3db427 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -123,7 +123,7 @@ const CONST = { }, }, - RIGHT_MODAL_BACKGROUND_OVERLAY_OPACITY: 0.4, + RIGHT_MODAL_BACKGROUND_OVERLAY_OPACITY: 0.72, NEW_EXPENSIFY_URL: ACTIVE_EXPENSIFY_URL, APP_DOWNLOAD_LINKS: { diff --git a/src/styles/colors.js b/src/styles/colors.js index 9ac3226a1b80..3160b7a721cf 100644 --- a/src/styles/colors.js +++ b/src/styles/colors.js @@ -23,6 +23,7 @@ export default { darkDefaultButton: '#184E3D', darkDefaultButtonHover: '#2C6755', darkDefaultButtonPressed: '#467164', + darkRightHandSideOverlay: '#1A3D32', // Light Mode Theme Colors lightAppBackground: '#FCFBF9', @@ -35,6 +36,7 @@ export default { lightDefaultButton: '#EEEBE7', lightDefaultButtonHover: '#E3DFD9', lightDefaultButtonPressed: '#D2CCC3', + lightRightHandSideOverlay: '#EBE6DF', // Brand Colors from Figma blue100: '#B0D9FF', diff --git a/src/styles/styles.js b/src/styles/styles.js index 4a2472913fd2..dd327ab3c00d 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -1554,10 +1554,10 @@ const styles = (theme) => ({ top: 0, bottom: 0, right: 0, - backgroundColor: theme.shadow, + backgroundColor: theme.overlayBG, opacity: current.progress.interpolate({ inputRange: [0, 1], - outputRange: [0, variables.overlayOpacity], + outputRange: [0, CONST.RIGHT_MODAL_BACKGROUND_OVERLAY_OPACITY], extrapolate: 'clamp', }), }), diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js index 5ff997684304..4d066c82c3ff 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.js @@ -82,6 +82,7 @@ const darkTheme = { skeletonLHNOut: colors.darkDefaultButton, QRLogo: colors.green400, starDefaultBG: 'rgb(254, 228, 94)', + overlayBG: colors.darkRightHandSideOverlay, loungeAccessOverlay: colors.blue800, }; diff --git a/src/styles/themes/light.js b/src/styles/themes/light.js index e6ca56d248d6..2d1a6b34b36e 100644 --- a/src/styles/themes/light.js +++ b/src/styles/themes/light.js @@ -80,6 +80,7 @@ const lightTheme = { skeletonLHNOut: colors.lightDefaultButtonPressed, QRLogo: colors.green400, starDefaultBG: 'rgb(254, 228, 94)', + overlayBG: colors.lightRightHandSideOverlay, loungeAccessOverlay: colors.blue800, }; From cf28be0f15c317880734f08c4c020998981cf83e Mon Sep 17 00:00:00 2001 From: someone-here Date: Thu, 21 Sep 2023 15:41:19 +0530 Subject: [PATCH 101/156] Changed default values of policyID --- src/libs/ReportUtils.js | 5 ++--- src/libs/actions/Task.js | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index d25f77b4b3eb..c27f5f0c4b41 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2646,12 +2646,12 @@ function buildOptimisticWorkspaceChats(policyID, policyName) { * @param {String} parentReportID - Report ID of the chat where the Task is. * @param {String} title - Task title. * @param {String} description - Task description. - * @param {String | undefined} policyID - PolicyID of the parent report + * @param {String} policyID - PolicyID of the parent report * * @returns {Object} */ -function buildOptimisticTaskReport(ownerAccountID, assigneeAccountID = 0, parentReportID, title, description, policyID = undefined) { +function buildOptimisticTaskReport(ownerAccountID, assigneeAccountID = 0, parentReportID, title, description, policyID = CONST.POLICY.OWNER_EMAIL_FAKE) { return { reportID: generateReportID(), reportName: title, @@ -2664,7 +2664,6 @@ function buildOptimisticTaskReport(ownerAccountID, assigneeAccountID = 0, parent policyID, stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS.OPEN, - ...(_.isUndefined(policyID) ? {} : {policyID}), }; } diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 4e71a8793f1e..fa6cdcc77706 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -59,9 +59,9 @@ function clearOutTaskInfo() { * @param {String} assigneeEmail * @param {Number} assigneeAccountID * @param {Object} assigneeChatReport - The chat report between you and the assignee - * @param {String | undefined} policyID - the policyID of the parent report + * @param {String} policyID - the policyID of the parent report */ -function createTaskAndNavigate(parentReportID, title, description, assigneeEmail, assigneeAccountID = 0, assigneeChatReport = null, policyID = undefined) { +function createTaskAndNavigate(parentReportID, title, description, assigneeEmail, assigneeAccountID = 0, assigneeChatReport = null, policyID = CONST.POLICY.OWNER_EMAIL_FAKE) { const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(currentUserAccountID, assigneeAccountID, parentReportID, title, description, policyID); const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : 0; From fee898ec5c0ad16077669dca728acd8503ab4648 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Thu, 21 Sep 2023 12:30:30 +0100 Subject: [PATCH 102/156] feat: support for editing a tag of a money request --- src/CONST.ts | 1 + .../ReportActionItem/MoneyRequestView.js | 38 ++++++++++++- src/libs/ReportUtils.js | 11 ++++ src/libs/TransactionUtils.js | 16 ++++++ src/libs/actions/IOU.js | 4 +- src/pages/EditRequestPage.js | 54 ++++++++++++++++--- src/pages/EditRequestTagPage.js | 53 ++++++++++++++++++ 7 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 src/pages/EditRequestTagPage.js diff --git a/src/CONST.ts b/src/CONST.ts index eed1b98ae551..e20ca428ee34 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1360,6 +1360,7 @@ const CONST = { MERCHANT: 'merchant', CATEGORY: 'category', RECEIPT: 'receipt', + TAG: 'tag', }, FOOTER: { EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`, diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 178cab75a0c2..853b998fc5c6 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -1,5 +1,6 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; +import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; import lodashValues from 'lodash/values'; @@ -32,6 +33,7 @@ import * as TransactionUtils from '../../libs/TransactionUtils'; import OfflineWithFeedback from '../OfflineWithFeedback'; import categoryPropTypes from '../categoryPropTypes'; import SpacerView from '../SpacerView'; +import tagPropTypes from '../tagPropTypes'; const propTypes = { /** The report currently being looked at */ @@ -53,6 +55,15 @@ const propTypes = { /** The transaction associated with the transactionThread */ transaction: transactionPropTypes, + /** Collection of tags attached to a policy */ + policyTags: PropTypes.objectOf( + PropTypes.shape({ + name: PropTypes.string, + required: PropTypes.bool, + tags: PropTypes.objectOf(tagPropTypes), + }), + ), + ...withCurrentUserPersonalDetailsPropTypes, }; @@ -65,9 +76,10 @@ const defaultProps = { currency: CONST.CURRENCY.USD, comment: {comment: ''}, }, + policyTags: {}, }; -function MoneyRequestView({betas, report, parentReport, policyCategories, shouldShowHorizontalRule, transaction}) { +function MoneyRequestView({report, betas, parentReport, policyCategories, shouldShowHorizontalRule, transaction, policyTags}) { const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); @@ -80,6 +92,7 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should comment: transactionDescription, merchant: transactionMerchant, category: transactionCategory, + tag: transactionTag, } = ReportUtils.getTransactionDetails(transaction); const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; @@ -89,8 +102,14 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); - // A flag for showing categories + + // Fetches only the first tag, for now + const policyTagKey = _.first(_.keys(policyTags)); + const policyTagsList = lodashGet(policyTags, [policyTagKey, 'tags'], {}); + + // Flags for showing categories and tags const shouldShowCategory = isPolicyExpenseChat && Permissions.canUseCategories(betas) && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); + const shouldShowTag = isPolicyExpenseChat && Permissions.canUseTags(betas) && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagsList))); let description = `${translate('iou.amount')} • ${translate('iou.cash')}`; if (isSettled) { @@ -200,6 +219,18 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should /> )} + {shouldShowTag && ( + + Navigation.navigate(ROUTES.getEditRequestRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))} + /> + + )} `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`, + }, }), )(MoneyRequestView); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index a5af66f08460..4a9eff1e1b60 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1329,6 +1329,7 @@ function getTransactionDetails(transaction) { comment: TransactionUtils.getDescription(transaction), merchant: TransactionUtils.getMerchant(transaction), category: TransactionUtils.getCategory(transaction), + tag: TransactionUtils.getTag(transaction), }; } @@ -1580,6 +1581,11 @@ function getModifiedExpenseMessage(reportAction) { if (hasModifiedCategory) { return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.category, reportActionOriginalMessage.oldCategory, Localize.translateLocal('common.category'), true); } + + const hasModifiedTag = _.has(reportActionOriginalMessage, 'oldTag') && _.has(reportActionOriginalMessage, 'tag'); + if (hasModifiedTag) { + return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.tag, reportActionOriginalMessage.oldTag, Localize.translateLocal('common.tag'), true); + } } /** @@ -1625,6 +1631,11 @@ function getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, i originalMessage.category = transactionChanges.category; } + if (_.has(transactionChanges, 'tag')) { + originalMessage.oldTag = TransactionUtils.getTag(oldTransaction); + originalMessage.tag = transactionChanges.tag; + } + return originalMessage; } diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 5dcfbc467c20..e3fe84ff1af8 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -151,6 +151,10 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep updatedTransaction.category = transactionChanges.category; } + if (_.has(transactionChanges, 'tag')) { + updatedTransaction.tag = transactionChanges.tag; + } + if (shouldStopSmartscan && _.has(transaction, 'receipt') && !_.isEmpty(transaction.receipt) && lodashGet(transaction, 'receipt.state') !== CONST.IOU.RECEIPT_STATE.OPEN) { updatedTransaction.receipt.state = CONST.IOU.RECEIPT_STATE.OPEN; } @@ -162,6 +166,7 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep ...(_.has(transactionChanges, 'currency') && {currency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(_.has(transactionChanges, 'merchant') && {merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(_.has(transactionChanges, 'category') && {category: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(_.has(transactionChanges, 'tag') && {merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }; return updatedTransaction; @@ -253,6 +258,16 @@ function getCategory(transaction) { return lodashGet(transaction, 'category', ''); } +/** + * Return the tag from the transaction. This "tag" field has no "modified" complement. + * + * @param {Object} transaction + * @return {String} + */ +function getTag(transaction) { + return lodashGet(transaction, 'tag', ''); +} + /** * Return the created field from the transaction, return the modifiedCreated if present. * @@ -399,6 +414,7 @@ export { getMerchant, getCreated, getCategory, + getTag, getLinkedTransaction, getAllReportTransactions, hasReceipt, diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 36d512c8d843..e93272cca8f2 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1192,6 +1192,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC currency: null, merchant: null, category: null, + tag: null, }, }, }, @@ -1230,7 +1231,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC ]; // STEP 6: Call the API endpoint - const {created, amount, currency, comment, merchant, category} = ReportUtils.getTransactionDetails(updatedTransaction); + const {created, amount, currency, comment, merchant, category, tag} = ReportUtils.getTransactionDetails(updatedTransaction); API.write( 'EditMoneyRequest', { @@ -1242,6 +1243,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC comment, merchant, category, + tag, }, {optimisticData, successData, failureData}, ); diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index fedbc61a6e15..4fcfa7398d57 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -1,26 +1,29 @@ import React, {useEffect} from 'react'; import PropTypes from 'prop-types'; +import _ from 'underscore'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; -import compose from '../libs/compose'; import CONST from '../CONST'; -import Navigation from '../libs/Navigation/Navigation'; import ONYXKEYS from '../ONYXKEYS'; +import compose from '../libs/compose'; +import Navigation from '../libs/Navigation/Navigation'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import * as ReportUtils from '../libs/ReportUtils'; import * as TransactionUtils from '../libs/TransactionUtils'; import * as Policy from '../libs/actions/Policy'; +import * as IOU from '../libs/actions/IOU'; +import * as CurrencyUtils from '../libs/CurrencyUtils'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '../components/withCurrentUserPersonalDetails'; +import tagPropTypes from '../components/tagPropTypes'; +import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView'; import EditRequestDescriptionPage from './EditRequestDescriptionPage'; import EditRequestMerchantPage from './EditRequestMerchantPage'; import EditRequestCreatedPage from './EditRequestCreatedPage'; import EditRequestAmountPage from './EditRequestAmountPage'; import EditRequestReceiptPage from './EditRequestReceiptPage'; -import reportPropTypes from './reportPropTypes'; -import * as IOU from '../libs/actions/IOU'; -import * as CurrencyUtils from '../libs/CurrencyUtils'; -import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView'; import EditRequestCategoryPage from './EditRequestCategoryPage'; +import EditRequestTagPage from './EditRequestTagPage'; +import reportPropTypes from './reportPropTypes'; const propTypes = { /** Route from navigation */ @@ -35,6 +38,7 @@ const propTypes = { }), }).isRequired, + /** Onyx props */ /** The report object for the thread report */ report: reportPropTypes, @@ -56,6 +60,15 @@ const propTypes = { email: PropTypes.string, }), + /** Collection of tags attached to a policy */ + policyTags: PropTypes.objectOf( + PropTypes.shape({ + name: PropTypes.string, + required: PropTypes.bool, + tags: PropTypes.objectOf(tagPropTypes), + }), + ), + ...withCurrentUserPersonalDetailsPropTypes, }; @@ -66,9 +79,10 @@ const defaultProps = { session: { email: null, }, + policyTags: {}, }; -function EditRequestPage({report, route, parentReport, policy, session}) { +function EditRequestPage({report, route, parentReport, policy, session, policyTags}) { const parentReportAction = ReportActionsUtils.getParentReportAction(report); const transaction = TransactionUtils.getLinkedTransaction(parentReportAction); const { @@ -77,6 +91,7 @@ function EditRequestPage({report, route, parentReport, policy, session}) { comment: transactionDescription, merchant: transactionMerchant, category: transactionCategory, + tag: transactionTag, } = ReportUtils.getTransactionDetails(transaction); const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; @@ -92,6 +107,9 @@ function EditRequestPage({report, route, parentReport, policy, session}) { const isRequestor = ReportUtils.isMoneyRequestReport(parentReport) && lodashGet(session, 'accountID', null) === parentReportAction.actorAccountID; const canEdit = !isSettled && !isDeleted && (isAdmin || isRequestor); + // For now, it always defaults to the first tag of the policy + const tagName = _.first(_.keys(policyTags)); + // Dismiss the modal when the request is paid or deleted useEffect(() => { if (canEdit) { @@ -196,6 +214,25 @@ function EditRequestPage({report, route, parentReport, policy, session}) { ); } + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAG) { + return ( + { + let updatedTag = transactionChanges.tag; + + // In case the same tag has been selected, reset the tag. + if (transactionTag === updatedTag) { + updatedTag = ''; + } + editMoneyRequest({tag: updatedTag}); + }} + /> + ); + } + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) { return ( `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, }, + policyTags: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, + }, }), )(EditRequestPage); diff --git a/src/pages/EditRequestTagPage.js b/src/pages/EditRequestTagPage.js new file mode 100644 index 000000000000..72ed072eec16 --- /dev/null +++ b/src/pages/EditRequestTagPage.js @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Navigation from '../libs/Navigation/Navigation'; +import useLocalize from '../hooks/useLocalize'; +import ScreenWrapper from '../components/ScreenWrapper'; +import HeaderWithBackButton from '../components/HeaderWithBackButton'; +import TagPicker from '../components/TagPicker'; + +const propTypes = { + /** Transaction default tag value */ + defaultTag: PropTypes.string.isRequired, + + /** The policyID we are getting tags for */ + policyID: PropTypes.string.isRequired, + + /** The tag name to which the default tag belongs to */ + tagName: PropTypes.string.isRequired, + + /** Callback to fire when the Save button is pressed */ + onSubmit: PropTypes.func.isRequired, +}; + +function EditRequestTagPage({defaultTag, policyID, tagName, onSubmit}) { + const {translate} = useLocalize(); + + const selectTag = (tag) => { + onSubmit({tag: tag.searchText}); + }; + + return ( + + + + + + ); +} + +EditRequestTagPage.propTypes = propTypes; +EditRequestTagPage.displayName = 'EditRequestTagPage'; + +export default EditRequestTagPage; From 1f2b4b434051ec0d998fe8ea728a55389a9ac1d2 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Thu, 21 Sep 2023 18:38:52 +0700 Subject: [PATCH 103/156] add default shouldUseDefaultHover for button --- src/components/AvatarCropModal/AvatarCropModal.js | 2 +- src/components/Button/index.js | 2 +- src/components/DistanceRequest.js | 1 - src/components/MoneyRequestConfirmationList.js | 1 - src/components/PDFView/PDFPasswordForm.js | 1 - 5 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index 71cdc350ca38..689c95ac0751 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -426,7 +426,7 @@ function AvatarCropModal(props) { iconFill={themeColors.inverse} iconStyles={[styles.mr0]} onPress={rotateImage} - shouldUseDefaultHover + /> diff --git a/src/components/Button/index.js b/src/components/Button/index.js index bfde528a4750..ea1181b7e734 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -145,7 +145,7 @@ const defaultProps = { style: [], innerStyles: [], textStyles: [], - shouldUseDefaultHover: false, + shouldUseDefaultHover: true, success: false, danger: false, children: null, diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index f3f1085291d8..bf5a4cb9548b 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -264,7 +264,6 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, text={translate('distance.addStop')} isDisabled={numberOfWaypoints === MAX_WAYPOINTS} innerStyles={[styles.ph10]} - shouldUseDefaultHover /> diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 3c3f788bf240..da98d324681e 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -474,7 +474,6 @@ function MoneyRequestConfirmationList(props) { iconRight={Expensicons.DownArrow} iconFill={themeColors.icon} style={styles.mh0} - shouldUseDefaultHover /> diff --git a/src/components/PDFView/PDFPasswordForm.js b/src/components/PDFView/PDFPasswordForm.js index 4ac79ab8d8b8..29e6e2c49ec6 100644 --- a/src/components/PDFView/PDFPasswordForm.js +++ b/src/components/PDFView/PDFPasswordForm.js @@ -139,7 +139,6 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat style={styles.mt4} isLoading={shouldShowLoadingIndicator} pressOnEnter - shouldUseDefaultHover /> ) : ( From 450082f899b779a2378bc8acdd2fdfe017c9c474 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Thu, 21 Sep 2023 18:39:51 +0700 Subject: [PATCH 104/156] revert empty change --- src/components/AvatarCropModal/AvatarCropModal.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index 689c95ac0751..baa958106f84 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -426,7 +426,6 @@ function AvatarCropModal(props) { iconFill={themeColors.inverse} iconStyles={[styles.mr0]} onPress={rotateImage} - /> From fe298437d98698583169ca3b380ad1439ee46cc9 Mon Sep 17 00:00:00 2001 From: VH Date: Thu, 21 Sep 2023 19:36:27 +0700 Subject: [PATCH 105/156] Check if current user is isPayer via report object --- src/components/AvatarWithDisplayName.js | 2 +- src/libs/OptionsListUtils.js | 2 +- src/libs/ReportUtils.js | 5 ++--- src/libs/SidebarUtils.js | 2 +- src/libs/actions/Task.js | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index e82dbe05a6d0..4bfcadf69f67 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -85,7 +85,7 @@ function AvatarWithDisplayName(props) { const subtitle = ReportUtils.getChatRoomSubtitle(props.report); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(props.report); const isMoneyRequestOrReport = ReportUtils.isMoneyRequestReport(props.report) || ReportUtils.isMoneyRequest(props.report); - const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policy, true); + const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policy); const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs([props.report.ownerAccountID], props.personalDetails); const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(_.values(ownerPersonalDetails), false); const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(props.report); diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 3bdf77745432..7c36fa095029 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -539,7 +539,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { result.text = reportName; result.searchText = getSearchText(report, reportName, personalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); - result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), false, personalDetail.login, personalDetail.accountID); + result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), personalDetail.login, personalDetail.accountID); result.subtitle = subtitle; return result; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index a5af66f08460..5063bd4b1a49 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -999,13 +999,12 @@ function getWorkspaceIcon(report, policy = undefined) { * @param {Object} report * @param {Object} personalDetails * @param {*} [defaultIcon] - * @param {Boolean} [isPayer] * @param {String} [defaultName] * @param {Number} [defaultAccountID] * @param {Object} [policy] * @returns {Array<*>} */ -function getIcons(report, personalDetails, defaultIcon = null, isPayer = false, defaultName = '', defaultAccountID = -1, policy = undefined) { +function getIcons(report, personalDetails, defaultIcon = null, defaultName = '', defaultAccountID = -1, policy = undefined) { if (_.isEmpty(report)) { const fallbackIcon = { source: defaultIcon || Expensicons.FallbackAvatar, @@ -1093,13 +1092,13 @@ function getIcons(report, personalDetails, defaultIcon = null, isPayer = false, type: CONST.ICON_TYPE_AVATAR, name: lodashGet(personalDetails, [report.managerID, 'displayName'], ''), }; - const ownerIcon = { id: report.ownerAccountID, source: UserUtils.getAvatar(lodashGet(personalDetails, [report.ownerAccountID, 'avatar']), report.ownerAccountID), type: CONST.ICON_TYPE_AVATAR, name: lodashGet(personalDetails, [report.ownerAccountID, 'displayName'], ''), }; + const isPayer = currentUserAccountID === report.managerID; return isPayer ? [managerIcon, ownerIcon] : [ownerIcon, managerIcon]; } diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index f645697690e6..965d969c1332 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -385,7 +385,7 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, result.subtitle = subtitle; result.participantsList = participantPersonalDetailList; - result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), true, '', -1, policy); + result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), '', -1, policy); result.searchText = OptionsListUtils.getSearchText(report, reportName, participantPersonalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); result.displayNamesWithTooltips = displayNamesWithTooltips; result.isLastMessageDeletedParentAction = report.isLastMessageDeletedParentAction; diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 4e71a8793f1e..865f3e287c2d 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -743,7 +743,7 @@ function getShareDestination(reportID, reports, personalDetails) { subtitle = ReportUtils.getChatRoomSubtitle(report); } return { - icons: ReportUtils.getIcons(report, personalDetails, Expensicons.FallbackAvatar, ReportUtils.isIOUReport(report)), + icons: ReportUtils.getIcons(report, personalDetails, Expensicons.FallbackAvatar), displayName: ReportUtils.getReportName(report), subtitle, }; From ce80fef9d00f18f711d8acae7923b41057920fb3 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 21 Sep 2023 14:56:13 +0200 Subject: [PATCH 106/156] Form performance optimisation --- .../useForm => components/Form}/FormContext.js | 0 .../useForm => components/Form}/FormProvider.js | 9 +++++---- .../useForm => components/Form}/FormWrapper.js | 8 ++++---- .../useForm => components/Form}/InputWrapper.js | 6 +++--- src/hooks/useForm/index.js | 11 ----------- src/pages/settings/Profile/DisplayNamePage.js | 17 ++++++++--------- 6 files changed, 20 insertions(+), 31 deletions(-) rename src/{hooks/useForm => components/Form}/FormContext.js (100%) rename src/{hooks/useForm => components/Form}/FormProvider.js (96%) rename src/{hooks/useForm => components/Form}/FormWrapper.js (96%) rename src/{hooks/useForm => components/Form}/InputWrapper.js (78%) delete mode 100644 src/hooks/useForm/index.js diff --git a/src/hooks/useForm/FormContext.js b/src/components/Form/FormContext.js similarity index 100% rename from src/hooks/useForm/FormContext.js rename to src/components/Form/FormContext.js diff --git a/src/hooks/useForm/FormProvider.js b/src/components/Form/FormProvider.js similarity index 96% rename from src/hooks/useForm/FormProvider.js rename to src/components/Form/FormProvider.js index 9cbfdd3189a7..3926680a5eeb 100644 --- a/src/hooks/useForm/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -1,4 +1,4 @@ -import React, {createRef, useCallback, useRef, useState} from 'react'; +import React, {createRef, useCallback, useMemo, useRef, useState} from 'react'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; @@ -8,9 +8,9 @@ import * as FormActions from '../../libs/actions/FormActions'; import FormContext from './FormContext'; import FormWrapper from './FormWrapper'; import compose from '../../libs/compose'; -import {withNetwork} from '../../components/OnyxProvider'; +import {withNetwork} from '../OnyxProvider'; import stylePropTypes from '../../styles/stylePropTypes'; -import networkPropTypes from '../../components/networkPropTypes'; +import networkPropTypes from '../networkPropTypes'; const propTypes = { /** A unique Onyx key identifying the form */ @@ -229,9 +229,10 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c }, [errors, formState, inputValues, onValidate, setTouchedInput, shouldValidateOnBlur, shouldValidateOnChange], ); + const value = useMemo(() => ({registerInput}), [registerInput]); return ( - + {/* eslint-disable react/jsx-props-no-spreading */} ; + return ; } InputWrapper.propTypes = propTypes; diff --git a/src/hooks/useForm/index.js b/src/hooks/useForm/index.js deleted file mode 100644 index faf102e5fd16..000000000000 --- a/src/hooks/useForm/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import {useContext} from 'react'; -import InputWrapper from './InputWrapper'; -import FormContext from './FormContext'; -import FormProvider from './FormProvider'; - -function useForm() { - const formContext = useContext(FormContext); - return {Input: InputWrapper, Form: FormProvider, formContext}; -} - -export default useForm; diff --git a/src/pages/settings/Profile/DisplayNamePage.js b/src/pages/settings/Profile/DisplayNamePage.js index ec8f96cd210e..4b6ae4df80be 100644 --- a/src/pages/settings/Profile/DisplayNamePage.js +++ b/src/pages/settings/Profile/DisplayNamePage.js @@ -16,7 +16,8 @@ import compose from '../../../libs/compose'; import * as ErrorUtils from '../../../libs/ErrorUtils'; import ROUTES from '../../../ROUTES'; import Navigation from '../../../libs/Navigation/Navigation'; -import useForm from '../../../hooks/useForm'; +import FormProvider from "../../../components/Form/FormProvider"; +import InputWrapper from "../../../components/Form/InputWrapper"; const propTypes = { ...withLocalizePropTypes, @@ -64,8 +65,6 @@ function DisplayNamePage(props) { return errors; }; - const {Form, Input} = useForm(); - return ( Navigation.goBack(ROUTES.SETTINGS_PROFILE)} /> -
{props.translate('displayNamePage.isShownOnProfile')} - - -
+
); } From 493f5f6075d3c91fac1457486c917bfc3a6ef2f2 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Thu, 21 Sep 2023 20:28:31 +0700 Subject: [PATCH 107/156] cleanup redundant shouldUseDefaultHover prop --- src/components/ConfirmContent.js | 2 -- src/pages/settings/Wallet/WalletPage/BaseWalletPage.js | 1 - 2 files changed, 3 deletions(-) diff --git a/src/components/ConfirmContent.js b/src/components/ConfirmContent.js index 9a72d4e7d584..20a5275e37e8 100644 --- a/src/components/ConfirmContent.js +++ b/src/components/ConfirmContent.js @@ -133,7 +133,6 @@ function ConfirmContent(props) { style={[styles.mt3, styles.noSelect]} onPress={props.onCancel} text={props.cancelText || translate('common.no')} - shouldUseDefaultHover /> )} @@ -144,7 +143,6 @@ function ConfirmContent(props) { style={[styles.noSelect, styles.flex1]} onPress={props.onCancel} text={props.cancelText || translate('common.no')} - shouldUseDefaultHover medium /> )} diff --git a/src/pages/settings/Wallet/WalletPage/BaseWalletPage.js b/src/pages/settings/Wallet/WalletPage/BaseWalletPage.js index 1393892b6ef0..81ddc068d791 100644 --- a/src/pages/settings/Wallet/WalletPage/BaseWalletPage.js +++ b/src/pages/settings/Wallet/WalletPage/BaseWalletPage.js @@ -443,7 +443,6 @@ function BaseWalletPage(props) { onPress={navigateToAddPaypalRoute} style={[styles.mb4]} text={translate('common.edit')} - shouldUseDefaultHover /> )}