From 8496d9904e37d988c484da17e1c54c7107a4cd34 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Thu, 13 Jul 2023 16:20:23 +0200 Subject: [PATCH 001/110] Handle onBlur in MAgic Code Input component --- src/components/MagicCodeInput.js | 95 +++++++++++++++++--------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index f27b1a2fd0c5..42c517aaecdc 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -1,5 +1,5 @@ import React, {useEffect, useImperativeHandle, useRef, useState, forwardRef} from 'react'; -import {StyleSheet, View} from 'react-native'; +import {StyleSheet, View, Pressable} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import styles from '../styles/styles'; @@ -96,19 +96,21 @@ const composeToString = (value) => _.map(value, (v) => (v === undefined || v === const getInputPlaceholderSlots = (length) => Array.from(Array(length).keys()); function MagicCodeInput(props) { - const inputRefs = useRef([]); + const inputRefs = useRef(); const [input, setInput] = useState(''); const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); + const shouldFocusLast = useRef(false); + const lastFocusedIndex = useRef(0); const blurMagicCodeInput = () => { - inputRefs.current[editIndex].blur(); + inputRefs.current.blur(); setFocusedIndex(undefined); }; const focusMagicCodeInput = () => { setFocusedIndex(0); - inputRefs.current[0].focus(); + inputRefs.current.focus(); }; useImperativeHandle(props.innerRef, () => ({ @@ -123,7 +125,7 @@ function MagicCodeInput(props) { setInput(''); setFocusedIndex(0); setEditIndex(0); - inputRefs.current[0].focus(); + inputRefs.current.focus(); props.onChangeText(''); }, blur() { @@ -158,9 +160,9 @@ function MagicCodeInput(props) { let focusTimeout = null; if (props.shouldDelayFocus) { - focusTimeout = setTimeout(() => inputRefs.current[0].focus(), CONST.ANIMATED_TRANSITION); + focusTimeout = setTimeout(() => inputRefs.current.focus(), CONST.ANIMATED_TRANSITION); } else { - inputRefs.current[0].focus(); + inputRefs.current.focus(); } return () => { @@ -180,7 +182,13 @@ function MagicCodeInput(props) { * @param {Number} index */ const onFocus = (event) => { + if (shouldFocusLast.current) { + setInput(''); + setFocusedIndex(lastFocusedIndex.current); + setEditIndex(lastFocusedIndex.current); + } event.preventDefault(); + shouldFocusLast.current = true; }; /** @@ -190,8 +198,9 @@ function MagicCodeInput(props) { * @param {Object} event * @param {Number} index */ - const onPress = (event, index) => { - event.preventDefault(); + const onPress = (index) => { + shouldFocusLast.current = false; + inputRefs.current.focus(); setInput(''); setFocusedIndex(index); setEditIndex(index); @@ -273,7 +282,7 @@ function MagicCodeInput(props) { props.onChangeText(composeToString(numbers)); if (!_.isUndefined(newFocusedIndex)) { - inputRefs.current[newFocusedIndex].focus(); + inputRefs.current.focus(); } } if (keyValue === 'ArrowLeft' && !_.isUndefined(focusedIndex)) { @@ -281,13 +290,13 @@ function MagicCodeInput(props) { setInput(''); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); - inputRefs.current[newFocusedIndex].focus(); + inputRefs.current.focus(); } else if (keyValue === 'ArrowRight' && !_.isUndefined(focusedIndex)) { const newFocusedIndex = Math.min(focusedIndex + 1, props.maxLength - 1); setInput(''); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); - inputRefs.current[newFocusedIndex].focus(); + inputRefs.current.focus(); } else if (keyValue === 'Enter') { // We should prevent users from submitting when it's offline. if (props.network.isOffline) { @@ -306,9 +315,37 @@ function MagicCodeInput(props) { return ( <> + + (inputRefs.current = ref)} + autoFocus={props.autoFocus && !props.shouldDelayFocus} + inputMode="numeric" + textContentType="oneTimeCode" + name={props.name} + maxLength={props.maxLength} + value={input} + hideFocusedState + autoComplete={props.autoComplete} + keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + onChangeText={(value) => { + onChangeText(value); + }} + onKeyPress={onKeyPress} + onFocus={onFocus} + onBlur={() => { + console.log('blur', focusedIndex); + lastFocusedIndex.current = focusedIndex; + setFocusedIndex(undefined); + }} + caretHidden={isMobileSafari} + inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + /> + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( - onPress(index)} style={[styles.w15]} > {decomposeString(props.value, props.maxLength)[index] || ''} - - (inputRefs.current[index] = ref)} - autoFocus={index === 0 && props.autoFocus && !props.shouldDelayFocus} - inputMode="numeric" - textContentType="oneTimeCode" - name={props.name} - maxLength={props.maxLength} - value={input} - hideFocusedState - autoComplete={index === 0 ? props.autoComplete : 'off'} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} - onChangeText={(value) => { - // Do not run when the event comes from an input that is - // not currently being responsible for the input, this is - // necessary to avoid calls when the input changes due to - // deleted characters. Only happens in mobile. - if (index !== editIndex || _.isUndefined(focusedIndex)) { - return; - } - onChangeText(value); - }} - onKeyPress={onKeyPress} - onPress={(event) => onPress(event, index)} - onFocus={onFocus} - caretHidden={isMobileSafari} - inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - /> - - + ))} {!_.isEmpty(props.errorText) && ( From 077170b6ca6511ba54589516a060943c386e9144 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Wed, 19 Jul 2023 22:05:11 +0200 Subject: [PATCH 002/110] Add TapGestureHandler --- src/components/MagicCodeInput.js | 68 +++++++++++++++++--------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 63b56db5aec0..779b94919bf2 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -13,6 +13,7 @@ import {withNetwork} from './OnyxProvider'; import networkPropTypes from './networkPropTypes'; import useOnNetworkReconnect from '../hooks/useOnNetworkReconnect'; import * as Browser from '../libs/Browser'; +import { TapGestureHandler } from 'react-native-gesture-handler'; const propTypes = { /** Information about the network */ @@ -101,6 +102,7 @@ function MagicCodeInput(props) { const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); const shouldFocusLast = useRef(false); + const inputWidth = useRef(0); const lastFocusedIndex = useRef(0); const blurMagicCodeInput = () => { @@ -317,39 +319,43 @@ function MagicCodeInput(props) { return ( <> - - (inputRefs.current = ref)} - autoFocus={props.autoFocus && !props.shouldDelayFocus} - inputMode="numeric" - textContentType="oneTimeCode" - name={props.name} - maxLength={props.maxLength} - value={input} - hideFocusedState - autoComplete={props.autoComplete} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} - onChangeText={(value) => { - onChangeText(value); + + { + onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))) }} - onKeyPress={onKeyPress} - onFocus={onFocus} - onBlur={() => { - console.log('blur', focusedIndex); - lastFocusedIndex.current = focusedIndex; - setFocusedIndex(undefined); - }} - caretHidden={isMobileSafari} - inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - /> + > + { + inputWidth.current = e.nativeEvent.layout.width; + }} + ref={(ref) => (inputRefs.current = ref)} + autoFocus={props.autoFocus && !props.shouldDelayFocus} + inputMode="numeric" + textContentType="oneTimeCode" + name={props.name} + maxLength={props.maxLength} + value={input} + hideFocusedState + autoComplete={props.autoComplete} + keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + onChangeText={(value) => { + onChangeText(value); + }} + onKeyPress={onKeyPress} + onFocus={onFocus} + onBlur={() => { + lastFocusedIndex.current = focusedIndex; + setFocusedIndex(undefined); + }} + caretHidden={isMobileSafari} + inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + /> + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( - onPress(index)} - style={[styles.w15]} - > + {decomposeString(props.value, props.maxLength)[index] || ''} - + ))} {!_.isEmpty(props.errorText) && ( From 9be387c5be6f38ba615853c2f971be3491f32868 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Fri, 21 Jul 2023 17:50:23 +0200 Subject: [PATCH 003/110] Fix backspace on phones --- src/components/MagicCodeInput.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 779b94919bf2..9414491bbeae 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -236,7 +236,8 @@ function MagicCodeInput(props) { numbers = [...numbers.slice(0, editIndex), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex, props.maxLength)]; setFocusedIndex(updatedFocusedIndex); - setInput(value); + setEditIndex(updatedFocusedIndex); + setInput(''); const finalInput = composeToString(numbers); props.onChangeText(finalInput); From 1751dfd12ff36d78b65935e284acaba9eebcc4cd Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Fri, 21 Jul 2023 18:05:57 +0200 Subject: [PATCH 004/110] Fix autofocus --- src/components/MagicCodeInput.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 9414491bbeae..33fa9c33b837 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -1,7 +1,8 @@ import React, {useEffect, useImperativeHandle, useRef, useState, forwardRef} from 'react'; -import {StyleSheet, View, Pressable} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; +import { TapGestureHandler } from 'react-native-gesture-handler'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import * as ValidationUtils from '../libs/ValidationUtils'; @@ -13,7 +14,6 @@ import {withNetwork} from './OnyxProvider'; import networkPropTypes from './networkPropTypes'; import useOnNetworkReconnect from '../hooks/useOnNetworkReconnect'; import * as Browser from '../libs/Browser'; -import { TapGestureHandler } from 'react-native-gesture-handler'; const propTypes = { /** Information about the network */ @@ -192,14 +192,12 @@ function MagicCodeInput(props) { setEditIndex(lastFocusedIndex.current); } event.preventDefault(); - shouldFocusLast.current = true; }; /** * Callback for the onPress event, updates the indexes * of the currently focused input. * - * @param {Object} event * @param {Number} index */ const onPress = (index) => { @@ -208,6 +206,7 @@ function MagicCodeInput(props) { setInput(''); setFocusedIndex(index); setEditIndex(index); + lastFocusedIndex.current = index; }; /** @@ -346,6 +345,7 @@ function MagicCodeInput(props) { onKeyPress={onKeyPress} onFocus={onFocus} onBlur={() => { + shouldFocusLast.current = true; lastFocusedIndex.current = focusedIndex; setFocusedIndex(undefined); }} From dc9a37fc4aea5583e33bdb2508cdd14ec74369fd Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Mon, 24 Jul 2023 17:19:32 +0200 Subject: [PATCH 005/110] Remove strange line in magic input --- src/components/MagicCodeInput.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 33fa9c33b837..0cf36bee047e 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -15,6 +15,8 @@ import networkPropTypes from './networkPropTypes'; import useOnNetworkReconnect from '../hooks/useOnNetworkReconnect'; import * as Browser from '../libs/Browser'; +const TEXT_INPUT_EMPTY_STATE = ''; + const propTypes = { /** Information about the network */ network: networkPropTypes.isRequired, @@ -98,7 +100,7 @@ const getInputPlaceholderSlots = (length) => Array.from(Array(length).keys()); function MagicCodeInput(props) { const inputRefs = useRef(); - const [input, setInput] = useState(''); + const [input, setInput] = useState(TEXT_INPUT_EMPTY_STATE); const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); const shouldFocusLast = useRef(false); @@ -120,11 +122,11 @@ function MagicCodeInput(props) { focusMagicCodeInput(); }, resetFocus() { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); focusMagicCodeInput(); }, clear() { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(0); setEditIndex(0); inputRefs.current.focus(); @@ -187,7 +189,7 @@ function MagicCodeInput(props) { */ const onFocus = (event) => { if (shouldFocusLast.current) { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(lastFocusedIndex.current); setEditIndex(lastFocusedIndex.current); } @@ -203,7 +205,7 @@ function MagicCodeInput(props) { const onPress = (index) => { shouldFocusLast.current = false; inputRefs.current.focus(); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(index); setEditIndex(index); lastFocusedIndex.current = index; @@ -236,7 +238,7 @@ function MagicCodeInput(props) { setFocusedIndex(updatedFocusedIndex); setEditIndex(updatedFocusedIndex); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); const finalInput = composeToString(numbers); props.onChangeText(finalInput); @@ -257,7 +259,7 @@ function MagicCodeInput(props) { // If the currently focused index already has a value, it will delete // that value but maintain the focus on the same input. if (numbers[focusedIndex] !== CONST.MAGIC_CODE_EMPTY_CHAR) { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); numbers = [...numbers.slice(0, focusedIndex), CONST.MAGIC_CODE_EMPTY_CHAR, ...numbers.slice(focusedIndex + 1, props.maxLength)]; setEditIndex(focusedIndex); props.onChangeText(composeToString(numbers)); @@ -280,7 +282,7 @@ function MagicCodeInput(props) { // Saves the input string so that it can compare to the change text // event that will be triggered, this is a workaround for mobile that // triggers the change text on the event after the key press. - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); props.onChangeText(composeToString(numbers)); @@ -291,13 +293,13 @@ function MagicCodeInput(props) { } if (keyValue === 'ArrowLeft' && !_.isUndefined(focusedIndex)) { const newFocusedIndex = Math.max(0, focusedIndex - 1); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); inputRefs.current.focus(); } else if (keyValue === 'ArrowRight' && !_.isUndefined(focusedIndex)) { const newFocusedIndex = Math.min(focusedIndex + 1, props.maxLength - 1); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); inputRefs.current.focus(); @@ -306,7 +308,7 @@ function MagicCodeInput(props) { if (props.network.isOffline) { return; } - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); props.onFulfill(props.value); } }; @@ -352,6 +354,8 @@ function MagicCodeInput(props) { caretHidden={isMobileSafari} inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + style={[isMobileSafari ? styles.bgTransparent : styles.opacity0]} + textInputContainerStyles={[styles.borderNone]} /> From 0a5ba32ca07644d3650b25fa8cd056e3a5b326de Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Tue, 1 Aug 2023 15:44:29 +0200 Subject: [PATCH 006/110] Add visible wrapper for android --- src/components/MagicCodeInput.js | 79 ++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 38c5ff72bf48..2b7379bb77e7 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -2,7 +2,7 @@ import React, {useEffect, useImperativeHandle, useRef, useState, forwardRef} fro import {StyleSheet, View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; -import { TapGestureHandler } from 'react-native-gesture-handler'; +import {TapGestureHandler} from 'react-native-gesture-handler'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import * as ValidationUtils from '../libs/ValidationUtils'; @@ -321,46 +321,57 @@ function MagicCodeInput(props) { return ( <> - + { - onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))) + onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); }} > - { - inputWidth.current = e.nativeEvent.layout.width; - }} - ref={(ref) => (inputRefs.current = ref)} - autoFocus={props.autoFocus && !props.shouldDelayFocus} - inputMode="numeric" - textContentType="oneTimeCode" - name={props.name} - maxLength={props.maxLength} - value={input} - hideFocusedState - autoComplete={props.autoComplete} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} - onChangeText={(value) => { - onChangeText(value); - }} - onKeyPress={onKeyPress} - onFocus={onFocus} - onBlur={() => { - shouldFocusLast.current = true; - lastFocusedIndex.current = focusedIndex; - setFocusedIndex(undefined); - }} - caretHidden={isMobileSafari} - inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - style={[isMobileSafari ? styles.bgTransparent : styles.opacity0]} - textInputContainerStyles={[styles.borderNone]} - /> + {/* Android does not handle touch on invisible Views so I created wrapper around inivisble View just to handle taps */} + + + { + inputWidth.current = e.nativeEvent.layout.width; + }} + ref={(ref) => (inputRefs.current = ref)} + autoFocus={props.autoFocus && !props.shouldDelayFocus} + inputMode="numeric" + textContentType="oneTimeCode" + name={props.name} + maxLength={props.maxLength} + value={input} + hideFocusedState + autoComplete={props.autoComplete} + keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + onChangeText={(value) => { + onChangeText(value); + }} + onKeyPress={onKeyPress} + onFocus={onFocus} + onBlur={() => { + shouldFocusLast.current = true; + lastFocusedIndex.current = focusedIndex; + setFocusedIndex(undefined); + }} + caretHidden={isMobileSafari} + inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + style={[isMobileSafari ? styles.bgTransparent : styles.opacity0]} + textInputContainerStyles={[styles.borderNone]} + /> + + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( - + Date: Wed, 23 Aug 2023 11:31:52 +0200 Subject: [PATCH 007/110] Remove index from focus --- src/components/MagicCodeInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 6c5e89e7e2a8..8e4b27fef6c2 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -156,7 +156,7 @@ function MagicCodeInput(props) { } // Focus the last input if an error occurred to allow for corrections - inputRefs.current[props.maxLength - 1].focus(); + inputRefs.current.focus(); }, [props.hasError, props.maxLength]); useEffect(() => { From 6ece16c37fc21980370be83d58cbaf52ddab34ef Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Wed, 30 Aug 2023 14:27:39 +0200 Subject: [PATCH 008/110] Reset position after clear --- src/components/MagicCodeInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index b5b4850cf0fa..0879ea8badc4 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -123,6 +123,7 @@ function MagicCodeInput(props) { clear() { setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(0); + lastFocusedIndex.current = 0; setEditIndex(0); inputRefs.current.focus(); props.onChangeText(''); From 43dc30f7f98b608dd98f3c585e1709c81bdfa7ba Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Wed, 30 Aug 2023 17:48:48 +0200 Subject: [PATCH 009/110] Improve usability of input on mobile web browser --- src/components/MagicCodeInput.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 0879ea8badc4..d2a2a7e8ed9c 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -13,6 +13,7 @@ import FormHelpMessage from './FormHelpMessage'; import {withNetwork} from './OnyxProvider'; import networkPropTypes from './networkPropTypes'; import useNetwork from '../hooks/useNetwork'; +import * as Browser from '../libs/Browser'; const TEXT_INPUT_EMPTY_STATE = ''; @@ -187,7 +188,9 @@ function MagicCodeInput(props) { */ const onPress = (index) => { shouldFocusLast.current = false; - inputRefs.current.focus(); + if (!Browser.isMobileChrome()) { + inputRefs.current.focus(); + } setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(index); setEditIndex(index); @@ -301,7 +304,16 @@ function MagicCodeInput(props) { { + if (!Browser.isMobileSafari()) { + return; + } + onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); + }} onBegan={(e) => { + if (Browser.isMobileSafari()) { + return; + } onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); }} > From 1e72e48d2c488db426aa1d623b4a2e6bd77f968a Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Thu, 31 Aug 2023 10:53:37 +0200 Subject: [PATCH 010/110] Make mobile browser code more readable --- src/components/MagicCodeInput.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index d2a2a7e8ed9c..9ec950bee75b 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -188,7 +188,7 @@ function MagicCodeInput(props) { */ const onPress = (index) => { shouldFocusLast.current = false; - if (!Browser.isMobileChrome()) { + if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) { inputRefs.current.focus(); } setInput(TEXT_INPUT_EMPTY_STATE); @@ -304,16 +304,7 @@ function MagicCodeInput(props) { { - if (!Browser.isMobileSafari()) { - return; - } - onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); - }} onBegan={(e) => { - if (Browser.isMobileSafari()) { - return; - } onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); }} > From c21cb051214e36cc1a1427b847d2899713a7997e Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Mon, 4 Sep 2023 16:21:15 +0200 Subject: [PATCH 011/110] Add focus comment --- src/components/MagicCodeInput.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 9ec950bee75b..2cbe8ed2b5f2 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -188,6 +188,8 @@ function MagicCodeInput(props) { */ const onPress = (index) => { shouldFocusLast.current = false; + // TapGestureHandler works differently on mobile web and native app + // On web gesture handelr doesn't block interactions with textInput below so there is no need to run `focus()` manually if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) { inputRefs.current.focus(); } From 4cfd1e03665930e6a774f6d562a6310d1639a8f6 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Tue, 5 Sep 2023 15:10:09 +0200 Subject: [PATCH 012/110] Remove not necessary Views --- src/components/MagicCodeInput.js | 86 +++++++++++++++----------------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 050750b6f59f..32942915fb0a 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -304,52 +304,48 @@ function MagicCodeInput(props) { return ( <> - - { - onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); - }} + { + onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); + }} + > + {/* Android does not handle touch on invisible Views so I created wrapper around inivisble TextInput just to handle taps */} + - {/* Android does not handle touch on invisible Views so I created wrapper around inivisble View just to handle taps */} - - - { - inputWidth.current = e.nativeEvent.layout.width; - }} - ref={(ref) => (inputRefs.current = ref)} - autoFocus={props.autoFocus} - inputMode="numeric" - textContentType="oneTimeCode" - name={props.name} - maxLength={props.maxLength} - value={input} - hideFocusedState - autoComplete={props.autoComplete} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} - onChangeText={(value) => { - onChangeText(value); - }} - onKeyPress={onKeyPress} - onFocus={onFocus} - onBlur={() => { - shouldFocusLast.current = true; - lastFocusedIndex.current = focusedIndex; - setFocusedIndex(undefined); - }} - selectionColor="transparent" - inputStyle={[styles.inputTransparent]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - style={[styles.inputTransparent]} - textInputContainerStyles={[styles.borderNone]} - /> - - - - + { + inputWidth.current = e.nativeEvent.layout.width; + }} + ref={(ref) => (inputRefs.current = ref)} + autoFocus={props.autoFocus} + inputMode="numeric" + textContentType="oneTimeCode" + name={props.name} + maxLength={props.maxLength} + value={input} + hideFocusedState + autoComplete={props.autoComplete} + keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + onChangeText={(value) => { + onChangeText(value); + }} + onKeyPress={onKeyPress} + onFocus={onFocus} + onBlur={() => { + shouldFocusLast.current = true; + lastFocusedIndex.current = focusedIndex; + setFocusedIndex(undefined); + }} + selectionColor="transparent" + inputStyle={[styles.inputTransparent]} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + style={[styles.inputTransparent]} + textInputContainerStyles={[styles.borderNone]} + /> + + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( Date: Tue, 5 Sep 2023 18:46:50 +0200 Subject: [PATCH 013/110] Update src/components/MagicCodeInput.js Co-authored-by: Santhosh Sellavel <85645967+Santhosh-Sellavel@users.noreply.github.com> --- src/components/MagicCodeInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 32942915fb0a..caf21b7dd08f 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -189,7 +189,7 @@ function MagicCodeInput(props) { const onPress = (index) => { shouldFocusLast.current = false; // TapGestureHandler works differently on mobile web and native app - // On web gesture handelr doesn't block interactions with textInput below so there is no need to run `focus()` manually + // On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) { inputRefs.current.focus(); } From 7eba3a9219ad700b6025f27be92a3e96cc661dd4 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 15 Sep 2023 18:01:49 +0700 Subject: [PATCH 014/110] fix: 26945 --- src/pages/iou/MoneyRequestSelectorPage.js | 14 ++++++++++++-- src/pages/iou/steps/MoneyRequestAmountForm.js | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 33353bf5f4a4..8f35ce538a4b 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -1,6 +1,6 @@ import {withOnyx} from 'react-native-onyx'; import {View} from 'react-native'; -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import ONYXKEYS from '../../ONYXKEYS'; @@ -22,6 +22,7 @@ import NewRequestAmountPage from './steps/NewRequestAmountPage'; import reportPropTypes from '../reportPropTypes'; import * as ReportUtils from '../../libs/ReportUtils'; import themeColors from '../../styles/themes/default'; +import usePrevious from '../../hooks/usePrevious'; const propTypes = { /** React Navigation route */ @@ -69,6 +70,16 @@ function MoneyRequestSelectorPage(props) { IOU.resetMoneyRequestInfo(moneyRequestID); }; + const prevSelectedTab = usePrevious(props.selectedTab); + + useEffect(() => { + if (prevSelectedTab === props.selectedTab) { + return; + } + + resetMoneyRequestInfo(); + }, [props.selectedTab, prevSelectedTab]); + return ( )} diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.js index 1ea0b002b235..70e6902067a7 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.js +++ b/src/pages/iou/steps/MoneyRequestAmountForm.js @@ -100,7 +100,7 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu }; useEffect(() => { - if (!currency || !amount) { + if (!currency || !_.isNumber(amount)) { return; } const amountAsStringForState = CurrencyUtils.convertToFrontendAmount(amount).toString(); From 4e9e40d959af817d302413ebfca5530c53e0ba89 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 15 Sep 2023 15:30:23 +0200 Subject: [PATCH 015/110] Add types --- src/libs/actions/Device/getDeviceInfo/getOSAndName/types.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/libs/actions/Device/getDeviceInfo/getOSAndName/types.ts diff --git a/src/libs/actions/Device/getDeviceInfo/getOSAndName/types.ts b/src/libs/actions/Device/getDeviceInfo/getOSAndName/types.ts new file mode 100644 index 000000000000..6616a13d8d23 --- /dev/null +++ b/src/libs/actions/Device/getDeviceInfo/getOSAndName/types.ts @@ -0,0 +1,4 @@ +// to keep same behavior from before migration +// eslint-disable-next-line @typescript-eslint/naming-convention +type GetOSAndName = () => {device_name: string | undefined; os_version: string | undefined}; +export default GetOSAndName; From 807a802b29b8202757afc6326b1d5b56b9a9f584 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 15 Sep 2023 16:23:13 +0200 Subject: [PATCH 016/110] Migrate getOSAndName to typescript --- .../actions/Device/getDeviceInfo/getOSAndName/index.js | 4 ---- .../getOSAndName/{index.native.js => index.native.ts} | 9 +++++++-- .../actions/Device/getDeviceInfo/getOSAndName/index.ts | 10 ++++++++++ 3 files changed, 17 insertions(+), 6 deletions(-) delete mode 100644 src/libs/actions/Device/getDeviceInfo/getOSAndName/index.js rename src/libs/actions/Device/getDeviceInfo/getOSAndName/{index.native.js => index.native.ts} (61%) create mode 100644 src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts diff --git a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.js b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.js deleted file mode 100644 index 29b004412f64..000000000000 --- a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.js +++ /dev/null @@ -1,4 +0,0 @@ -// Don't import this file with '* as Device'. It's known to make VSCode IntelliSense crash. -import {getOSAndName} from 'expensify-common/lib/Device'; - -export default getOSAndName; diff --git a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.js b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts similarity index 61% rename from src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.js rename to src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts index 11d59abea1f1..455487d056f2 100644 --- a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.js +++ b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts @@ -1,11 +1,16 @@ import Str from 'expensify-common/lib/str'; import RNDeviceInfo from 'react-native-device-info'; +import GetOSAndName from './types'; -export default function getOSAndName() { +const getOSAndName: GetOSAndName = () => { const deviceName = RNDeviceInfo.getDeviceNameSync(); const prettyName = `${Str.UCFirst(RNDeviceInfo.getManufacturerSync() || '')} ${deviceName}`; return { + // eslint-disable-next-line @typescript-eslint/naming-convention device_name: RNDeviceInfo.isEmulatorSync() ? `Emulator - ${prettyName}` : prettyName, + // eslint-disable-next-line @typescript-eslint/naming-convention os_version: RNDeviceInfo.getSystemVersion(), }; -} +}; + +export default getOSAndName; diff --git a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts new file mode 100644 index 000000000000..5155fc07afb2 --- /dev/null +++ b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts @@ -0,0 +1,10 @@ +import {getOSAndName as libGetOSAndName} from 'expensify-common/lib/Device'; +import GetOSAndName from './types'; + +const getOSAndName: GetOSAndName = () => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const {device_name, os_version} = libGetOSAndName(); + // eslint-disable-next-line @typescript-eslint/naming-convention + return {device_name, os_version}; +}; +export default getOSAndName; From d2132f88e686348114a6423093e02e7bedb2dac3 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 18 Sep 2023 12:58:27 +0200 Subject: [PATCH 017/110] [TS migration] Migrate 'ErrorUtils.js' lib to TypeScript --- src/libs/ErrorUtils.js | 135 ------------------------------------- src/libs/ErrorUtils.ts | 113 +++++++++++++++++++++++++++++++ src/types/onyx/Response.ts | 3 +- 3 files changed, 115 insertions(+), 136 deletions(-) delete mode 100644 src/libs/ErrorUtils.js create mode 100644 src/libs/ErrorUtils.ts diff --git a/src/libs/ErrorUtils.js b/src/libs/ErrorUtils.js deleted file mode 100644 index 95bbad5f5409..000000000000 --- a/src/libs/ErrorUtils.js +++ /dev/null @@ -1,135 +0,0 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; -import CONST from '../CONST'; -import DateUtils from './DateUtils'; -import * as Localize from './Localize'; - -/** - * @param {Object} response - * @param {Number} response.jsonCode - * @param {String} response.message - * @returns {String} - */ -function getAuthenticateErrorMessage(response) { - switch (response.jsonCode) { - case CONST.JSON_CODE.UNABLE_TO_RETRY: - return 'session.offlineMessageRetry'; - case 401: - return 'passwordForm.error.incorrectLoginOrPassword'; - case 402: - // If too few characters are passed as the password, the WAF will pass it to the API as an empty - // string, which results in a 402 error from Auth. - if (response.message === '402 Missing partnerUserSecret') { - return 'passwordForm.error.incorrectLoginOrPassword'; - } - return 'passwordForm.error.twoFactorAuthenticationEnabled'; - case 403: - if (response.message === 'Invalid code') { - return 'passwordForm.error.incorrect2fa'; - } - return 'passwordForm.error.invalidLoginOrPassword'; - case 404: - return 'passwordForm.error.unableToResetPassword'; - case 405: - return 'passwordForm.error.noAccess'; - case 413: - return 'passwordForm.error.accountLocked'; - default: - return 'passwordForm.error.fallback'; - } -} - -/** - * Method used to get an error object with microsecond as the key. - * @param {String} error - error key or message to be saved - * @return {Object} - * - */ -function getMicroSecondOnyxError(error) { - return {[DateUtils.getMicroseconds()]: error}; -} - -/** - * @param {Object} onyxData - * @param {Object} onyxData.errors - * @returns {String} - */ -function getLatestErrorMessage(onyxData) { - if (_.isEmpty(onyxData.errors)) { - return ''; - } - return _.chain(onyxData.errors || []) - .keys() - .sortBy() - .reverse() - .map((key) => onyxData.errors[key]) - .first() - .value(); -} - -/** - * @param {Object} onyxData - * @param {Object} onyxData.errorFields - * @param {String} fieldName - * @returns {Object} - */ -function getLatestErrorField(onyxData, fieldName) { - const errorsForField = lodashGet(onyxData, ['errorFields', fieldName], {}); - - if (_.isEmpty(errorsForField)) { - return {}; - } - return _.chain(errorsForField) - .keys() - .sortBy() - .reverse() - .map((key) => ({[key]: errorsForField[key]})) - .first() - .value(); -} - -/** - * @param {Object} onyxData - * @param {Object} onyxData.errorFields - * @param {String} fieldName - * @returns {Object} - */ -function getEarliestErrorField(onyxData, fieldName) { - const errorsForField = lodashGet(onyxData, ['errorFields', fieldName], {}); - - if (_.isEmpty(errorsForField)) { - return {}; - } - return _.chain(errorsForField) - .keys() - .sortBy() - .map((key) => ({[key]: errorsForField[key]})) - .first() - .value(); -} - -/** - * Method used to generate error message for given inputID - * @param {Object} errors - An object containing current errors in the form - * @param {String} inputID - * @param {String|Array} message - Message to assign to the inputID errors - * - */ -function addErrorMessage(errors, inputID, message) { - if (!message || !inputID) { - return; - } - - const errorList = errors; - const translatedMessage = Localize.translateIfPhraseKey(message); - - if (_.isEmpty(errorList[inputID])) { - errorList[inputID] = [translatedMessage, {isTranslated: true}]; - } else if (_.isString(errorList[inputID])) { - errorList[inputID] = [`${errorList[inputID]}\n${translatedMessage}`, {isTranslated: true}]; - } else { - errorList[inputID][0] = `${errorList[inputID][0]}\n${translatedMessage}`; - } -} - -export {getAuthenticateErrorMessage, getMicroSecondOnyxError, getLatestErrorMessage, getLatestErrorField, getEarliestErrorField, addErrorMessage}; diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts new file mode 100644 index 000000000000..899a319f3c91 --- /dev/null +++ b/src/libs/ErrorUtils.ts @@ -0,0 +1,113 @@ +import CONST from '../CONST'; +import DateUtils from './DateUtils'; +import * as Localize from './Localize'; +import Response from '../types/onyx/Response'; +import {ErrorFields} from '../types/onyx/OnyxCommon'; + +function getAuthenticateErrorMessage(response: Response): string { + switch (response.jsonCode) { + case CONST.JSON_CODE.UNABLE_TO_RETRY: + return 'session.offlineMessageRetry'; + case 401: + return 'passwordForm.error.incorrectLoginOrPassword'; + case 402: + // If too few characters are passed as the password, the WAF will pass it to the API as an empty + // string, which results in a 402 error from Auth. + if (response.message === '402 Missing partnerUserSecret') { + return 'passwordForm.error.incorrectLoginOrPassword'; + } + return 'passwordForm.error.twoFactorAuthenticationEnabled'; + case 403: + if (response.message === 'Invalid code') { + return 'passwordForm.error.incorrect2fa'; + } + return 'passwordForm.error.invalidLoginOrPassword'; + case 404: + return 'passwordForm.error.unableToResetPassword'; + case 405: + return 'passwordForm.error.noAccess'; + case 413: + return 'passwordForm.error.accountLocked'; + default: + return 'passwordForm.error.fallback'; + } +} + +/** + * Method used to get an error object with microsecond as the key. + * @param error - error key or message to be saved + */ +function getMicroSecondOnyxError(error: string): Record { + return {[DateUtils.getMicroseconds()]: error}; +} + +type OnyxDataWithErrors = { + errors: Record; +}; + +function getLatestErrorMessage(onyxData: TOnyxData): string { + if (Object.keys(onyxData.errors).length === 0) { + return ''; + } + + const key = Object.keys(onyxData.errors ?? {}) + .sort() + .reverse()[0]; + + return onyxData.errors[key]; +} + +type OnyxDataWithErrorFields = { + errorFields: ErrorFields; +}; + +function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Record { + const errorsForField = onyxData.errorFields[fieldName] ?? {}; + + if (Object.keys(errorsForField).length === 0) { + return {}; + } + + const key = Object.keys(errorsForField).sort().reverse()[0]; + + return {[key]: errorsForField[key]}; +} + +function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Record { + const errorsForField = onyxData.errorFields[fieldName] ?? {}; + + if (Object.keys(errorsForField).length === 0) { + return {}; + } + + const key = Object.keys(errorsForField).sort()[0]; + + return {[key]: errorsForField[key]}; +} + +type ErrorsList = Record; + +/** + * Method used to generate error message for given inputID + * @param errorList - An object containing current errors in the form + * @param message - Message to assign to the inputID errors + */ +function addErrorMessage(errors: ErrorsList, inputID?: string, message?: string) { + if (!message || !inputID) { + return; + } + + const errorList = errors; + const error = errorList[inputID]; + const translatedMessage = Localize.translateIfPhraseKey(message); + + if (!error) { + errorList[inputID] = [translatedMessage, {isTranslated: true}]; + } else if (typeof error === 'string') { + errorList[inputID] = [`${error}\n${translatedMessage}`, {isTranslated: true}]; + } else if (Array.isArray(error)) { + error[0] = `${error[0]}\n${translatedMessage}`; + } +} + +export {getAuthenticateErrorMessage, getMicroSecondOnyxError, getLatestErrorMessage, getLatestErrorField, getEarliestErrorField, addErrorMessage}; diff --git a/src/types/onyx/Response.ts b/src/types/onyx/Response.ts index c501034e971c..255ac6d9bae4 100644 --- a/src/types/onyx/Response.ts +++ b/src/types/onyx/Response.ts @@ -3,9 +3,10 @@ import {OnyxUpdate} from 'react-native-onyx'; type Response = { previousUpdateID?: number | string; lastUpdateID?: number | string; - jsonCode?: number; + jsonCode?: number | string; onyxData?: OnyxUpdate[]; requestID?: string; + message?: string; }; export default Response; From 3baa2e62a8b8fc6712a33e6cb889754fad8eef52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 19 Sep 2023 13:33:28 +0800 Subject: [PATCH 018/110] Prevent nested timezone setting --- src/libs/Navigation/AppNavigator/AuthScreens.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index e0197805f09c..b446bad03c4a 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -69,8 +69,10 @@ Onyx.connect({ // If the current timezone is different than the user's timezone, and their timezone is set to automatic // then update their timezone. if (_.isObject(timezone) && timezone.automatic && timezone.selected !== currentTimezone) { - timezone.selected = currentTimezone; - PersonalDetails.updateAutomaticTimezone(timezone); + PersonalDetails.updateAutomaticTimezone({ + automatic: true, + selected: currentTimezone + }); } }, }); From b424d0abb46b42a6f9e57d4718d47fe20147b79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 19 Sep 2023 14:27:34 +0800 Subject: [PATCH 019/110] fix prettier --- src/libs/Navigation/AppNavigator/AuthScreens.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index b446bad03c4a..50d27094e32b 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -71,7 +71,7 @@ Onyx.connect({ if (_.isObject(timezone) && timezone.automatic && timezone.selected !== currentTimezone) { PersonalDetails.updateAutomaticTimezone({ automatic: true, - selected: currentTimezone + selected: currentTimezone, }); } }, From 97320a0abbc04bfdd5395db4e0ca7d0de2f5d1ff Mon Sep 17 00:00:00 2001 From: s-alves10 Date: Tue, 19 Sep 2023 05:06:29 -0500 Subject: [PATCH 020/110] fix: use of measureParentContainer instead of parentContainerRef --- .../BaseAutoCompleteSuggestions.js | 2 +- src/components/AutoCompleteSuggestions/index.js | 16 +++++++--------- src/styles/StyleUtils.ts | 6 ++---- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js index b4710f1f343e..40e08d876907 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js @@ -70,7 +70,7 @@ function BaseAutoCompleteSuggestions(props) { }); const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * props.suggestions.length; - const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, props.shouldIncludeReportRecipientLocalTimeHeight)); + const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value)); useEffect(() => { rowHeight.value = withTiming(measureHeightOfSuggestionRows(props.suggestions.length, props.isSuggestionPickerLarge), { diff --git a/src/components/AutoCompleteSuggestions/index.js b/src/components/AutoCompleteSuggestions/index.js index b37fcd7181d9..54a2b7dad1fd 100644 --- a/src/components/AutoCompleteSuggestions/index.js +++ b/src/components/AutoCompleteSuggestions/index.js @@ -14,7 +14,7 @@ import useWindowDimensions from '../../hooks/useWindowDimensions'; * On the native platform, tapping on auto-complete suggestions will not blur the main input. */ -function AutoCompleteSuggestions({parentContainerRef, ...props}) { +function AutoCompleteSuggestions({measureParentContainer, ...props}) { const containerRef = React.useRef(null); const {windowHeight, windowWidth} = useWindowDimensions(); const [{width, left, bottom}, setContainerState] = React.useState({ @@ -37,11 +37,11 @@ function AutoCompleteSuggestions({parentContainerRef, ...props}) { }, []); React.useEffect(() => { - if (!parentContainerRef || !parentContainerRef.current) { + if (!measureParentContainer) { return; } - parentContainerRef.current.measureInWindow((x, y, w) => setContainerState({left: x, bottom: windowHeight - y, width: w})); - }, [parentContainerRef, windowHeight, windowWidth]); + measureParentContainer((x, y, w) => setContainerState({left: x, bottom: windowHeight - y, width: w})); + }, [measureParentContainer, windowHeight, windowWidth]); const componentToRender = ( ); - if (!width) { - return componentToRender; - } - - return ReactDOM.createPortal({componentToRender}, document.querySelector('body')); + return Boolean(width) && ( + ReactDOM.createPortal({componentToRender}, document.querySelector('body')) + ); } AutoCompleteSuggestions.propTypes = propTypes; diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index ec06bb07c3fe..6dd023f634de 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -935,11 +935,9 @@ function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: {lef /** * Gets the correct position for auto complete suggestion container */ -function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldIncludeReportRecipientLocalTimeHeight: boolean): ViewStyle | CSSProperties { +function getAutoCompleteSuggestionContainerStyle(itemsHeight: number): ViewStyle | CSSProperties { 'worklet'; - const optionalPadding = shouldIncludeReportRecipientLocalTimeHeight ? CONST.RECIPIENT_LOCAL_TIME_HEIGHT : 0; - const padding = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + optionalPadding; const borderWidth = 2; const height = itemsHeight + 2 * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING + borderWidth; @@ -947,7 +945,7 @@ function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldIncl // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. return { overflow: 'hidden', - top: -(height + padding), + top: -(height + CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING), height, }; } From c904b6d42493fc46d92c2de6d60eee8046e974fb Mon Sep 17 00:00:00 2001 From: s-alves10 Date: Tue, 19 Sep 2023 05:09:40 -0500 Subject: [PATCH 021/110] fix: use portal for native platforms --- src/components/AutoCompleteSuggestions/index.native.js | 5 +++-- .../home/report/ReportActionCompose/ReportActionCompose.js | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/index.native.js b/src/components/AutoCompleteSuggestions/index.native.js index 514cec6cd844..588f44809a3b 100644 --- a/src/components/AutoCompleteSuggestions/index.native.js +++ b/src/components/AutoCompleteSuggestions/index.native.js @@ -1,10 +1,11 @@ import React from 'react'; +import {Portal} from '@gorhom/portal'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import {propTypes} from './autoCompleteSuggestionsPropTypes'; -function AutoCompleteSuggestions({parentContainerRef, ...props}) { +function AutoCompleteSuggestions({measureParentContainer, ...props}) { // eslint-disable-next-line react/jsx-props-no-spreading - return ; + return } AutoCompleteSuggestions.propTypes = propTypes; diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index ddcd43cd8cd0..e872dc688dd5 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -5,6 +5,7 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import {useAnimatedRef} from 'react-native-reanimated'; +import {PortalHost} from '@gorhom/portal'; import styles from '../../../../styles/styles'; import ONYXKEYS from '../../../../ONYXKEYS'; import * as Report from '../../../../libs/actions/Report'; @@ -325,6 +326,7 @@ function ReportActionCompose({ ref={containerRef} style={[shouldShowReportRecipientLocalTime && !lodashGet(network, 'isOffline') && styles.chatItemComposeWithFirstRow, isComposerFullSize && styles.chatItemFullComposeRow]} > + Date: Tue, 19 Sep 2023 13:23:27 +0200 Subject: [PATCH 022/110] ref: moved Chronos lib to TS and add export type needed in Chronos lib --- src/libs/actions/{Chronos.js => Chronos.ts} | 9 ++------- src/types/onyx/OriginalMessage.ts | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) rename src/libs/actions/{Chronos.js => Chronos.ts} (89%) diff --git a/src/libs/actions/Chronos.js b/src/libs/actions/Chronos.ts similarity index 89% rename from src/libs/actions/Chronos.js rename to src/libs/actions/Chronos.ts index b9c0eed7b354..bf758128714f 100644 --- a/src/libs/actions/Chronos.js +++ b/src/libs/actions/Chronos.ts @@ -3,14 +3,9 @@ import Onyx from 'react-native-onyx'; import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API'; +import {ChronosOOOEvent} from '../../types/onyx/OriginalMessage'; -/** - * @param {String} reportID - * @param {String} reportActionID - * @param {String} eventID - * @param {Object[]} events - */ -const removeEvent = (reportID, reportActionID, eventID, events) => { +const removeEvent = (reportID: string, reportActionID: string, eventID: string, events: ChronosOOOEvent[]) => { const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 8ed25cb286b0..369ff44773ab 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -137,4 +137,4 @@ type OriginalMessage = | OriginalMessagePolicyTask; export default OriginalMessage; -export type {Reaction}; +export type {Reaction, ChronosOOOEvent}; From 341efce627945305f8fe7a66b73c226619043fd9 Mon Sep 17 00:00:00 2001 From: s-alves10 Date: Tue, 19 Sep 2023 06:32:34 -0500 Subject: [PATCH 023/110] fix: remove unused props --- .../autoCompleteSuggestionsPropTypes.js | 3 --- src/components/EmojiSuggestions.js | 4 ---- src/components/MentionSuggestions.js | 4 ---- .../report/ReportActionCompose/ComposerWithSuggestions.js | 2 -- .../home/report/ReportActionCompose/ReportActionCompose.js | 1 - src/pages/home/report/ReportActionCompose/SuggestionEmoji.js | 2 -- .../home/report/ReportActionCompose/SuggestionMention.js | 2 -- src/pages/home/report/ReportActionCompose/Suggestions.js | 2 -- .../ReportActionCompose/composerWithSuggestionsProps.js | 3 --- src/pages/home/report/ReportActionCompose/suggestionProps.js | 3 --- 10 files changed, 26 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js b/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js index 16040991a3d8..8c6dca1902c5 100644 --- a/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js +++ b/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js @@ -22,9 +22,6 @@ const propTypes = { * When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */ isSuggestionPickerLarge: PropTypes.bool.isRequired, - /** Show that we should include ReportRecipientLocalTime view height */ - shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired, - /** create accessibility label for each item */ accessibilityLabelExtractor: PropTypes.func.isRequired, diff --git a/src/components/EmojiSuggestions.js b/src/components/EmojiSuggestions.js index b06b0cc63eb8..d7f7a8d6091a 100644 --- a/src/components/EmojiSuggestions.js +++ b/src/components/EmojiSuggestions.js @@ -40,9 +40,6 @@ const propTypes = { * 2.5 items. When this value is true, the height can be up to 5 items. */ isEmojiPickerLarge: PropTypes.bool.isRequired, - /** Show that we should include ReportRecipientLocalTime view height */ - shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired, - /** Stores user's preferred skin tone */ preferredSkinToneIndex: PropTypes.number.isRequired, @@ -102,7 +99,6 @@ function EmojiSuggestions(props) { highlightedSuggestionIndex={props.highlightedEmojiIndex} onSelect={props.onSelect} isSuggestionPickerLarge={props.isEmojiPickerLarge} - shouldIncludeReportRecipientLocalTimeHeight={props.shouldIncludeReportRecipientLocalTimeHeight} accessibilityLabelExtractor={keyExtractor} measureParentContainer={props.measureParentContainer} /> diff --git a/src/components/MentionSuggestions.js b/src/components/MentionSuggestions.js index 11df8a597ded..a85eb352b6db 100644 --- a/src/components/MentionSuggestions.js +++ b/src/components/MentionSuggestions.js @@ -41,9 +41,6 @@ const propTypes = { * When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */ isMentionPickerLarge: PropTypes.bool.isRequired, - /** Show that we should include ReportRecipientLocalTime view height */ - shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired, - /** Meaures the parent container's position and dimensions. */ measureParentContainer: PropTypes.func, }; @@ -125,7 +122,6 @@ function MentionSuggestions(props) { highlightedSuggestionIndex={props.highlightedMentionIndex} onSelect={props.onSelect} isSuggestionPickerLarge={props.isMentionPickerLarge} - shouldIncludeReportRecipientLocalTimeHeight={props.shouldIncludeReportRecipientLocalTimeHeight} accessibilityLabelExtractor={keyExtractor} measureParentContainer={props.measureParentContainer} /> diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index 04757b0ff276..c85b4c0604d1 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -91,7 +91,6 @@ function ComposerWithSuggestions({ setIsFullComposerAvailable, setIsCommentEmpty, submitForm, - shouldShowReportRecipientLocalTime, shouldShowComposeInput, measureParentContainer, // Refs @@ -503,7 +502,6 @@ function ComposerWithSuggestions({ isComposerFullSize={isComposerFullSize} updateComment={updateComment} composerHeight={composerHeight} - shouldShowReportRecipientLocalTime={shouldShowReportRecipientLocalTime} onInsertedEmoji={onInsertedEmoji} measureParentContainer={measureParentContainer} // Input diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index e872dc688dd5..d91645589931 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -389,7 +389,6 @@ function ReportActionCompose({ setIsFullComposerAvailable={setIsFullComposerAvailable} setIsCommentEmpty={setIsCommentEmpty} submitForm={submitForm} - shouldShowReportRecipientLocalTime={shouldShowReportRecipientLocalTime} shouldShowComposeInput={shouldShowComposeInput} onFocus={onFocus} onBlur={onBlur} diff --git a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.js b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.js index a760627e53cc..910a338c83b6 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.js +++ b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.js @@ -58,7 +58,6 @@ function SuggestionEmoji({ setSelection, updateComment, isComposerFullSize, - shouldShowReportRecipientLocalTime, isAutoSuggestionPickerLarge, forwardedRef, resetKeyboardInput, @@ -235,7 +234,6 @@ function SuggestionEmoji({ isComposerFullSize={isComposerFullSize} preferredSkinToneIndex={preferredSkinTone} isEmojiPickerLarge={isAutoSuggestionPickerLarge} - shouldIncludeReportRecipientLocalTimeHeight={shouldShowReportRecipientLocalTime} measureParentContainer={measureParentContainer} /> ); diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.js b/src/pages/home/report/ReportActionCompose/SuggestionMention.js index a76025b67b1e..2a464b3bddb3 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.js +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.js @@ -51,7 +51,6 @@ function SuggestionMention({ personalDetails, updateComment, composerHeight, - shouldShowReportRecipientLocalTime, forwardedRef, isAutoSuggestionPickerLarge, measureParentContainer, @@ -284,7 +283,6 @@ function SuggestionMention({ isComposerFullSize={isComposerFullSize} isMentionPickerLarge={isAutoSuggestionPickerLarge} composerHeight={composerHeight} - shouldIncludeReportRecipientLocalTimeHeight={shouldShowReportRecipientLocalTime} measureParentContainer={measureParentContainer} /> ); diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.js b/src/pages/home/report/ReportActionCompose/Suggestions.js index 60cb9de4ccfb..a00bd342b17d 100644 --- a/src/pages/home/report/ReportActionCompose/Suggestions.js +++ b/src/pages/home/report/ReportActionCompose/Suggestions.js @@ -36,7 +36,6 @@ function Suggestions({ setSelection, updateComment, composerHeight, - shouldShowReportRecipientLocalTime, forwardedRef, onInsertedEmoji, resetKeyboardInput, @@ -105,7 +104,6 @@ function Suggestions({ isComposerFullSize, updateComment, composerHeight, - shouldShowReportRecipientLocalTime, isAutoSuggestionPickerLarge, measureParentContainer, }; diff --git a/src/pages/home/report/ReportActionCompose/composerWithSuggestionsProps.js b/src/pages/home/report/ReportActionCompose/composerWithSuggestionsProps.js index b8d9f0b6d816..0c8f36114c44 100644 --- a/src/pages/home/report/ReportActionCompose/composerWithSuggestionsProps.js +++ b/src/pages/home/report/ReportActionCompose/composerWithSuggestionsProps.js @@ -74,9 +74,6 @@ const propTypes = { /** A method to call when the form is submitted */ submitForm: PropTypes.func.isRequired, - /** Whether the recipient local time is shown or not */ - shouldShowReportRecipientLocalTime: PropTypes.bool.isRequired, - /** Whether the compose input is shown or not */ shouldShowComposeInput: PropTypes.bool.isRequired, diff --git a/src/pages/home/report/ReportActionCompose/suggestionProps.js b/src/pages/home/report/ReportActionCompose/suggestionProps.js index 12447929b980..815a1c5619f5 100644 --- a/src/pages/home/report/ReportActionCompose/suggestionProps.js +++ b/src/pages/home/report/ReportActionCompose/suggestionProps.js @@ -22,9 +22,6 @@ const baseProps = { /** Callback to update the comment draft */ updateComment: PropTypes.func.isRequired, - /** Flag whether we need to consider the participants */ - shouldShowReportRecipientLocalTime: PropTypes.bool.isRequired, - /** Meaures the parent container's position and dimensions. */ measureParentContainer: PropTypes.func.isRequired, }; From 6b5621dc2fd3fdb7fbbb2520104a5a84d7d0eb97 Mon Sep 17 00:00:00 2001 From: s-alves10 Date: Tue, 19 Sep 2023 06:43:51 -0500 Subject: [PATCH 024/110] fix: prettier --- src/components/AutoCompleteSuggestions/index.js | 3 ++- src/components/AutoCompleteSuggestions/index.native.js | 6 +++++- .../home/report/ReportActionCompose/ReportActionCompose.js | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/index.js b/src/components/AutoCompleteSuggestions/index.js index 54a2b7dad1fd..9234d04f4507 100644 --- a/src/components/AutoCompleteSuggestions/index.js +++ b/src/components/AutoCompleteSuggestions/index.js @@ -51,7 +51,8 @@ function AutoCompleteSuggestions({measureParentContainer, ...props}) { /> ); - return Boolean(width) && ( + return ( + Boolean(width) && ReactDOM.createPortal({componentToRender}, document.querySelector('body')) ); } diff --git a/src/components/AutoCompleteSuggestions/index.native.js b/src/components/AutoCompleteSuggestions/index.native.js index 588f44809a3b..002435cba14b 100644 --- a/src/components/AutoCompleteSuggestions/index.native.js +++ b/src/components/AutoCompleteSuggestions/index.native.js @@ -5,7 +5,11 @@ import {propTypes} from './autoCompleteSuggestionsPropTypes'; function AutoCompleteSuggestions({measureParentContainer, ...props}) { // eslint-disable-next-line react/jsx-props-no-spreading - return + return ( + + + + ); } AutoCompleteSuggestions.propTypes = propTypes; diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index d91645589931..f1de1b476335 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -326,7 +326,7 @@ function ReportActionCompose({ ref={containerRef} style={[shouldShowReportRecipientLocalTime && !lodashGet(network, 'isOffline') && styles.chatItemComposeWithFirstRow, isComposerFullSize && styles.chatItemFullComposeRow]} > - + Date: Tue, 19 Sep 2023 06:51:21 -0500 Subject: [PATCH 025/110] fix: lint error --- src/components/AutoCompleteSuggestions/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AutoCompleteSuggestions/index.native.js b/src/components/AutoCompleteSuggestions/index.native.js index 002435cba14b..f5ff4636f395 100644 --- a/src/components/AutoCompleteSuggestions/index.native.js +++ b/src/components/AutoCompleteSuggestions/index.native.js @@ -4,9 +4,9 @@ import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import {propTypes} from './autoCompleteSuggestionsPropTypes'; function AutoCompleteSuggestions({measureParentContainer, ...props}) { - // eslint-disable-next-line react/jsx-props-no-spreading return ( + {/* eslint-disable-next-line react/jsx-props-no-spreading */} ); From 5927668c6d907846fb72d1da5963656c2c9502e2 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Tue, 19 Sep 2023 15:11:41 +0200 Subject: [PATCH 026/110] Clear input right away when asking for new one --- .../Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js index bcea33d9c366..300bd23cc2e5 100644 --- a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js @@ -111,6 +111,7 @@ function BaseValidateCodeForm(props) { const resendValidateCode = () => { User.requestContactMethodValidateCode(props.contactMethod); setValidateCode(''); + inputValidateCodeRef.current.clear(); inputValidateCodeRef.current.focus(); }; From ee7aacc91ec292cf57e46cfdf670f74e19f0c3ff Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 19 Sep 2023 21:03:22 +0200 Subject: [PATCH 027/110] Fix tests --- src/libs/ErrorUtils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 899a319f3c91..66dbd923a82d 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -46,15 +46,15 @@ type OnyxDataWithErrors = { }; function getLatestErrorMessage(onyxData: TOnyxData): string { - if (Object.keys(onyxData.errors).length === 0) { + const errors = onyxData.errors ?? {}; + + if (Object.keys(errors).length === 0) { return ''; } - const key = Object.keys(onyxData.errors ?? {}) - .sort() - .reverse()[0]; + const key = Object.keys(errors).sort().reverse()[0]; - return onyxData.errors[key]; + return errors[key]; } type OnyxDataWithErrorFields = { From 359e7997dd774340c45396ebada30355cc424de0 Mon Sep 17 00:00:00 2001 From: Ali Toshmatov Date: Wed, 20 Sep 2023 12:46:43 +0500 Subject: [PATCH 028/110] Fixed image src, modified centered-content class --- docs/404.html | 4 ++-- docs/_sass/_main.scss | 29 ++++++----------------------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/docs/404.html b/docs/404.html index 1773388c6923..4338293218cc 100644 --- a/docs/404.html +++ b/docs/404.html @@ -1,8 +1,8 @@ --- permalink: /404.html --- -
- +
+ Hmm it's not here...
That page is nowhere to be found.
diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index bc9d19bfca11..52a45e39d8d4 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -564,30 +564,13 @@ button { } .centered-content { - height: 240px; + width: 100%; + height: calc(100vh - 56px); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; text-align: center; - font-size: larger; - position: absolute; - top: calc((100vh - 240px) / 2); - - width: 380px; - right: calc((100vw - 380px) / 2); - @include breakpoint($breakpoint-tablet) { - width: 500px; - right: calc((100vw - 500px) / 2); - } - - &.with-lhn { - right: calc((100vw - 380px) / 2); - - @include breakpoint($breakpoint-tablet) { - right: calc((100vw - 320px - 500px ) / 2); - } - - @include breakpoint($breakpoint-desktop) { - right: calc((100vw - 420px - 500px) / 2); - } - } div { margin-top: 8px; From fd9f36ed651a7fb75eb4af065ecd3482076169e9 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 20 Sep 2023 10:55:32 +0300 Subject: [PATCH 029/110] Customizing the isToday & isTomorrow & isYesterday functions to consider timezones --- src/libs/DateUtils.js | 52 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index b33a1b1b2a73..8ec19834e790 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -4,11 +4,10 @@ import {es, enGB} from 'date-fns/locale'; import { formatDistanceToNow, subMinutes, + addDays, + subDays, isBefore, subMilliseconds, - isToday, - isTomorrow, - isYesterday, startOfWeek, endOfWeek, format, @@ -85,6 +84,47 @@ function getLocalDateFromDatetime(locale, datetime, currentSelectedTimezone = ti return utcToZonedTime(parsedDatetime, currentSelectedTimezone); } +/** + * Checks if a given date is today in the specified time zone. + * + * @param {Date} date - The date to compare. + * @param {String} timeZone - The time zone to consider. + * @returns {Boolean} True if the date is today; otherwise, false. + */ +function isToday(date, timeZone) { + const currentDate = new Date(); + const currentDateInTimeZone = utcToZonedTime(currentDate, timeZone); + return isSameDay(date, currentDateInTimeZone); +} + +/** + * Checks if a given date is tomorrow in the specified time zone. + * + * @param {Date} date - The date to compare. + * @param {String} timeZone - The time zone to consider. + * @returns {Boolean} True if the date is tomorrow; otherwise, false. + */ +function isTomorrow(date, timeZone) { + const currentDate = new Date(); + const tomorrow = addDays(currentDate, 1); // Get the date for tomorrow in the current time zone + const tomorrowInTimeZone = utcToZonedTime(tomorrow, timeZone); + return isSameDay(date, tomorrowInTimeZone); +} + +/** + * Checks if a given date is yesterday in the specified time zone. + * + * @param {Date} date - The date to compare. + * @param {String} timeZone - The time zone to consider. + * @returns {Boolean} True if the date is yesterday; otherwise, false. + */ +function isYesterday(date, timeZone) { + const currentDate = new Date(); + const yesterday = subDays(currentDate, 1); // Get the date for yesterday in the current time zone + const yesterdayInTimeZone = utcToZonedTime(yesterday, timeZone); + return isSameDay(date, yesterdayInTimeZone); +} + /** * Formats an ISO-formatted datetime string to local date and time string * @@ -117,13 +157,13 @@ function datetimeToCalendarTime(locale, datetime, includeTimeZone = false, curre yesterdayAt = yesterdayAt.toLowerCase(); } - if (isToday(date)) { + if (isToday(date, currentSelectedTimezone)) { return `${todayAt} ${format(date, CONST.DATE.LOCAL_TIME_FORMAT)}${tz}`; } - if (isTomorrow(date)) { + if (isTomorrow(date, currentSelectedTimezone)) { return `${tomorrowAt} ${format(date, CONST.DATE.LOCAL_TIME_FORMAT)}${tz}`; } - if (isYesterday(date)) { + if (isYesterday(date, currentSelectedTimezone)) { return `${yesterdayAt} ${format(date, CONST.DATE.LOCAL_TIME_FORMAT)}${tz}`; } if (date >= startOfCurrentWeek && date <= endOfCurrentWeek) { From 50752ff03ce21048f14e88c81f5a7e5200491f48 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Sep 2023 11:10:34 +0200 Subject: [PATCH 030/110] chore: add TODO comment --- src/libs/actions/Chronos.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Chronos.ts b/src/libs/actions/Chronos.ts index bf758128714f..3e94196dc50a 100644 --- a/src/libs/actions/Chronos.ts +++ b/src/libs/actions/Chronos.ts @@ -1,4 +1,3 @@ -import _ from 'underscore'; import Onyx from 'react-native-onyx'; import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; @@ -14,6 +13,7 @@ const removeEvent = (reportID: string, reportActionID: string, eventID: string, [reportActionID]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, originalMessage: { + // TODO: remove underscore events: _.reject(events, (event) => event.id === eventID), }, }, From a90591c9b5ed976241692859287df027ddf4a0a5 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 20 Sep 2023 18:09:27 +0800 Subject: [PATCH 031/110] render option row offscreen if there is a stacked avatar --- src/components/LHNOptionsList/OptionRowLHN.js | 1 + src/components/OpacityView.js | 14 +++++++++++++- src/components/OptionRow.js | 1 + src/components/Pressable/PressableWithFeedback.js | 9 ++++++--- .../pressableWithSecondaryInteractionPropTypes.js | 4 ++++ 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index f5a293701454..5ead8bbb1fd6 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -180,6 +180,7 @@ function OptionRowLHN(props) { ]} accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} + needsOffscreenAlphaCompositing={props.optionItem.icons.length >= 2} > diff --git a/src/components/OpacityView.js b/src/components/OpacityView.js index 2d09da744267..daef93cdc09b 100644 --- a/src/components/OpacityView.js +++ b/src/components/OpacityView.js @@ -3,6 +3,7 @@ import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-nati import PropTypes from 'prop-types'; import variables from '../styles/variables'; import * as StyleUtils from '../styles/StyleUtils'; +import shouldRenderOffscreen from '../libs/shouldRenderOffscreen'; const propTypes = { /** @@ -27,11 +28,15 @@ const propTypes = { * @default 0.5 */ dimmingValue: PropTypes.number, + + /** Whether the view needs to be rendered offscreen (for Android only) */ + needsOffscreenAlphaCompositing: PropTypes.bool, }; const defaultProps = { style: [], dimmingValue: variables.hoverDimValue, + needsOffscreenAlphaCompositing: false, }; function OpacityView(props) { @@ -48,7 +53,14 @@ function OpacityView(props) { } }, [props.shouldDim, props.dimmingValue, opacity]); - return {props.children}; + return ( + + {props.children} + + ); } OpacityView.displayName = 'OpacityView'; diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index a07510f7603d..3586f263db14 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -212,6 +212,7 @@ class OptionRow extends Component { accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} hoverDimmingValue={1} hoverStyle={this.props.hoverStyle} + needsOffscreenAlphaCompositing={this.props.option.icons.length >= 2} > diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index 7eb0ee7286c9..53c5f70c81a2 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -7,7 +7,7 @@ import OpacityView from '../OpacityView'; import variables from '../../styles/variables'; import useSingleExecution from '../../hooks/useSingleExecution'; -const omittedProps = ['wrapperStyle']; +const omittedProps = ['wrapperStyle', 'needsOffscreenAlphaCompositing']; const PressableWithFeedbackPropTypes = { ...GenericPressablePropTypes.pressablePropTypes, @@ -27,6 +27,7 @@ const PressableWithFeedbackPropTypes = { * Used to locate this view from native classes. */ nativeID: propTypes.string, + needsOffscreenAlphaCompositing: propTypes.bool, }; const PressableWithFeedbackDefaultProps = { @@ -35,10 +36,11 @@ const PressableWithFeedbackDefaultProps = { hoverDimmingValue: variables.hoverDimValue, nativeID: '', wrapperStyle: [], + needsOffscreenAlphaCompositing: false, }; const PressableWithFeedback = forwardRef((props, ref) => { - const propsWithoutWrapperStyles = _.omit(props, omittedProps); + const propsWithoutWrapperProps = _.omit(props, omittedProps); const {isExecuting, singleExecution} = useSingleExecution(); const [isPressed, setIsPressed] = useState(false); const [isHovered, setIsHovered] = useState(false); @@ -49,11 +51,12 @@ const PressableWithFeedback = forwardRef((props, ref) => { shouldDim={Boolean(!isDisabled && (isPressed || isHovered))} dimmingValue={isPressed ? props.pressDimmingValue : props.hoverDimmingValue} style={props.wrapperStyle} + needsOffscreenAlphaCompositing={props.needsOffscreenAlphaCompositing} > { diff --git a/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js b/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js index f521a57957f3..0a4f7949643a 100644 --- a/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js +++ b/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js @@ -48,6 +48,9 @@ const propTypes = { /** Used to apply styles to the Pressable */ style: stylePropTypes, + + /** Whether the view needs to be rendered offscreen (for Android only) */ + needsOffscreenAlphaCompositing: PropTypes.bool, }; const defaultProps = { @@ -59,6 +62,7 @@ const defaultProps = { withoutFocusOnSecondaryInteraction: false, activeOpacity: 1, enableLongPressWithHover: false, + needsOffscreenAlphaCompositing: false, }; export {propTypes, defaultProps}; From 504bf3de89ae97a91ccba99353c56f9957a30c42 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Sep 2023 14:01:17 +0200 Subject: [PATCH 032/110] fix: switch from underscore to native js --- src/libs/actions/Chronos.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/actions/Chronos.ts b/src/libs/actions/Chronos.ts index 3e94196dc50a..1b46a68a1afe 100644 --- a/src/libs/actions/Chronos.ts +++ b/src/libs/actions/Chronos.ts @@ -13,8 +13,7 @@ const removeEvent = (reportID: string, reportActionID: string, eventID: string, [reportActionID]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, originalMessage: { - // TODO: remove underscore - events: _.reject(events, (event) => event.id === eventID), + events: events.filter((event) => event.id !== eventID), }, }, }, From 0ca5ddec38f9b63aa6c73e42c235c95dd9bcb361 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 20 Sep 2023 18:59:57 +0200 Subject: [PATCH 033/110] edit billable --- .../ReportActionItem/MoneyRequestView.js | 25 +++++++++++++++++-- src/libs/ReportUtils.js | 1 + src/libs/TransactionUtils.js | 16 ++++++++++++ src/libs/actions/IOU.js | 4 ++- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 712c7ded6ab0..18fc581b6fca 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -6,12 +6,15 @@ import PropTypes from 'prop-types'; import reportPropTypes from '../../pages/reportPropTypes'; import ONYXKEYS from '../../ONYXKEYS'; import ROUTES from '../../ROUTES'; +import Permissions from '../../libs/Permissions'; import Navigation from '../../libs/Navigation/Navigation'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '../withCurrentUserPersonalDetails'; import compose from '../../libs/compose'; import MenuItemWithTopDescription from '../MenuItemWithTopDescription'; import styles from '../../styles/styles'; +import themeColors from '../../styles/themes/default'; import * as ReportUtils from '../../libs/ReportUtils'; +import * as IOU from '../../libs/actions/IOU'; import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; import * as StyleUtils from '../../styles/StyleUtils'; import CONST from '../../CONST'; @@ -24,6 +27,8 @@ import * as ReceiptUtils from '../../libs/ReceiptUtils'; import useWindowDimensions from '../../hooks/useWindowDimensions'; import transactionPropTypes from '../transactionPropTypes'; import Image from '../Image'; +import Text from '../Text'; +import Switch from '../Switch'; import ReportActionItemImage from './ReportActionItemImage'; import * as TransactionUtils from '../../libs/TransactionUtils'; import OfflineWithFeedback from '../OfflineWithFeedback'; @@ -45,6 +50,7 @@ const propTypes = { }; const defaultProps = { + betas: [], parentReport: {}, transaction: { amount: 0, @@ -53,10 +59,9 @@ const defaultProps = { }, }; -function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, transaction}) { +function MoneyRequestView({betas, report, parentReport, shouldShowHorizontalRule, transaction}) { const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); - const parentReportAction = ReportActionsUtils.getParentReportAction(report); const moneyRequestReport = parentReport; const { @@ -65,6 +70,7 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans currency: transactionCurrency, comment: transactionDescription, merchant: transactionMerchant, + billable: transactionBillable, } = ReportUtils.getTransactionDetails(transaction); const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; @@ -72,6 +78,7 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); + const shouldShowBillable = Permissions.canUseTags(betas); let description = `${translate('iou.amount')} • ${translate('iou.cash')}`; if (isSettled) { @@ -169,6 +176,17 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans subtitleTextStyle={styles.textLabelError} /> + {shouldShowBillable && ( + + {translate('common.billable')} + IOU.editMoneyRequest(transaction.transactionID, transaction.reportID, {billable: value})} + /> + + )} + {shouldShowHorizontalRule && } ); @@ -181,6 +199,9 @@ MoneyRequestView.displayName = 'MoneyRequestView'; export default compose( withCurrentUserPersonalDetails, withOnyx({ + betas: { + key: ONYXKEYS.BETAS, + }, parentReport: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, }, diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8e9cea908f74..859d22b1c18c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1303,6 +1303,7 @@ function getTransactionDetails(transaction) { comment: TransactionUtils.getDescription(transaction), merchant: TransactionUtils.getMerchant(transaction), category: TransactionUtils.getCategory(transaction), + billable: TransactionUtils.getBillable(transaction), }; } diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 70583175f115..c10cd3af3001 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -144,6 +144,10 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep shouldStopSmartscan = true; } + if (_.has(transactionChanges, 'billable')) { + updatedTransaction.billable = transactionChanges.billable; + } + 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; } @@ -154,6 +158,7 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep ...(_.has(transactionChanges, 'amount') && {amount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(_.has(transactionChanges, 'currency') && {currency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(_.has(transactionChanges, 'merchant') && {merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(_.has(transactionChanges, 'billable') && {billable: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }; return updatedTransaction; @@ -245,6 +250,16 @@ function getCategory(transaction) { return lodashGet(transaction, 'category', ''); } +/** + * Return the category from the transaction. This "category" field has no "modified" complement. + * + * @param {Object} transaction + * @return {String} + */ +function getBillable(transaction) { + return lodashGet(transaction, 'billable', ''); +} + /** * Return the created field from the transaction, return the modifiedCreated if present. * @@ -391,6 +406,7 @@ export { getMerchant, getCreated, getCategory, + getBillable, getLinkedTransaction, getAllReportTransactions, hasReceipt, diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 587b2392deba..641593cc3443 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1130,6 +1130,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC created: null, currency: null, merchant: null, + billable: null, }, }, }, @@ -1166,7 +1167,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, billable} = ReportUtils.getTransactionDetails(updatedTransaction); API.write( 'EditMoneyRequest', { @@ -1178,6 +1179,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC comment, merchant, category, + billable, }, {optimisticData, successData, failureData}, ); From 37ade6b28e9eedaefd030837111f733271d6b16a Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 20 Sep 2023 19:35:44 +0200 Subject: [PATCH 034/110] fix lint --- src/components/ReportActionItem/MoneyRequestView.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 9745d6e7fe7b..0a0fa4bf21e8 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -47,6 +47,10 @@ const propTypes = { /** Whether we should display the horizontal rule below the component */ shouldShowHorizontalRule: PropTypes.bool.isRequired, + /* Onyx Props */ + /** List of betas available to current user */ + betas: PropTypes.arrayOf(PropTypes.string), + ...withCurrentUserPersonalDetailsPropTypes, }; From a9f062c9c0e0f447f35841f891ed055027a0513f Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 20 Sep 2023 19:36:52 +0200 Subject: [PATCH 035/110] comment change --- src/libs/TransactionUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 874f0a46fb6f..5e85c98cc42f 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -254,7 +254,7 @@ function getCategory(transaction) { } /** - * Return the category from the transaction. This "category" field has no "modified" complement. + * Return the billable from the transaction. This "billable" field has no "modified" complement. * * @param {Object} transaction * @return {String} From 228c4f5609e98df69afae2aed86f2f6fb5f78579 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 20 Sep 2023 19:42:07 +0200 Subject: [PATCH 036/110] added verification for billable in disabled fields --- src/components/ReportActionItem/MoneyRequestView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 0a0fa4bf21e8..a57ccad75f56 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -64,7 +64,7 @@ const defaultProps = { }, }; -function MoneyRequestView({betas, report, parentReport, shouldShowHorizontalRule, transaction}) { +function MoneyRequestView({betas, report, parentReport, shouldShowHorizontalRule, transaction, policy}) { const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); const parentReportAction = ReportActionsUtils.getParentReportAction(report); @@ -83,7 +83,7 @@ function MoneyRequestView({betas, report, parentReport, shouldShowHorizontalRule const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); - const shouldShowBillable = Permissions.canUseTags(betas); + const shouldShowBillable = Permissions.canUseTags(betas) && !lodashGet(policy, 'disabledFields.defaultBillable', true); let description = `${translate('iou.amount')} • ${translate('iou.cash')}`; if (isSettled) { From eda02a0807499330fb3b5015000f3ecef8dafc63 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Wed, 20 Sep 2023 19:52:55 +0000 Subject: [PATCH 037/110] Fix Copying Sent Money Report Action text copies the wrong text --- .../report/ContextMenu/ContextMenuActions.js | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 173bda0e5221..8abfe2555019 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -207,11 +207,34 @@ export default [ const transaction = TransactionUtils.getTransaction(originalMessage.IOUTransactionID); const {amount, currency, comment} = ReportUtils.getTransactionDetails(transaction); const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); - const displaymessage = Localize.translateLocal('iou.requestedAmount', { - formattedAmount, - comment, - }); - Clipboard.setString(displaymessage); + let displayMessage; + if (ReportActionsUtils.isSentMoneyReportAction(reportAction)) { + const iouReport = ReportUtils.getReport(originalMessage.IOUReportID); + const payerName = ReportUtils.getDisplayNameForParticipant(iouReport.managerID, true); + let translationKey; + switch (originalMessage.paymentType) { + case CONST.IOU.PAYMENT_TYPE.ELSEWHERE: + translationKey = 'iou.paidElsewhereWithAmount'; + break; + case CONST.IOU.PAYMENT_TYPE.PAYPAL_ME: + translationKey = 'iou.paidUsingPaypalWithAmount'; + break; + case CONST.IOU.PAYMENT_TYPE.EXPENSIFY: + case CONST.IOU.PAYMENT_TYPE.VBBA: + translationKey = 'iou.paidUsingExpensifyWithAmount'; + break; + default: + translationKey = ''; + break; + } + displayMessage = Localize.translateLocal(translationKey, {amount: formattedAmount, payer: payerName}); + } else { + displayMessage = Localize.translateLocal('iou.requestedAmount', { + formattedAmount, + comment, + }); + } + Clipboard.setString(displayMessage); } else if (content) { const parser = new ExpensiMark(); if (!Clipboard.canSetHtml()) { From 540dfa154de61ab9421d6fc0c0a3089d2b5ae2ff Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Wed, 20 Sep 2023 21:05:43 +0000 Subject: [PATCH 038/110] Fix Copying Sent Money Report Action text copies the wrong text --- .../home/report/ContextMenu/ContextMenuActions.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 8abfe2555019..d965fe457d76 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -204,13 +204,12 @@ export default [ Clipboard.setString(modifyExpenseMessage); } else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { const originalMessage = _.get(reportAction, 'originalMessage', {}); - const transaction = TransactionUtils.getTransaction(originalMessage.IOUTransactionID); - const {amount, currency, comment} = ReportUtils.getTransactionDetails(transaction); - const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); let displayMessage; if (ReportActionsUtils.isSentMoneyReportAction(reportAction)) { - const iouReport = ReportUtils.getReport(originalMessage.IOUReportID); - const payerName = ReportUtils.getDisplayNameForParticipant(iouReport.managerID, true); + const {amount, currency, IOUReportID} = originalMessage; + const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); + const iouReport = ReportUtils.getReport(IOUReportID); + const payerName = ReportActionsUtils.getDisplayNameForParticipant(iouReport.managerID, true); let translationKey; switch (originalMessage.paymentType) { case CONST.IOU.PAYMENT_TYPE.ELSEWHERE: @@ -229,6 +228,9 @@ export default [ } displayMessage = Localize.translateLocal(translationKey, {amount: formattedAmount, payer: payerName}); } else { + const transaction = TransactionUtils.getTransaction(originalMessage.IOUTransactionID); + const {amount, currency, comment} = ReportUtils.getTransactionDetails(transaction); + const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); displayMessage = Localize.translateLocal('iou.requestedAmount', { formattedAmount, comment, From 0167ae13c1d1d5fa6fe6cf6c852e400e14ec3877 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Wed, 20 Sep 2023 21:25:53 +0000 Subject: [PATCH 039/110] using getDisplayNameForParticipant function lib from ReportUtils instead of ReportActionsUtils --- src/pages/home/report/ContextMenu/ContextMenuActions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index d965fe457d76..9ee801fbd4a6 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -205,11 +205,11 @@ export default [ } else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { const originalMessage = _.get(reportAction, 'originalMessage', {}); let displayMessage; - if (ReportActionsUtils.isSentMoneyReportAction(reportAction)) { + if (originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) { const {amount, currency, IOUReportID} = originalMessage; const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); const iouReport = ReportUtils.getReport(IOUReportID); - const payerName = ReportActionsUtils.getDisplayNameForParticipant(iouReport.managerID, true); + const payerName = ReportUtils.getDisplayNameForParticipant(iouReport.managerID, true); let translationKey; switch (originalMessage.paymentType) { case CONST.IOU.PAYMENT_TYPE.ELSEWHERE: From 322201c843b9e77be98924950619eaf7a1e1440a Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Sep 2023 10:43:29 +0700 Subject: [PATCH 040/110] edit the route of new task page --- src/ROUTES.ts | 2 -- src/libs/Navigation/linkingConfig.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2c37116db395..6b26ec95b101 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -121,8 +121,6 @@ export default { getMoneyRequestTagRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}`, SPLIT_BILL_DETAILS: `r/:reportID/split/:reportActionID`, getSplitBillDetailsRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}`, - getNewTaskRoute: (reportID: string) => `${NEW_TASK}/${reportID}`, - NEW_TASK_WITH_REPORT_ID: `${NEW_TASK}/:reportID?`, TASK_TITLE: 'r/:reportID/title', TASK_DESCRIPTION: 'r/:reportID/description', TASK_ASSIGNEE: 'r/:reportID/assignee', diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index f4420330fbd9..5cf8170632b8 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -263,7 +263,7 @@ export default { }, NewTask: { screens: { - NewTask_Root: ROUTES.NEW_TASK_WITH_REPORT_ID, + NewTask_Root: ROUTES.NEW_TASK, NewTask_TaskAssigneeSelector: ROUTES.NEW_TASK_ASSIGNEE, NewTask_TaskShareDestinationSelector: ROUTES.NEW_TASK_SHARE_DESTINATION, NewTask_Details: ROUTES.NEW_TASK_DETAILS, From 96ce48ecbf6a8fed405fd3c55cba14ffd413cc71 Mon Sep 17 00:00:00 2001 From: Amy Evans Date: Thu, 21 Sep 2023 12:33:35 +0800 Subject: [PATCH 041/110] Add billable field to Transaction --- 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 de128d85e6b1..4a9bff4e18e8 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -21,6 +21,7 @@ type Routes = Record; type Transaction = { transactionID: string; amount: number; + billable: boolean; category: string; currency: string; reportID: string; From 61725c09eff0b5154cf2c217161846ca7d6b7c94 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Thu, 21 Sep 2023 14:39:55 +0200 Subject: [PATCH 042/110] Reset index on focus --- src/components/MagicCodeInput.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index caf21b7dd08f..b8501f4d227c 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -110,6 +110,8 @@ function MagicCodeInput(props) { const focusMagicCodeInput = () => { setFocusedIndex(0); + lastFocusedIndex.current = 0; + setEditIndex(0); inputRefs.current.focus(); }; From eaa4706efd7b04084586bd66d6a5fdae76b9fa9f Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Thu, 21 Sep 2023 15:02:28 +0200 Subject: [PATCH 043/110] distance request --- src/libs/actions/IOU.js | 4 +++- src/pages/iou/steps/MoneyRequestConfirmPage.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 0c0f1ddb90d8..85d95cc0a778 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -588,7 +588,7 @@ function getMoneyRequestInformation( * @param {String} currency * @param {String} merchant */ -function createDistanceRequest(report, participant, comment, created, transactionID, category, tag, amount, currency, merchant) { +function createDistanceRequest(report, participant, comment, created, transactionID, category, tag, amount, currency, merchant, billable) { const optimisticReceipt = { source: ReceiptGeneric, state: CONST.IOU.RECEIPT_STATE.OPEN, @@ -607,6 +607,7 @@ function createDistanceRequest(report, participant, comment, created, transactio transactionID, category, tag, + billable, ); API.write( 'CreateDistanceRequest', @@ -623,6 +624,7 @@ function createDistanceRequest(report, participant, comment, created, transactio created, category, tag, + billable, }, onyxData, ); diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 93ee2c7f8aac..b29fbe0d8970 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -176,9 +176,10 @@ function MoneyRequestConfirmPage(props) { props.iou.amount, props.iou.currency, props.iou.merchant, + props.iou.billable, ); }, - [props.report, props.iou.created, props.iou.transactionID, props.iou.category, props.iou.tag, props.iou.amount, props.iou.currency, props.iou.merchant], + [props.report, props.iou.created, props.iou.transactionID, props.iou.category, props.iou.tag, props.iou.amount, props.iou.currency, props.iou.merchant, props.iou.billable], ); const createTransaction = useCallback( From feb52ed8346f27d365ac49019ed860102a1d2456 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Thu, 21 Sep 2023 15:20:35 +0200 Subject: [PATCH 044/110] lint fix --- src/components/ReportActionItem/MoneyRequestView.js | 7 ------- src/libs/actions/IOU.js | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index b2322f5032b1..f2c33e9be063 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -57,13 +57,6 @@ const propTypes = { /** The transaction associated with the transactionThread */ transaction: transactionPropTypes, - /** Whether we should display the horizontal rule below the component */ - shouldShowHorizontalRule: PropTypes.bool.isRequired, - - /* Onyx Props */ - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - ...withCurrentUserPersonalDetailsPropTypes, }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 10ac4a88b691..d42a51515567 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -587,6 +587,7 @@ function getMoneyRequestInformation( * @param {Number} amount * @param {String} currency * @param {String} merchant + * @param {Boolean} [billable] */ function createDistanceRequest(report, participant, comment, created, transactionID, category, tag, amount, currency, merchant, billable) { const optimisticReceipt = { From 48fba564f239253d9330c7db9e58c35b54af83a0 Mon Sep 17 00:00:00 2001 From: Aman Sheikh Date: Thu, 21 Sep 2023 20:29:56 +0530 Subject: [PATCH 045/110] use vertical padding --- src/styles/styles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/styles.js b/src/styles/styles.js index 4a2472913fd2..c58cef754b57 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3719,7 +3719,7 @@ const styles = (theme) => ({ }, signInIconButton: { - padding: 2, + paddingVertical: 2, }, googleButtonContainer: { From 7e7de605209d1034c2bd3b10c1f5d60b3a12a7fc Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 21 Sep 2023 23:15:03 +0800 Subject: [PATCH 046/110] set map text color to black --- src/components/MapView/MapView.web.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/MapView/MapView.web.tsx b/src/components/MapView/MapView.web.tsx index ce13cee10f54..209928ab0499 100644 --- a/src/components/MapView/MapView.web.tsx +++ b/src/components/MapView/MapView.web.tsx @@ -11,6 +11,8 @@ import responder from './responder'; import utils from './utils'; import CONST from '../../CONST'; +import * as StyleUtils from '../../styles/StyleUtils'; +import themeColors from '../../styles/themes/default'; import Direction from './Direction'; import {MapViewHandle, MapViewProps} from './MapViewTypes'; @@ -87,6 +89,7 @@ const MapView = forwardRef( latitude: initialState.location[1], zoom: initialState.zoom, }} + style={StyleUtils.getTextColorStyle(themeColors.shadow) as React.CSSProperties} mapStyle={styleURL} > {waypoints?.map(({coordinate, markerComponent, id}) => { From 6f3221da0284f148e3d8d8354fee78614e7c6472 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:36:32 +0000 Subject: [PATCH 047/110] Fix Copying IOU Report Action text copies a wrong text --- src/libs/ReportUtils.js | 46 ++++++++++++++++++- .../report/ContextMenu/ContextMenuActions.js | 37 +-------------- 2 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 7916630d7b4e..cbd41816af09 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -557,7 +557,7 @@ function findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTim if (ignoreDomainRooms) { // We allow public announce rooms, admins, and announce rooms through since we bypass the default rooms beta for them. - // Check where ReportUtils.findLastAccessedReport is called in MainDrawerNavigator.js for more context. + // Check where findLastAccessedReport is called in MainDrawerNavigator.js for more context. // Domain rooms are now the only type of default room that are on the defaultRooms beta. sortedReports = _.filter( sortedReports, @@ -3517,6 +3517,49 @@ function getReportPreviewDisplayTransactions(reportPreviewAction) { ); } +/** + * Return iou report action display message + * + * @param {Object} reportAction report action + * @returns {String} + */ +function getIouReportActionDisplayMessage(reportAction) { + const originalMessage = _.get(reportAction, 'originalMessage', {}); + let displayMessage; + if (originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) { + const {amount, currency, IOUReportID} = originalMessage; + const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); + const iouReport = getReport(IOUReportID); + const payerName = isExpenseReport(iouReport) ? getPolicyName(iouReport, false, getPolicyName(iouReport)) : getDisplayNameForParticipant(iouReport.managerID); + let translationKey; + switch (originalMessage.paymentType) { + case CONST.IOU.PAYMENT_TYPE.ELSEWHERE: + translationKey = 'iou.paidElsewhereWithAmount'; + break; + case CONST.IOU.PAYMENT_TYPE.PAYPAL_ME: + translationKey = 'iou.paidUsingPaypalWithAmount'; + break; + case CONST.IOU.PAYMENT_TYPE.EXPENSIFY: + case CONST.IOU.PAYMENT_TYPE.VBBA: + translationKey = 'iou.paidUsingExpensifyWithAmount'; + break; + default: + translationKey = ''; + break; + } + displayMessage = Localize.translateLocal(translationKey, {amount: formattedAmount, payer: payerName}); + } else { + const transaction = TransactionUtils.getTransaction(originalMessage.IOUTransactionID); + const {amount, currency, comment} = getTransactionDetails(transaction); + const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); + displayMessage = Localize.translateLocal('iou.requestedAmount', { + formattedAmount, + comment, + }); + } + return displayMessage; +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -3657,4 +3700,5 @@ export { getReportPreviewDisplayTransactions, getTransactionsWithReceipts, hasMissingSmartscanFields, + getIouReportActionDisplayMessage, }; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 9ee801fbd4a6..eafc0e03bc9b 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -22,9 +22,6 @@ import MiniQuickEmojiReactions from '../../../../components/Reactions/MiniQuickE import Navigation from '../../../../libs/Navigation/Navigation'; import ROUTES from '../../../../ROUTES'; import * as Task from '../../../../libs/actions/Task'; -import * as Localize from '../../../../libs/Localize'; -import * as TransactionUtils from '../../../../libs/TransactionUtils'; -import * as CurrencyUtils from '../../../../libs/CurrencyUtils'; /** * Gets the HTML version of the message in an action. @@ -203,39 +200,7 @@ export default [ const modifyExpenseMessage = ReportUtils.getModifiedExpenseMessage(reportAction); Clipboard.setString(modifyExpenseMessage); } else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { - const originalMessage = _.get(reportAction, 'originalMessage', {}); - let displayMessage; - if (originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) { - const {amount, currency, IOUReportID} = originalMessage; - const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); - const iouReport = ReportUtils.getReport(IOUReportID); - const payerName = ReportUtils.getDisplayNameForParticipant(iouReport.managerID, true); - let translationKey; - switch (originalMessage.paymentType) { - case CONST.IOU.PAYMENT_TYPE.ELSEWHERE: - translationKey = 'iou.paidElsewhereWithAmount'; - break; - case CONST.IOU.PAYMENT_TYPE.PAYPAL_ME: - translationKey = 'iou.paidUsingPaypalWithAmount'; - break; - case CONST.IOU.PAYMENT_TYPE.EXPENSIFY: - case CONST.IOU.PAYMENT_TYPE.VBBA: - translationKey = 'iou.paidUsingExpensifyWithAmount'; - break; - default: - translationKey = ''; - break; - } - displayMessage = Localize.translateLocal(translationKey, {amount: formattedAmount, payer: payerName}); - } else { - const transaction = TransactionUtils.getTransaction(originalMessage.IOUTransactionID); - const {amount, currency, comment} = ReportUtils.getTransactionDetails(transaction); - const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); - displayMessage = Localize.translateLocal('iou.requestedAmount', { - formattedAmount, - comment, - }); - } + const displayMessage = ReportUtils.getIouReportActionDisplayMessage(reportAction); Clipboard.setString(displayMessage); } else if (content) { const parser = new ExpensiMark(); From 4c9641df241afb970b5d142bae214c1f5f999712 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:44:13 +0000 Subject: [PATCH 048/110] correct comment --- 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 cbd41816af09..82cd9047654c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -557,7 +557,7 @@ function findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTim if (ignoreDomainRooms) { // We allow public announce rooms, admins, and announce rooms through since we bypass the default rooms beta for them. - // Check where findLastAccessedReport is called in MainDrawerNavigator.js for more context. + // Check where ReportUtils.findLastAccessedReport is called in MainDrawerNavigator.js for more context. // Domain rooms are now the only type of default room that are on the defaultRooms beta. sortedReports = _.filter( sortedReports, From 8c2ec626a7e278062554e5c20cab6f8a7edd9785 Mon Sep 17 00:00:00 2001 From: c3024 Date: Fri, 22 Sep 2023 10:51:10 +0530 Subject: [PATCH 049/110] remove duplicate back icon, add clear message --- src/pages/workspace/WorkspaceNewRoomPage.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 89a3e854578d..c031fa8e6073 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -5,7 +5,9 @@ import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import withNavigationFocus, {withNavigationFocusPropTypes} from '../../components/withNavigationFocus'; import * as Report from '../../libs/actions/Report'; +import * as App from '../../libs/actions/App'; import useLocalize from '../../hooks/useLocalize'; +import useWindowDimensions from '../../hooks/useWindowDimensions'; import styles from '../../styles/styles'; import RoomNameInput from '../../components/RoomNameInput'; import Picker from '../../components/Picker'; @@ -69,6 +71,7 @@ const defaultProps = { function WorkspaceNewRoomPage(props) { const {translate} = useLocalize(); + const {isSmallScreenWidth} = useWindowDimensions(); const [visibility, setVisibility] = useState(CONST.REPORT.VISIBILITY.RESTRICTED); const [policyID, setPolicyID] = useState(null); const visibilityDescription = useMemo(() => translate(`newRoomPage.${visibility}Description`), [translate, visibility]); @@ -144,7 +147,12 @@ function WorkspaceNewRoomPage(props) { ); return ( - + App.createWorkspaceAndNavigateToIt('', false, '', false, !isSmallScreenWidth)} + > Date: Tue, 12 Sep 2023 10:21:39 +0200 Subject: [PATCH 050/110] migrate PopoverReportActionContextMenu class to function component --- .../PopoverReportActionContextMenu.js | 448 +++++++++--------- 1 file changed, 214 insertions(+), 234 deletions(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index dd0813132a8e..4dbf5a32cac2 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -1,13 +1,11 @@ -import React from 'react'; +import React, {forwardRef, useEffect, useState, useRef, useImperativeHandle, useCallback} from 'react'; import {Dimensions} from 'react-native'; import _ from 'underscore'; -import lodashGet from 'lodash/get'; import * as Report from '../../../../libs/actions/Report'; import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; import PopoverWithMeasuredContent from '../../../../components/PopoverWithMeasuredContent'; import BaseReportActionContextMenu from './BaseReportActionContextMenu'; import ConfirmModal from '../../../../components/ConfirmModal'; -import CONST from '../../../../CONST'; import * as ReportActionsUtils from '../../../../libs/ReportActionsUtils'; import * as IOU from '../../../../libs/actions/IOU'; @@ -15,82 +13,59 @@ const propTypes = { ...withLocalizePropTypes, }; -class PopoverReportActionContextMenu extends React.Component { - constructor(props) { - super(props); - - this.state = { - reportID: '0', - reportActionID: '0', - originalReportID: '0', - reportAction: {}, - selection: '', - reportActionDraftMessage: '', - isPopoverVisible: false, - isDeleteCommentConfirmModalVisible: false, - shouldSetModalVisibilityForDeleteConfirmation: true, - cursorRelativePosition: { - horizontal: 0, - vertical: 0, - }, - - // The horizontal and vertical position (relative to the screen) where the popover will display. - popoverAnchorPosition: { - horizontal: 0, - vertical: 0, - }, - isArchivedRoom: false, - isChronosReport: false, - isPinnedChat: false, - isUnreadChat: false, - }; - this.onPopoverShow = () => {}; - this.onPopoverHide = () => {}; - this.onPopoverHideActionCallback = () => {}; - this.contextMenuAnchor = undefined; - this.showContextMenu = this.showContextMenu.bind(this); - this.hideContextMenu = this.hideContextMenu.bind(this); - this.measureContextMenuAnchorPosition = this.measureContextMenuAnchorPosition.bind(this); - this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); - this.hideDeleteModal = this.hideDeleteModal.bind(this); - this.showDeleteModal = this.showDeleteModal.bind(this); - this.runAndResetOnPopoverShow = this.runAndResetOnPopoverShow.bind(this); - this.runAndResetOnPopoverHide = this.runAndResetOnPopoverHide.bind(this); - this.getContextMenuMeasuredLocation = this.getContextMenuMeasuredLocation.bind(this); - this.isActiveReportAction = this.isActiveReportAction.bind(this); - this.clearActiveReportAction = this.clearActiveReportAction.bind(this); - - this.dimensionsEventListener = null; - - this.contentRef = React.createRef(); - this.setContentRef = (ref) => { - this.contentRef.current = ref; +const PopoverReportActionContextMenu = forwardRef((props, ref) => { + const reportIDRef = useRef('0'); + const typeRef = useRef(''); + const reportActionRef = useRef({}); + const reportActionIDRef = useRef('0'); + const originalReportIDRef = useRef('0'); + const selectionRef = useRef(''); + const reportActionDraftMessageRef = useRef(''); + + const cursorRelativePosition = useRef({ + horizontal: 0, + vertical: 0, + }); + + // The horizontal and vertical position (relative to the screen) where the popover will display. + const popoverAnchorPosition = useRef({ + horizontal: 0, + vertical: 0, + }); + + const [instanceID, setInstanceID] = useState(''); + + const [isPopoverVisible, setIsPopoverVisible] = useState(false); + const [isDeleteCommentConfirmModalVisible, setIsDeleteCommentConfirmModalVisible] = useState(false); + const [shouldSetModalVisibilityForDeleteConfirmation, setShouldSetModalVisibilityForDeleteConfirmation] = useState(true); + const [isArchivedRoom, setIsArchivedRoom] = useState(false); + const [isChronosReport, setIsChronosReport] = useState(false); + const [isPinnedChat, setIsPinnedChat] = useState(false); + const [isUnreadChat, setIsUnreadChat] = useState(false); + + const contentRef = useRef(null); + const anchorRef = useRef(null); + const dimensionsEventListener = useRef(null); + const contextMenuAnchor = useRef(null); + const contextMenuTargetNode = useRef(null); + + const onPopoverShow = useRef(() => {}); + const onPopoverHide = useRef(() => {}); + const onCancelDeleteModal = useRef(() => {}); + const onComfirmDeleteModal = useRef(() => {}); + + const onPopoverHideActionCallback = useRef(() => {}); + const callbackWhenDeleteModalHide = useRef(() => {}); + + useEffect(() => { + dimensionsEventListener.current = Dimensions.addEventListener('change', measureContextMenuAnchorPosition); + + return () => { + if (dimensionsEventListener.current) { + dimensionsEventListener.current.remove(); + } }; - this.setContentRef = this.setContentRef.bind(this); - this.anchorRef = React.createRef(); - } - - componentDidMount() { - this.dimensionsEventListener = Dimensions.addEventListener('change', this.measureContextMenuAnchorPosition); - } - - shouldComponentUpdate(nextProps, nextState) { - const previousLocale = lodashGet(this.props, 'preferredLocale', CONST.LOCALES.DEFAULT); - const nextLocale = lodashGet(nextProps, 'preferredLocale', CONST.LOCALES.DEFAULT); - return ( - this.state.isPopoverVisible !== nextState.isPopoverVisible || - this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition || - this.state.isDeleteCommentConfirmModalVisible !== nextState.isDeleteCommentConfirmModalVisible || - previousLocale !== nextLocale - ); - } - - componentWillUnmount() { - if (!this.dimensionsEventListener) { - return; - } - this.dimensionsEventListener.remove(); - } + }, []); /** * Get the Context menu anchor position @@ -98,15 +73,15 @@ class PopoverReportActionContextMenu extends React.Component { * * @returns {Promise} */ - getContextMenuMeasuredLocation() { + const getContextMenuMeasuredLocation = () => { return new Promise((resolve) => { - if (this.contextMenuAnchor) { - (this.contextMenuAnchor.current || this.contextMenuAnchor).measureInWindow((x, y) => resolve({x, y})); + if (contextMenuAnchor.current) { + (contextMenuAnchor.current || contextMenuAnchor).measureInWindow((x, y) => resolve({x, y})); } else { resolve({x: 0, y: 0}); } }); - } + }; /** * Whether Context Menu is active for the Report Action. @@ -114,13 +89,9 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Number|String} actionID * @return {Boolean} */ - isActiveReportAction(actionID) { - return Boolean(actionID) && (this.state.reportActionID === actionID || this.state.reportAction.reportActionID === actionID); - } - - clearActiveReportAction() { - this.setState({reportID: '0', reportAction: {}}); - } + const isActiveReportAction = (actionID) => { + return Boolean(actionID) && (reportActionIDRef.current === actionID || reportActionRef.current.reportActionID === actionID); + }; /** * Show the ReportActionContextMenu modal popover. @@ -132,7 +103,6 @@ class PopoverReportActionContextMenu extends React.Component { * @param {String} reportID - Active Report Id * @param {Object} reportActionID - ReportAction for ContextMenu * @param {String} originalReportID - The currrent Report Id of the reportAction - * @param {String} draftMessage - ReportAction Draftmessage * @param {Function} [onShow] - Run a callback when Menu is shown * @param {Function} [onHide] - Run a callback when Menu is hidden * @param {Boolean} isArchivedRoom - Whether the provided report is an archived room @@ -140,7 +110,7 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Boolean} isPinnedChat - Flag to check if the chat is pinned in the LHN. Used for the Pin/Unpin action * @param {Boolean} isUnreadChat - Flag to check if the chat is unread in the LHN. Used for the Mark as Read/Unread action */ - showContextMenu( + const showContextMenu = ( type, event, selection, @@ -155,130 +125,125 @@ class PopoverReportActionContextMenu extends React.Component { isChronosReport = false, isPinnedChat = false, isUnreadChat = false, - ) { + ) => { const nativeEvent = event.nativeEvent || {}; - this.contextMenuAnchor = contextMenuAnchor; - this.contextMenuTargetNode = nativeEvent.target; - - // Singleton behaviour of ContextMenu creates race conditions when user requests multiple contextMenus. - // But it is possible that every new request registers new callbacks thus instanceID is used to corelate those callbacks - this.instanceID = Math.random().toString(36).substr(2, 5); - - this.onPopoverShow = onShow; - this.onPopoverHide = onHide; - - this.getContextMenuMeasuredLocation().then(({x, y}) => { - this.setState({ - cursorRelativePosition: { - horizontal: nativeEvent.pageX - x, - vertical: nativeEvent.pageY - y, - }, - popoverAnchorPosition: { - horizontal: nativeEvent.pageX, - vertical: nativeEvent.pageY, - }, - type, - reportID, - reportActionID, - originalReportID, - selection, - isPopoverVisible: true, - reportActionDraftMessage: draftMessage, - isArchivedRoom, - isChronosReport, - isPinnedChat, - isUnreadChat, - }); + contextMenuAnchor.current = contextMenuAnchor; + contextMenuTargetNode.current = nativeEvent.target; + + setInstanceID(Math.random().toString(36).substr(2, 5)); + + onPopoverShow.current = onShow; + onPopoverHide.current = onHide; + + getContextMenuMeasuredLocation().then(({x, y}) => { + popoverAnchorPosition.current = { + horizontal: nativeEvent.pageX - x, + vertical: nativeEvent.pageY - y, + }; + + popoverAnchorPosition.current = { + horizontal: nativeEvent.pageX, + vertical: nativeEvent.pageY, + }; + typeRef.current = type; + reportIDRef.current = reportID; + reportActionIDRef.current = reportActionID; + originalReportIDRef.current = originalReportID; + selectionRef.current = selection; + setIsPopoverVisible(true); + reportActionDraftMessageRef.current = draftMessage; + setIsArchivedRoom(isArchivedRoom); + setIsChronosReport(isChronosReport); + setIsPinnedChat(isPinnedChat); + setIsUnreadChat(isUnreadChat); }); - } + }; /** * This gets called on Dimensions change to find the anchor coordinates for the action context menu. */ - measureContextMenuAnchorPosition() { - if (!this.state.isPopoverVisible) { + const measureContextMenuAnchorPosition = useCallback(() => { + if (!isPopoverVisible) { return; } - this.getContextMenuMeasuredLocation().then(({x, y}) => { + + getContextMenuMeasuredLocation().then(({x, y}) => { if (!x || !y) { return; } - this.setState((prev) => ({ - popoverAnchorPosition: { - horizontal: prev.cursorRelativePosition.horizontal + x, - vertical: prev.cursorRelativePosition.vertical + y, - }, - })); + + popoverAnchorPosition.current = { + horizontal: cursorRelativePosition.horizontal + x, + vertical: cursorRelativePosition.vertical + y, + }; }); - } + }, [isPopoverVisible, getContextMenuMeasuredLocation, cursorRelativePosition]); /** * After Popover shows, call the registered onPopoverShow callback and reset it */ - runAndResetOnPopoverShow() { - this.onPopoverShow(); + const runAndResetOnPopoverShow = () => { + onPopoverShow.current(); // After we have called the action, reset it. - this.onPopoverShow = () => {}; - } + onPopoverShow.current = () => {}; + }; /** * After Popover hides, call the registered onPopoverHide & onPopoverHideActionCallback callback and reset it */ - runAndResetOnPopoverHide() { - this.setState({reportID: '0', reportActionID: '0', originalReportID: '0'}, () => { - this.onPopoverHide = this.runAndResetCallback(this.onPopoverHide); - this.onPopoverHideActionCallback = this.runAndResetCallback(this.onPopoverHideActionCallback); - }); - } + const runAndResetOnPopoverHide = () => { + reportIDRef.current = '0'; + reportActionIDRef.current = '0'; + originalReportIDRef.current = '0'; + + onPopoverHide.current = runAndResetCallback(onPopoverHide.current); + onPopoverHideActionCallback.current = runAndResetCallback(onPopoverHideActionCallback.current); + }; /** * Hide the ReportActionContextMenu modal popover. * @param {Function} onHideActionCallback Callback to be called after popover is completely hidden */ - hideContextMenu(onHideActionCallback) { + const hideContextMenu = (onHideActionCallback) => { if (_.isFunction(onHideActionCallback)) { - this.onPopoverHideActionCallback = onHideActionCallback; + onPopoverHideActionCallback.current = onHideActionCallback; } - this.setState({ - selection: '', - reportActionDraftMessage: '', - isPopoverVisible: false, - }); - } + + selectionRef.current = ''; + reportActionDraftMessageRef.current = ''; + setIsPopoverVisible(false); + }; /** * Run the callback and return a noop function to reset it * @param {Function} callback * @returns {Function} */ - runAndResetCallback(callback) { + const runAndResetCallback = (callback) => { callback(); return () => {}; - } - - confirmDeleteAndHideModal() { - this.callbackWhenDeleteModalHide = () => (this.onComfirmDeleteModal = this.runAndResetCallback(this.onComfirmDeleteModal)); + }; - if (ReportActionsUtils.isMoneyRequestAction(this.state.reportAction)) { - IOU.deleteMoneyRequest(this.state.reportAction.originalMessage.IOUTransactionID, this.state.reportAction); + const confirmDeleteAndHideModal = useCallback(() => { + callbackWhenDeleteModalHide.current = () => (onComfirmDeleteModal.current = runAndResetCallback(onComfirmDeleteModal.current)); + if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { + IOU.deleteMoneyRequest(reportActionRef.current.originalMessage.IOUTransactionID, reportActionRef.current); } else { - Report.deleteReportComment(this.state.reportID, this.state.reportAction); + Report.deleteReportComment(reportIDRef.current, reportActionRef.current); } - this.setState({isDeleteCommentConfirmModalVisible: false}); - } - - hideDeleteModal() { - this.callbackWhenDeleteModalHide = () => (this.onCancelDeleteModal = this.runAndResetCallback(this.onCancelDeleteModal)); - this.setState({ - isDeleteCommentConfirmModalVisible: false, - shouldSetModalVisibilityForDeleteConfirmation: true, - isArchivedRoom: false, - isChronosReport: false, - isPinnedChat: false, - isUnreadChat: false, - }); - } + setIsDeleteCommentConfirmModalVisible(false); + }, [reportActionRef]); + + const hideDeleteModal = () => { + callbackWhenDeleteModalHide.current = () => (onCancelDeleteModal.current = runAndResetCallback(onCancelDeleteModal.current)); + setIsDeleteCommentConfirmModalVisible(false); + setShouldSetModalVisibilityForDeleteConfirmation(true); + setIsArchivedRoom(false); + setIsChronosReport(false); + setIsPinnedChat(false); + setIsUnreadChat(false); + }; /** * Opens the Confirm delete action modal @@ -288,67 +253,82 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Function} [onConfirm] * @param {Function} [onCancel] */ - showDeleteModal(reportID, reportAction, shouldSetModalVisibility = true, onConfirm = () => {}, onCancel = () => {}) { - this.onCancelDeleteModal = onCancel; - this.onComfirmDeleteModal = onConfirm; - this.setState({ - reportID, - reportAction, - shouldSetModalVisibilityForDeleteConfirmation: shouldSetModalVisibility, - isDeleteCommentConfirmModalVisible: true, - }); - } - - render() { - return ( - <> - - - - {}, onCancel = () => {}) => { + onCancelDeleteModal.current = onCancel; + onComfirmDeleteModal.current = onConfirm; + + reportIDRef.current = reportID; + reportActionRef.current = reportAction; + + setShouldSetModalVisibilityForDeleteConfirmation(shouldSetModalVisibility); + setIsDeleteCommentConfirmModalVisible(true); + }; + + useImperativeHandle(ref, () => ({ + showContextMenu, + hideContextMenu, + showDeleteModal, + hideDeleteModal, + isActiveReportAction, + instanceID, + runAndResetOnPopoverHide, + })); + + const reportAction = reportActionRef.current; + + return ( + <> + + - - ); - } -} + + { + reportIDRef.current = '0'; + reportActionRef.current = {}; + callbackWhenDeleteModalHide.current(); + }} + prompt={props.translate('reportActionContextMenu.deleteConfirmation', {action: reportAction})} + confirmText={props.translate('common.delete')} + cancelText={props.translate('common.cancel')} + danger + /> + + ); +}); PopoverReportActionContextMenu.propTypes = propTypes; +PopoverReportActionContextMenu.displayName = 'PopoverReportActionContextMenu'; export default withLocalize(PopoverReportActionContextMenu); From eebfe3dd363932ed4e5dd8e73e8966cde5327203 Mon Sep 17 00:00:00 2001 From: OlimpiaZurek Date: Tue, 12 Sep 2023 16:22:00 +0200 Subject: [PATCH 051/110] code improvements after cr --- .../PopoverReportActionContextMenu.js | 158 +++++++++--------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 4dbf5a32cac2..b57fc3f8e172 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -2,18 +2,15 @@ import React, {forwardRef, useEffect, useState, useRef, useImperativeHandle, use import {Dimensions} from 'react-native'; import _ from 'underscore'; import * as Report from '../../../../libs/actions/Report'; -import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; import PopoverWithMeasuredContent from '../../../../components/PopoverWithMeasuredContent'; import BaseReportActionContextMenu from './BaseReportActionContextMenu'; import ConfirmModal from '../../../../components/ConfirmModal'; import * as ReportActionsUtils from '../../../../libs/ReportActionsUtils'; import * as IOU from '../../../../libs/actions/IOU'; +import useLocalize from '../../../../hooks/useLocalize'; -const propTypes = { - ...withLocalizePropTypes, -}; - -const PopoverReportActionContextMenu = forwardRef((props, ref) => { +function PopoverReportActionContextMenu(_props, ref) { + const {translate} = useLocalize(); const reportIDRef = useRef('0'); const typeRef = useRef(''); const reportActionRef = useRef({}); @@ -38,15 +35,16 @@ const PopoverReportActionContextMenu = forwardRef((props, ref) => { const [isPopoverVisible, setIsPopoverVisible] = useState(false); const [isDeleteCommentConfirmModalVisible, setIsDeleteCommentConfirmModalVisible] = useState(false); const [shouldSetModalVisibilityForDeleteConfirmation, setShouldSetModalVisibilityForDeleteConfirmation] = useState(true); - const [isArchivedRoom, setIsArchivedRoom] = useState(false); - const [isChronosReport, setIsChronosReport] = useState(false); - const [isPinnedChat, setIsPinnedChat] = useState(false); - const [isUnreadChat, setIsUnreadChat] = useState(false); + + const [isRoomArchived, setIsRoomArchived] = useState(false); + const [isChronosReportEnabled, setIsChronosReportEnabled] = useState(false); + const [isChatPinned, setIsChatPinned] = useState(false); + const [hasUnreadMessages, setHasUnreadMessages] = useState(false); const contentRef = useRef(null); const anchorRef = useRef(null); const dimensionsEventListener = useRef(null); - const contextMenuAnchor = useRef(null); + const contextMenuAnchorRef = useRef(null); const contextMenuTargetNode = useRef(null); const onPopoverShow = useRef(() => {}); @@ -57,31 +55,54 @@ const PopoverReportActionContextMenu = forwardRef((props, ref) => { const onPopoverHideActionCallback = useRef(() => {}); const callbackWhenDeleteModalHide = useRef(() => {}); - useEffect(() => { - dimensionsEventListener.current = Dimensions.addEventListener('change', measureContextMenuAnchorPosition); - - return () => { - if (dimensionsEventListener.current) { - dimensionsEventListener.current.remove(); - } - }; - }, []); - /** * Get the Context menu anchor position * We calculate the achor coordinates from measureInWindow async method * * @returns {Promise} */ - const getContextMenuMeasuredLocation = () => { - return new Promise((resolve) => { - if (contextMenuAnchor.current) { - (contextMenuAnchor.current || contextMenuAnchor).measureInWindow((x, y) => resolve({x, y})); - } else { - resolve({x: 0, y: 0}); + const getContextMenuMeasuredLocation = useCallback( + () => + new Promise((resolve) => { + if (contextMenuAnchorRef.current && _.isFunction(contextMenuAnchorRef.current.measureInWindow)) { + contextMenuAnchorRef.current.measureInWindow((x, y) => resolve({x, y})); + } else { + resolve({x: 0, y: 0}); + } + }), + [], + ); + + /** + * This gets called on Dimensions change to find the anchor coordinates for the action context menu. + */ + const measureContextMenuAnchorPosition = useCallback(() => { + if (!isPopoverVisible) { + return; + } + + getContextMenuMeasuredLocation().then(({x, y}) => { + if (!x || !y) { + return; } + + popoverAnchorPosition.current = { + horizontal: cursorRelativePosition.horizontal + x, + vertical: cursorRelativePosition.vertical + y, + }; }); - }; + }, [isPopoverVisible, getContextMenuMeasuredLocation]); + + useEffect(() => { + dimensionsEventListener.current = Dimensions.addEventListener('change', measureContextMenuAnchorPosition); + + return () => { + if (!dimensionsEventListener.current) { + return; + } + dimensionsEventListener.current.remove(); + }; + }, [measureContextMenuAnchorPosition]); /** * Whether Context Menu is active for the Report Action. @@ -127,7 +148,7 @@ const PopoverReportActionContextMenu = forwardRef((props, ref) => { isUnreadChat = false, ) => { const nativeEvent = event.nativeEvent || {}; - contextMenuAnchor.current = contextMenuAnchor; + contextMenuAnchorRef.current = contextMenuAnchor; contextMenuTargetNode.current = nativeEvent.target; setInstanceID(Math.random().toString(36).substr(2, 5)); @@ -152,33 +173,13 @@ const PopoverReportActionContextMenu = forwardRef((props, ref) => { selectionRef.current = selection; setIsPopoverVisible(true); reportActionDraftMessageRef.current = draftMessage; - setIsArchivedRoom(isArchivedRoom); - setIsChronosReport(isChronosReport); - setIsPinnedChat(isPinnedChat); - setIsUnreadChat(isUnreadChat); + setIsRoomArchived(isArchivedRoom); + setIsChronosReportEnabled(isChronosReport); + setIsChatPinned(isPinnedChat); + setHasUnreadMessages(isUnreadChat); }); }; - /** - * This gets called on Dimensions change to find the anchor coordinates for the action context menu. - */ - const measureContextMenuAnchorPosition = useCallback(() => { - if (!isPopoverVisible) { - return; - } - - getContextMenuMeasuredLocation().then(({x, y}) => { - if (!x || !y) { - return; - } - - popoverAnchorPosition.current = { - horizontal: cursorRelativePosition.horizontal + x, - vertical: cursorRelativePosition.vertical + y, - }; - }); - }, [isPopoverVisible, getContextMenuMeasuredLocation, cursorRelativePosition]); - /** * After Popover shows, call the registered onPopoverShow callback and reset it */ @@ -189,6 +190,16 @@ const PopoverReportActionContextMenu = forwardRef((props, ref) => { onPopoverShow.current = () => {}; }; + /** + * Run the callback and return a noop function to reset it + * @param {Function} callback + * @returns {Function} + */ + const runAndResetCallback = (callback) => { + callback(); + return () => {}; + }; + /** * After Popover hides, call the registered onPopoverHide & onPopoverHideActionCallback callback and reset it */ @@ -215,19 +226,9 @@ const PopoverReportActionContextMenu = forwardRef((props, ref) => { setIsPopoverVisible(false); }; - /** - * Run the callback and return a noop function to reset it - * @param {Function} callback - * @returns {Function} - */ - const runAndResetCallback = (callback) => { - callback(); - return () => {}; - }; - const confirmDeleteAndHideModal = useCallback(() => { callbackWhenDeleteModalHide.current = () => (onComfirmDeleteModal.current = runAndResetCallback(onComfirmDeleteModal.current)); - if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { + if (ReportActionsUtils.isMoneyRequestAction(reportActionRef.current)) { IOU.deleteMoneyRequest(reportActionRef.current.originalMessage.IOUTransactionID, reportActionRef.current); } else { Report.deleteReportComment(reportIDRef.current, reportActionRef.current); @@ -239,10 +240,10 @@ const PopoverReportActionContextMenu = forwardRef((props, ref) => { callbackWhenDeleteModalHide.current = () => (onCancelDeleteModal.current = runAndResetCallback(onCancelDeleteModal.current)); setIsDeleteCommentConfirmModalVisible(false); setShouldSetModalVisibilityForDeleteConfirmation(true); - setIsArchivedRoom(false); - setIsChronosReport(false); - setIsPinnedChat(false); - setIsUnreadChat(false); + setIsRoomArchived(false); + setIsChronosReportEnabled(false); + setIsChatPinned(false); + setHasUnreadMessages(false); }; /** @@ -299,17 +300,17 @@ const PopoverReportActionContextMenu = forwardRef((props, ref) => { reportActionID={reportActionIDRef.current} draftMessage={reportActionDraftMessageRef.current} selection={selectionRef.current} - isArchivedRoom={isArchivedRoom} - isChronosReport={isChronosReport} - isPinnedChat={isPinnedChat} - isUnreadChat={isUnreadChat} + isArchivedRoom={isRoomArchived} + isChronosReport={isChronosReportEnabled} + isPinnedChat={isChatPinned} + isUnreadChat={hasUnreadMessages} anchor={contextMenuTargetNode} contentRef={contentRef} originalReportID={originalReportIDRef.current} /> { reportActionRef.current = {}; callbackWhenDeleteModalHide.current(); }} - prompt={props.translate('reportActionContextMenu.deleteConfirmation', {action: reportAction})} - confirmText={props.translate('common.delete')} - cancelText={props.translate('common.cancel')} + prompt={translate('reportActionContextMenu.deleteConfirmation', {action: reportAction})} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} danger /> ); -}); +} -PopoverReportActionContextMenu.propTypes = propTypes; PopoverReportActionContextMenu.displayName = 'PopoverReportActionContextMenu'; -export default withLocalize(PopoverReportActionContextMenu); +export default forwardRef(PopoverReportActionContextMenu); From 0f6938d2c569ffb51d73102a1cd0d5846522fe07 Mon Sep 17 00:00:00 2001 From: OlimpiaZurek Date: Tue, 12 Sep 2023 18:13:33 +0200 Subject: [PATCH 052/110] fix lint issue --- .../home/report/ContextMenu/PopoverReportActionContextMenu.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index b57fc3f8e172..5d680d2309e8 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -124,6 +124,7 @@ function PopoverReportActionContextMenu(_props, ref) { * @param {String} reportID - Active Report Id * @param {Object} reportActionID - ReportAction for ContextMenu * @param {String} originalReportID - The currrent Report Id of the reportAction + * @param {String} draftMessage - ReportAction Draftmessage * @param {Function} [onShow] - Run a callback when Menu is shown * @param {Function} [onHide] - Run a callback when Menu is hidden * @param {Boolean} isArchivedRoom - Whether the provided report is an archived room From a6f63a2b83d172efb79a55a18a66f6d7a45955ee Mon Sep 17 00:00:00 2001 From: OlimpiaZurek Date: Fri, 22 Sep 2023 12:34:57 +0200 Subject: [PATCH 053/110] fix conflicts --- .../ContextMenu/PopoverReportActionContextMenu.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 5d680d2309e8..0207b877f0c2 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -110,9 +110,13 @@ function PopoverReportActionContextMenu(_props, ref) { * @param {Number|String} actionID * @return {Boolean} */ - const isActiveReportAction = (actionID) => { - return Boolean(actionID) && (reportActionIDRef.current === actionID || reportActionRef.current.reportActionID === actionID); - }; + const isActiveReportAction = (actionID) => Boolean(actionID) && (reportActionIDRef.current === actionID || reportActionRef.current.reportActionID === actionID); + + + const clearActiveReportAction = () => { + reportActionIDRef.current = '0'; + reportActionRef.current = {}; + } /** * Show the ReportActionContextMenu modal popover. @@ -274,6 +278,7 @@ function PopoverReportActionContextMenu(_props, ref) { isActiveReportAction, instanceID, runAndResetOnPopoverHide, + clearActiveReportAction, })); const reportAction = reportActionRef.current; From 594bd6b1eb73a6d3b28147f91825ed7dad13d8ef Mon Sep 17 00:00:00 2001 From: OlimpiaZurek Date: Fri, 22 Sep 2023 12:43:54 +0200 Subject: [PATCH 054/110] prettier fix --- .../home/report/ContextMenu/PopoverReportActionContextMenu.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 0207b877f0c2..4f09df7330ff 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -112,11 +112,10 @@ function PopoverReportActionContextMenu(_props, ref) { */ const isActiveReportAction = (actionID) => Boolean(actionID) && (reportActionIDRef.current === actionID || reportActionRef.current.reportActionID === actionID); - const clearActiveReportAction = () => { reportActionIDRef.current = '0'; reportActionRef.current = {}; - } + }; /** * Show the ReportActionContextMenu modal popover. From 7786995f9a6390062b35cd3cff3f3f6fe25445ad Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 22 Sep 2023 12:54:14 +0200 Subject: [PATCH 055/110] Update comments --- .../actions/Device/getDeviceInfo/getOSAndName/index.native.ts | 2 ++ src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts | 2 ++ src/libs/actions/Device/getDeviceInfo/getOSAndName/types.ts | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts index 455487d056f2..bb9eb572570e 100644 --- a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts +++ b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts @@ -6,8 +6,10 @@ const getOSAndName: GetOSAndName = () => { const deviceName = RNDeviceInfo.getDeviceNameSync(); const prettyName = `${Str.UCFirst(RNDeviceInfo.getManufacturerSync() || '')} ${deviceName}`; return { + // Parameter names are predefined and we don't choose it here // eslint-disable-next-line @typescript-eslint/naming-convention device_name: RNDeviceInfo.isEmulatorSync() ? `Emulator - ${prettyName}` : prettyName, + // Parameter names are predefined and we don't choose it here // eslint-disable-next-line @typescript-eslint/naming-convention os_version: RNDeviceInfo.getSystemVersion(), }; diff --git a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts index 5155fc07afb2..d63c2fedc51d 100644 --- a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts +++ b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts @@ -2,8 +2,10 @@ import {getOSAndName as libGetOSAndName} from 'expensify-common/lib/Device'; import GetOSAndName from './types'; const getOSAndName: GetOSAndName = () => { + // Parameter names are predefined and we don't choose it here // eslint-disable-next-line @typescript-eslint/naming-convention const {device_name, os_version} = libGetOSAndName(); + // Parameter names are predefined and we don't choose it here // eslint-disable-next-line @typescript-eslint/naming-convention return {device_name, os_version}; }; diff --git a/src/libs/actions/Device/getDeviceInfo/getOSAndName/types.ts b/src/libs/actions/Device/getDeviceInfo/getOSAndName/types.ts index 6616a13d8d23..2ca67c3c59c3 100644 --- a/src/libs/actions/Device/getDeviceInfo/getOSAndName/types.ts +++ b/src/libs/actions/Device/getDeviceInfo/getOSAndName/types.ts @@ -1,4 +1,4 @@ -// to keep same behavior from before migration +// Parameter names are predefined and we don't choose it here // eslint-disable-next-line @typescript-eslint/naming-convention type GetOSAndName = () => {device_name: string | undefined; os_version: string | undefined}; export default GetOSAndName; From bb4509a226ae0ca2d4b2e2c8600dae4bc905c554 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Fri, 22 Sep 2023 14:34:28 +0200 Subject: [PATCH 056/110] getBillable change to false --- src/libs/TransactionUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index bbda53666af7..48bc79bbe128 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -264,7 +264,7 @@ function getCategory(transaction) { * @return {String} */ function getBillable(transaction) { - return lodashGet(transaction, 'billable', ''); + return lodashGet(transaction, 'billable', false); } /** From b0bdad61d495840f8ef683b0add4eb8d6527f499 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Fri, 22 Sep 2023 13:50:18 +0000 Subject: [PATCH 057/110] remove paypal paymentType --- src/libs/ReportUtils.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 0f4b1882e901..4b7acd121d0b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3611,9 +3611,6 @@ function getIouReportActionDisplayMessage(reportAction) { case CONST.IOU.PAYMENT_TYPE.ELSEWHERE: translationKey = 'iou.paidElsewhereWithAmount'; break; - case CONST.IOU.PAYMENT_TYPE.PAYPAL_ME: - translationKey = 'iou.paidUsingPaypalWithAmount'; - break; case CONST.IOU.PAYMENT_TYPE.EXPENSIFY: case CONST.IOU.PAYMENT_TYPE.VBBA: translationKey = 'iou.paidUsingExpensifyWithAmount'; From 0deecc606ab79f182e09b4a2c598a5f90d54e235 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Fri, 22 Sep 2023 14:46:26 +0000 Subject: [PATCH 058/110] fix payer name --- 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 4b7acd121d0b..dd5007b61af5 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3605,7 +3605,7 @@ function getIouReportActionDisplayMessage(reportAction) { const {amount, currency, IOUReportID} = originalMessage; const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); const iouReport = getReport(IOUReportID); - const payerName = isExpenseReport(iouReport) ? getPolicyName(iouReport, false, getPolicyName(iouReport)) : getDisplayNameForParticipant(iouReport.managerID); + const payerName = isExpenseReport(iouReport) ? getPolicyName(iouReport) : getDisplayNameForParticipant(iouReport.managerID); let translationKey; switch (originalMessage.paymentType) { case CONST.IOU.PAYMENT_TYPE.ELSEWHERE: From ac8aaa8b92b239a63e4284a9c16bb90d61588d6e Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 23 Sep 2023 01:06:34 +0530 Subject: [PATCH 059/110] fix error for zip code in bank account step --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f7c028d2a106..d2c0fd643927 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1064,7 +1064,7 @@ export default { noBankAccountSelected: 'Please choose an account', taxID: 'Please enter a valid tax ID number', website: 'Please enter a valid website', - zipCode: 'Please enter a valid zip code', + zipCode: `Incorrect zip code format. Acceptable format: ${CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}`, phoneNumber: 'Please enter a valid phone number', companyName: 'Please enter a valid legal business name', addressCity: 'Please enter a valid city', diff --git a/src/languages/es.ts b/src/languages/es.ts index a68f33a33730..d1f1294a316a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1079,7 +1079,7 @@ export default { noBankAccountSelected: 'Por favor, elige una cuenta bancaria', taxID: 'Por favor, introduce un número de identificación fiscal válido', website: 'Por favor, introduce un sitio web válido', - zipCode: 'Por favor, introduce un código postal válido', + zipCode: `Formato de código postal incorrecto. Formato aceptable: ${CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}`, phoneNumber: 'Por favor, introduce un teléfono válido', companyName: 'Por favor, introduce un nombre comercial legal válido', addressCity: 'Por favor, introduce una ciudad válida', From d5bf680b2ee99f3f57f452854f8075bbfd58df7d Mon Sep 17 00:00:00 2001 From: charan h s Date: Sat, 23 Sep 2023 09:34:09 +0530 Subject: [PATCH 060/110] delete documentedShortcuts --- src/libs/KeyboardShortcut/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/KeyboardShortcut/index.js b/src/libs/KeyboardShortcut/index.js index 37d85c7bfbfc..050ce7d32464 100644 --- a/src/libs/KeyboardShortcut/index.js +++ b/src/libs/KeyboardShortcut/index.js @@ -83,6 +83,9 @@ _.each(CONST.KEYBOARD_SHORTCUTS, (shortcut) => { */ function unsubscribe(displayName, callbackID) { eventHandlers[displayName] = _.reject(eventHandlers[displayName], (callback) => callback.id === callbackID); + if(_.has(documentedShortcuts, displayName) && _.size(eventHandlers[displayName]) === 0){ + delete documentedShortcuts[displayName] + } } /** From 2be206582fa08a43613d44fe5b57918418ccee60 Mon Sep 17 00:00:00 2001 From: charan h s Date: Sat, 23 Sep 2023 09:46:07 +0530 Subject: [PATCH 061/110] prettier --- src/libs/KeyboardShortcut/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/KeyboardShortcut/index.js b/src/libs/KeyboardShortcut/index.js index 050ce7d32464..f91c81a1b856 100644 --- a/src/libs/KeyboardShortcut/index.js +++ b/src/libs/KeyboardShortcut/index.js @@ -83,8 +83,8 @@ _.each(CONST.KEYBOARD_SHORTCUTS, (shortcut) => { */ function unsubscribe(displayName, callbackID) { eventHandlers[displayName] = _.reject(eventHandlers[displayName], (callback) => callback.id === callbackID); - if(_.has(documentedShortcuts, displayName) && _.size(eventHandlers[displayName]) === 0){ - delete documentedShortcuts[displayName] + if (_.has(documentedShortcuts, displayName) && _.size(eventHandlers[displayName]) === 0) { + delete documentedShortcuts[displayName]; } } From 834f927457211ece3d9c8e4f0d96de8162517ca7 Mon Sep 17 00:00:00 2001 From: c3024 Date: Sat, 23 Sep 2023 15:42:50 +0530 Subject: [PATCH 062/110] linkkey --- src/pages/workspace/WorkspaceNewRoomPage.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index c031fa8e6073..e6c2d09f6069 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -7,7 +7,6 @@ import withNavigationFocus, {withNavigationFocusPropTypes} from '../../component import * as Report from '../../libs/actions/Report'; import * as App from '../../libs/actions/App'; import useLocalize from '../../hooks/useLocalize'; -import useWindowDimensions from '../../hooks/useWindowDimensions'; import styles from '../../styles/styles'; import RoomNameInput from '../../components/RoomNameInput'; import Picker from '../../components/Picker'; @@ -71,7 +70,6 @@ const defaultProps = { function WorkspaceNewRoomPage(props) { const {translate} = useLocalize(); - const {isSmallScreenWidth} = useWindowDimensions(); const [visibility, setVisibility] = useState(CONST.REPORT.VISIBILITY.RESTRICTED); const [policyID, setPolicyID] = useState(null); const visibilityDescription = useMemo(() => translate(`newRoomPage.${visibility}Description`), [translate, visibility]); @@ -151,7 +149,7 @@ function WorkspaceNewRoomPage(props) { shouldShow={!Permissions.canUsePolicyRooms(props.betas) || !workspaceOptions.length} shouldShowBackButton={false} linkKey="workspace.emptyWorkspace.title" - onLinkPress={() => App.createWorkspaceAndNavigateToIt('', false, '', false, !isSmallScreenWidth)} + onLinkPress={() => App.createWorkspaceAndNavigateToIt('', false, '', false, false)} > Date: Mon, 25 Sep 2023 02:23:51 -0400 Subject: [PATCH 063/110] fix new tab opens when downloading offline --- src/components/AttachmentModal.js | 3 ++- src/pages/home/report/ContextMenu/ContextMenuActions.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 3f89f4032061..6464c18b727c 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -30,6 +30,7 @@ import useWindowDimensions from '../hooks/useWindowDimensions'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import useNativeDriver from '../libs/useNativeDriver'; +import * as NetworkStore from '../libs/Network/NetworkStore'; /** * Modal render prop component that exposes modal launching triggers that can be used @@ -350,7 +351,7 @@ function AttachmentModal(props) { downloadAttachment(source)} shouldShowCloseButton={!props.isSmallScreenWidth} shouldShowBackButton={props.isSmallScreenWidth} diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 2a65bc2e67ab..3a7bfeec684d 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -25,6 +25,7 @@ import * as Task from '../../../../libs/actions/Task'; import * as Localize from '../../../../libs/Localize'; import * as TransactionUtils from '../../../../libs/TransactionUtils'; import * as CurrencyUtils from '../../../../libs/CurrencyUtils'; +import * as NetworkStore from '../../../../libs/Network/NetworkStore'; /** * Gets the HTML version of the message in an action. @@ -101,7 +102,7 @@ export default [ shouldShow: (type, reportAction) => { const isAttachment = ReportActionsUtils.isReportActionAttachment(reportAction); const messageHtml = lodashGet(reportAction, ['message', 0, 'html']); - return isAttachment && messageHtml !== CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML && reportAction.reportActionID && !ReportActionsUtils.isMessageDeleted(reportAction); + return isAttachment && messageHtml !== CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML && reportAction.reportActionID && !ReportActionsUtils.isMessageDeleted(reportAction) && !NetworkStore.isOffline(); }, onPress: (closePopover, {reportAction}) => { const message = _.last(lodashGet(reportAction, 'message', [{}])); From 8d12ce33ef2385c2ca6a70cb5f9595563921b4f9 Mon Sep 17 00:00:00 2001 From: Steven KKC Date: Mon, 25 Sep 2023 02:48:32 -0400 Subject: [PATCH 064/110] fix lint error --- src/pages/home/report/ContextMenu/ContextMenuActions.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 3a7bfeec684d..8504ee036a0a 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -102,7 +102,13 @@ export default [ shouldShow: (type, reportAction) => { const isAttachment = ReportActionsUtils.isReportActionAttachment(reportAction); const messageHtml = lodashGet(reportAction, ['message', 0, 'html']); - return isAttachment && messageHtml !== CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML && reportAction.reportActionID && !ReportActionsUtils.isMessageDeleted(reportAction) && !NetworkStore.isOffline(); + return ( + isAttachment && + messageHtml !== CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML && + reportAction.reportActionID && + !ReportActionsUtils.isMessageDeleted(reportAction) && + !NetworkStore.isOffline() + ); }, onPress: (closePopover, {reportAction}) => { const message = _.last(lodashGet(reportAction, 'message', [{}])); From 33dd82fac2f864538d8757722c37497f056f7839 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 25 Sep 2023 16:20:16 +0700 Subject: [PATCH 065/110] fix lint --- src/pages/iou/MoneyRequestSelectorPage.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 3b92dbc245e0..c7a65215da58 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -74,10 +74,12 @@ function MoneyRequestSelectorPage(props) { useEffect(() => { if (prevSelectedTab === props.selectedTab) { - return; + return; } resetMoneyRequestInfo(); + // resetMoneyRequestInfo function is not added as dependencies since they don't change between renders + // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.selectedTab, prevSelectedTab]); return ( From f7e799fd5e1eae2f82d1e4529ed395422f13392b Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 25 Sep 2023 11:31:39 +0200 Subject: [PATCH 066/110] fix empty state of the status menu item --- src/pages/settings/Profile/CustomStatus/StatusPage.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index 807bd73cecc1..b490ee76bbd7 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -34,8 +34,16 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { const defaultEmoji = draftEmojiCode || currentUserEmojiCode; const defaultText = draftEmojiCode ? draftText : currentUserStatusText; - const customStatus = draftEmojiCode ? `${draftEmojiCode} ${draftText}` : `${currentUserEmojiCode || ''} ${currentUserStatusText || ''}`; const hasDraftStatus = !!draftEmojiCode || !!draftText; + const customStatus = useMemo(() => { + if (draftEmojiCode) { + return `${draftEmojiCode} ${draftText}`; + } + if (currentUserEmojiCode || currentUserStatusText) { + return `${currentUserEmojiCode || ''} ${currentUserStatusText || ''}`; + } + return ''; + }, [draftEmojiCode, draftText, currentUserEmojiCode, currentUserStatusText]); const clearStatus = () => { User.clearCustomStatus(); From f5378049ea92c08bd2b6b2ceb4ed6ef35168c574 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 25 Sep 2023 11:36:26 +0200 Subject: [PATCH 067/110] change style of Clear Status button --- src/pages/settings/Profile/CustomStatus/StatusPage.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index b490ee76bbd7..39d5bcc6d52c 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -7,7 +7,6 @@ import MenuItemWithTopDescription from '../../../../components/MenuItemWithTopDe import HeaderPageLayout from '../../../../components/HeaderPageLayout'; import * as Expensicons from '../../../../components/Icon/Expensicons'; import withLocalize from '../../../../components/withLocalize'; -import MenuItem from '../../../../components/MenuItem'; import Button from '../../../../components/Button'; import Text from '../../../../components/Text'; import Navigation from '../../../../libs/Navigation/Navigation'; @@ -98,12 +97,12 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { /> {(!!currentUserEmojiCode || !!currentUserStatusText) && ( - )} From 805bab22574989536d60720f83260636101ad0f1 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Mon, 25 Sep 2023 12:12:39 +0200 Subject: [PATCH 068/110] system message --- src/libs/ReportUtils.js | 10 ++++++++++ src/libs/TransactionUtils.js | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 138cdc2cecb8..87b721b816e0 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1599,6 +1599,11 @@ function getModifiedExpenseMessage(reportAction) { if (hasModifiedTag) { return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.tag, reportActionOriginalMessage.oldTag, Localize.translateLocal('common.tag'), true); } + + const hasModifiedBillable = _.has(reportActionOriginalMessage, 'oldBillable') && _.has(reportActionOriginalMessage, 'billable'); + if (hasModifiedBillable) { + return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.billable, reportActionOriginalMessage.oldBillable, Localize.translateLocal('common.billable'), true); + } } /** @@ -1649,6 +1654,11 @@ function getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, i originalMessage.tag = transactionChanges.tag; } + if (_.has(transactionChanges, 'billable')) { + originalMessage.oldBillable = TransactionUtils.getBillable(oldTransaction); + originalMessage.billable = transactionChanges.billable; + } + return originalMessage; } diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 5ec771c35b80..8fa48b2c7063 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -263,7 +263,7 @@ function getCategory(transaction) { } /** - * Return the billable from the transaction. This "billable" field has no "modified" complement. + * Return the billable field from the transaction. This "billable" field has no "modified" complement. * * @param {Object} transaction * @return {Boolean} From a561d7986703a932658a9c3efc130a03f9f14c76 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 25 Sep 2023 12:47:59 +0200 Subject: [PATCH 069/110] reduce margin above title --- src/pages/settings/Profile/CustomStatus/StatusPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index 39d5bcc6d52c..324d919fe35e 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -84,7 +84,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { backgroundColor={themeColors.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.STATUS]} footer={footerComponent} > - + {localize.translate('statusPage.setStatusTitle')} {localize.translate('statusPage.statusExplanation')} From aeb95dd2b253b2e743e7b14e66d0c0a401934f18 Mon Sep 17 00:00:00 2001 From: bartektomczyk Date: Thu, 21 Sep 2023 13:49:37 +0200 Subject: [PATCH 070/110] reafctor: migrated WalletStatementModal to function component --- .../WalletStatementModal/index.native.js | 101 +++++++++--------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/src/components/WalletStatementModal/index.native.js b/src/components/WalletStatementModal/index.native.js index 590431274da5..85fefb474145 100644 --- a/src/components/WalletStatementModal/index.native.js +++ b/src/components/WalletStatementModal/index.native.js @@ -1,24 +1,22 @@ -import React from 'react'; +import React, {useCallback, useRef} from 'react'; import {WebView} from 'react-native-webview'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import withLocalize from '../withLocalize'; +import {useNavigation} from '@react-navigation/native'; import ONYXKEYS from '../../ONYXKEYS'; -import compose from '../../libs/compose'; import {walletStatementPropTypes, walletStatementDefaultProps} from './WalletStatementModalPropTypes'; import FullScreenLoadingIndicator from '../FullscreenLoadingIndicator'; import * as Report from '../../libs/actions/Report'; -import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; -class WalletStatementModal extends React.Component { - constructor(props) { - super(props); +const IOU_ROUTES = [ROUTES.IOU_REQUEST, ROUTES.IOU_SEND]; +const renderLoading = () => ; - this.authToken = lodashGet(props, 'session.authToken', null); - this.navigate = this.navigate.bind(this); - } +function WalletStatementModal({statementPageURL, session}) { + const webViewRef = useRef(); + const authToken = lodashGet(session, 'authToken', null); + const {navigate} = useNavigation(); /** * Handles in-app navigation for webview links @@ -26,54 +24,53 @@ class WalletStatementModal extends React.Component { * @param {String} params.type * @param {String} params.url */ - navigate({type, url}) { - if (!this.webview || (type !== 'STATEMENT_NAVIGATE' && type !== 'CONCIERGE_NAVIGATE')) { - return; - } + const handleNavigationStateChange = useCallback( + ({type, url}) => { + if (!webViewRef.current || (type !== 'STATEMENT_NAVIGATE' && type !== 'CONCIERGE_NAVIGATE')) { + return; + } + + if (type === 'CONCIERGE_NAVIGATE') { + webViewRef.current.stopLoading(); + Report.navigateToConciergeChat(); + } - if (type === 'CONCIERGE_NAVIGATE') { - this.webview.stopLoading(); - Report.navigateToConciergeChat(); - } + if (type === 'STATEMENT_NAVIGATE' && url) { + const iouRoute = _.find(IOU_ROUTES, (item) => url.includes(item)); - if (type === 'STATEMENT_NAVIGATE' && url) { - const iouRoutes = [ROUTES.IOU_REQUEST, ROUTES.IOU_SEND]; - const navigateToIOURoute = _.find(iouRoutes, (iouRoute) => url.includes(iouRoute)); - if (navigateToIOURoute) { - this.webview.stopLoading(); - Navigation.navigate(navigateToIOURoute); + if (iouRoute) { + webViewRef.current.stopLoading(); + navigate(iouRoute); + } } - } - } + }, + [webViewRef, navigate], + ); - render() { - return ( - (this.webview = node)} - originWhitelist={['https://*']} - source={{ - uri: this.props.statementPageURL, - headers: { - Cookie: `authToken=${this.authToken}`, - }, - }} - incognito // 'incognito' prop required for Android, issue here https://github.com/react-native-webview/react-native-webview/issues/1352 - startInLoadingState - renderLoading={() => } - onNavigationStateChange={this.navigate} - /> - ); - } + return ( + + ); } +WalletStatementModal.displayName = 'WalletStatementModal'; WalletStatementModal.propTypes = walletStatementPropTypes; WalletStatementModal.defaultProps = walletStatementDefaultProps; -export default compose( - withLocalize, - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - }), -)(WalletStatementModal); +export default withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, +})(WalletStatementModal); From f01d1bee6e92a754ee8f5cec2027ba4b8c366d33 Mon Sep 17 00:00:00 2001 From: bartektomczyk Date: Fri, 22 Sep 2023 07:43:52 +0200 Subject: [PATCH 071/110] refactor: used Navigation instance instead of hook --- src/components/WalletStatementModal/index.native.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/WalletStatementModal/index.native.js b/src/components/WalletStatementModal/index.native.js index 85fefb474145..c050a7ba6557 100644 --- a/src/components/WalletStatementModal/index.native.js +++ b/src/components/WalletStatementModal/index.native.js @@ -3,12 +3,13 @@ import {WebView} from 'react-native-webview'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import {useNavigation} from '@react-navigation/native'; -import ONYXKEYS from '../../ONYXKEYS'; + import {walletStatementPropTypes, walletStatementDefaultProps} from './WalletStatementModalPropTypes'; import FullScreenLoadingIndicator from '../FullscreenLoadingIndicator'; import * as Report from '../../libs/actions/Report'; +import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; +import ONYXKEYS from '../../ONYXKEYS'; const IOU_ROUTES = [ROUTES.IOU_REQUEST, ROUTES.IOU_SEND]; const renderLoading = () => ; @@ -16,7 +17,6 @@ const renderLoading = () => ; function WalletStatementModal({statementPageURL, session}) { const webViewRef = useRef(); const authToken = lodashGet(session, 'authToken', null); - const {navigate} = useNavigation(); /** * Handles in-app navigation for webview links @@ -40,11 +40,11 @@ function WalletStatementModal({statementPageURL, session}) { if (iouRoute) { webViewRef.current.stopLoading(); - navigate(iouRoute); + Navigation.navigate(iouRoute); } } }, - [webViewRef, navigate], + [webViewRef], ); return ( From 54e0f2099d97e658503b7b75376d55752d43a3aa Mon Sep 17 00:00:00 2001 From: bartektomczyk Date: Fri, 22 Sep 2023 08:02:22 +0200 Subject: [PATCH 072/110] refactor: added message type to the const object --- src/CONST.ts | 4 ++++ src/components/WalletStatementModal/index.js | 5 +++-- src/components/WalletStatementModal/index.native.js | 5 +++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index c6ae9cc5928d..8b29e0273071 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -982,6 +982,10 @@ const CONST = { GOLD: 'GOLD', SILVER: 'SILVER', }, + STATEMENT_WEB_MESSAGE_TYPE: { + STATEMENT: 'STATEMENT_NAVIGATE', + CONCIERGE: 'CONCIERGE_NAVIGATE', + }, }, PLAID: { diff --git a/src/components/WalletStatementModal/index.js b/src/components/WalletStatementModal/index.js index 84109217b18f..119d77d008b9 100644 --- a/src/components/WalletStatementModal/index.js +++ b/src/components/WalletStatementModal/index.js @@ -12,6 +12,7 @@ import FullScreenLoadingIndicator from '../FullscreenLoadingIndicator'; import ROUTES from '../../ROUTES'; import Navigation from '../../libs/Navigation/Navigation'; import * as Report from '../../libs/actions/Report'; +import CONST from '../../CONST'; function WalletStatementModal({statementPageURL, session}) { const [isLoading, setIsLoading] = useState(true); @@ -27,11 +28,11 @@ function WalletStatementModal({statementPageURL, session}) { return; } - if (event.data.type === 'CONCIERGE_NAVIGATE') { + if (event.data.type === CONST.WALLET.STATEMENT_WEB_MESSAGE_TYPE.CONCIERGE) { Report.navigateToConciergeChat(); } - if (event.data.type === 'STATEMENT_NAVIGATE' && event.data.url) { + if (event.data.type === CONST.WALLET.STATEMENT_WEB_MESSAGE_TYPE.STATEMENT && event.data.url) { const iouRoutes = [ROUTES.IOU_REQUEST, ROUTES.IOU_SEND]; const navigateToIOURoute = _.find(iouRoutes, (iouRoute) => event.data.url.includes(iouRoute)); if (navigateToIOURoute) { diff --git a/src/components/WalletStatementModal/index.native.js b/src/components/WalletStatementModal/index.native.js index c050a7ba6557..01ef7905f368 100644 --- a/src/components/WalletStatementModal/index.native.js +++ b/src/components/WalletStatementModal/index.native.js @@ -10,6 +10,7 @@ import * as Report from '../../libs/actions/Report'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import ONYXKEYS from '../../ONYXKEYS'; +import CONST from '../../CONST'; const IOU_ROUTES = [ROUTES.IOU_REQUEST, ROUTES.IOU_SEND]; const renderLoading = () => ; @@ -30,12 +31,12 @@ function WalletStatementModal({statementPageURL, session}) { return; } - if (type === 'CONCIERGE_NAVIGATE') { + if (type === CONST.WALLET.STATEMENT_WEB_MESSAGE_TYPE.CONCIERGE) { webViewRef.current.stopLoading(); Report.navigateToConciergeChat(); } - if (type === 'STATEMENT_NAVIGATE' && url) { + if (type === CONST.WALLET.STATEMENT_WEB_MESSAGE_TYPE.STATEMENT && url) { const iouRoute = _.find(IOU_ROUTES, (item) => url.includes(item)); if (iouRoute) { From 47a5d08816ad73e9d1bfb515d968a5bc844678e7 Mon Sep 17 00:00:00 2001 From: bartektomczyk Date: Fri, 22 Sep 2023 14:07:47 +0200 Subject: [PATCH 073/110] refactor: changed conts variable name --- src/CONST.ts | 2 +- src/components/WalletStatementModal/index.js | 6 +++--- src/components/WalletStatementModal/index.native.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8b29e0273071..c7b49c36ab7e 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -982,7 +982,7 @@ const CONST = { GOLD: 'GOLD', SILVER: 'SILVER', }, - STATEMENT_WEB_MESSAGE_TYPE: { + WEB_MESSAGE_TYPE: { STATEMENT: 'STATEMENT_NAVIGATE', CONCIERGE: 'CONCIERGE_NAVIGATE', }, diff --git a/src/components/WalletStatementModal/index.js b/src/components/WalletStatementModal/index.js index 119d77d008b9..b4d87bf4e0e4 100644 --- a/src/components/WalletStatementModal/index.js +++ b/src/components/WalletStatementModal/index.js @@ -24,15 +24,15 @@ function WalletStatementModal({statementPageURL, session}) { * @param {MessageEvent} event */ const navigate = (event) => { - if (!event.data || !event.data.type || (event.data.type !== 'STATEMENT_NAVIGATE' && event.data.type !== 'CONCIERGE_NAVIGATE')) { + if (!event.data || !event.data.type || (event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE)) { return; } - if (event.data.type === CONST.WALLET.STATEMENT_WEB_MESSAGE_TYPE.CONCIERGE) { + if (event.data.type === CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE) { Report.navigateToConciergeChat(); } - if (event.data.type === CONST.WALLET.STATEMENT_WEB_MESSAGE_TYPE.STATEMENT && event.data.url) { + if (event.data.type === CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && event.data.url) { const iouRoutes = [ROUTES.IOU_REQUEST, ROUTES.IOU_SEND]; const navigateToIOURoute = _.find(iouRoutes, (iouRoute) => event.data.url.includes(iouRoute)); if (navigateToIOURoute) { diff --git a/src/components/WalletStatementModal/index.native.js b/src/components/WalletStatementModal/index.native.js index 01ef7905f368..b1e190781890 100644 --- a/src/components/WalletStatementModal/index.native.js +++ b/src/components/WalletStatementModal/index.native.js @@ -27,16 +27,16 @@ function WalletStatementModal({statementPageURL, session}) { */ const handleNavigationStateChange = useCallback( ({type, url}) => { - if (!webViewRef.current || (type !== 'STATEMENT_NAVIGATE' && type !== 'CONCIERGE_NAVIGATE')) { + if (!webViewRef.current || (type !== CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && type !== CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE)) { return; } - if (type === CONST.WALLET.STATEMENT_WEB_MESSAGE_TYPE.CONCIERGE) { + if (type === CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE) { webViewRef.current.stopLoading(); Report.navigateToConciergeChat(); } - if (type === CONST.WALLET.STATEMENT_WEB_MESSAGE_TYPE.STATEMENT && url) { + if (type === CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && url) { const iouRoute = _.find(IOU_ROUTES, (item) => url.includes(item)); if (iouRoute) { From 378247dd34473904f2619690a610c96a9b09a577 Mon Sep 17 00:00:00 2001 From: bartektomczyk Date: Mon, 25 Sep 2023 13:01:37 +0200 Subject: [PATCH 074/110] refactor: remove not needed empty line --- src/components/WalletStatementModal/index.native.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/WalletStatementModal/index.native.js b/src/components/WalletStatementModal/index.native.js index b1e190781890..38d1f90af00d 100644 --- a/src/components/WalletStatementModal/index.native.js +++ b/src/components/WalletStatementModal/index.native.js @@ -3,7 +3,6 @@ import {WebView} from 'react-native-webview'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; - import {walletStatementPropTypes, walletStatementDefaultProps} from './WalletStatementModalPropTypes'; import FullScreenLoadingIndicator from '../FullscreenLoadingIndicator'; import * as Report from '../../libs/actions/Report'; From 5c4ad5ff9334daf927853fe4e7af29e172c8e9be Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 25 Sep 2023 13:57:30 +0200 Subject: [PATCH 075/110] use MenuItem for clearStatus --- src/pages/settings/Profile/CustomStatus/StatusPage.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index 324d919fe35e..0fa231aec6f1 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -9,6 +9,7 @@ import * as Expensicons from '../../../../components/Icon/Expensicons'; import withLocalize from '../../../../components/withLocalize'; import Button from '../../../../components/Button'; import Text from '../../../../components/Text'; +import MenuItem from '../../../../components/MenuItem'; import Navigation from '../../../../libs/Navigation/Navigation'; import * as User from '../../../../libs/actions/User'; import MobileBackgroundImage from '../../../../../assets/images/money-stack.svg'; @@ -97,8 +98,9 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { /> {(!!currentUserEmojiCode || !!currentUserStatusText) && ( - Date: Mon, 25 Sep 2023 13:49:51 +0100 Subject: [PATCH 076/110] prevent glitching when attempting to select text on the Scan tab --- src/pages/iou/ReceiptSelector/index.js | 35 ++++++++++++++++++-------- src/styles/styles.js | 7 +++++- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index a817195fe8a3..21a9e46aa50c 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -1,5 +1,5 @@ -import {View, Text, PixelRatio} from 'react-native'; -import React, {useContext, useState} from 'react'; +import {View, Text, PanResponder, PixelRatio} from 'react-native'; +import React, {useContext, useRef, useState} from 'react'; import lodashGet from 'lodash/get'; import _ from 'underscore'; import PropTypes from 'prop-types'; @@ -130,6 +130,13 @@ function ReceiptSelector(props) { IOU.navigateToNextPage(iou, iouType, reportID, report, props.route.path); }; + const panResponder = useRef( + PanResponder.create({ + onMoveShouldSetPanResponder: () => true, + onPanResponderTerminationRequest: () => false, + }) + ).current; + return ( {!isDraggingOver ? ( @@ -144,15 +151,21 @@ function ReceiptSelector(props) { height={CONST.RECEIPT.ICON_SIZE} /> - {translate('receipt.upload')} - - {isSmallScreenWidth ? translate('receipt.chooseReceipt') : translate('receipt.dragReceiptBeforeEmail')} - - {isSmallScreenWidth ? null : translate('receipt.dragReceiptAfterEmail')} - + + {translate('receipt.upload')} + + {isSmallScreenWidth ? translate('receipt.chooseReceipt') : translate('receipt.dragReceiptBeforeEmail')} + + {isSmallScreenWidth ? null : translate('receipt.dragReceiptAfterEmail')} + + {({openPicker}) => ( ({ marginRight: 20, justifyContent: 'center', alignItems: 'center', - padding: 40, + paddingVertical: 40, gap: 4, flex: 1, }), + receiptViewTextContainer: { + paddingHorizontal: 40, + ...sizing.w100, + }, + cameraView: { flex: 1, overflow: 'hidden', From e62bffe9870721d48363748b4526fe9ad0bfe945 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Mon, 25 Sep 2023 15:54:43 +0200 Subject: [PATCH 077/110] change system message --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/ReportUtils.js | 7 ++++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index def4b351e112..d645e997a9d5 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -244,6 +244,7 @@ export default { merchant: 'Merchant', category: 'Category', billable: 'Billable', + nonBillable: 'Non-billable', tag: 'Tag', receipt: 'Receipt', replace: 'Replace', diff --git a/src/languages/es.ts b/src/languages/es.ts index a78a30375fe9..83cf47192343 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -234,6 +234,7 @@ export default { merchant: 'Comerciante', category: 'Categoría', billable: 'Facturable', + nonBillable: 'No facturable', tag: 'Etiqueta', receipt: 'Recibo', replace: 'Sustituir', diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 87b721b816e0..148fa775a122 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1602,7 +1602,7 @@ function getModifiedExpenseMessage(reportAction) { const hasModifiedBillable = _.has(reportActionOriginalMessage, 'oldBillable') && _.has(reportActionOriginalMessage, 'billable'); if (hasModifiedBillable) { - return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.billable, reportActionOriginalMessage.oldBillable, Localize.translateLocal('common.billable'), true); + return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.billable, reportActionOriginalMessage.oldBillable, Localize.translateLocal('iou.request'), true); } } @@ -1655,8 +1655,9 @@ function getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, i } if (_.has(transactionChanges, 'billable')) { - originalMessage.oldBillable = TransactionUtils.getBillable(oldTransaction); - originalMessage.billable = transactionChanges.billable; + const oldBillable = TransactionUtils.getBillable(oldTransaction); + originalMessage.oldBillable = oldBillable ? Localize.translateLocal('common.billable') : Localize.translateLocal('common.nonBillable'); + originalMessage.billable = transactionChanges.billable ? Localize.translateLocal('common.billable') : Localize.translateLocal('common.nonBillable'); } return originalMessage; From a35872e97a42969f857a995bfef3f6ba980a1316 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Mon, 25 Sep 2023 15:09:01 +0100 Subject: [PATCH 078/110] fix lint error --- src/pages/iou/ReceiptSelector/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 21a9e46aa50c..1748a745f8c8 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -134,7 +134,7 @@ function ReceiptSelector(props) { PanResponder.create({ onMoveShouldSetPanResponder: () => true, onPanResponderTerminationRequest: () => false, - }) + }), ).current; return ( From d3afa28f60ed868947162f35851b90068ccc54c2 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:54:05 +0200 Subject: [PATCH 079/110] rename getIOUReportActionDisplayMessage method --- src/libs/ReportUtils.js | 4 ++-- src/pages/home/report/ContextMenu/ContextMenuActions.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index dd5007b61af5..e04f49e06070 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3598,7 +3598,7 @@ function getReportPreviewDisplayTransactions(reportPreviewAction) { * @param {Object} reportAction report action * @returns {String} */ -function getIouReportActionDisplayMessage(reportAction) { +function getIOUReportActionDisplayMessage(reportAction) { const originalMessage = _.get(reportAction, 'originalMessage', {}); let displayMessage; if (originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) { @@ -3773,5 +3773,5 @@ export { getReportPreviewDisplayTransactions, getTransactionsWithReceipts, hasMissingSmartscanFields, - getIouReportActionDisplayMessage, + getIOUReportActionDisplayMessage, }; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index eafc0e03bc9b..64b7d39ed975 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -200,7 +200,7 @@ export default [ const modifyExpenseMessage = ReportUtils.getModifiedExpenseMessage(reportAction); Clipboard.setString(modifyExpenseMessage); } else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { - const displayMessage = ReportUtils.getIouReportActionDisplayMessage(reportAction); + const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction); Clipboard.setString(displayMessage); } else if (content) { const parser = new ExpensiMark(); From c021644089574c286452454afa7021acc1720049 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Mon, 25 Sep 2023 19:25:47 +0200 Subject: [PATCH 080/110] Remove refocus on error code --- src/components/MagicCodeInput.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index b8501f4d227c..433dbe2433c3 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -149,15 +149,6 @@ function MagicCodeInput(props) { useNetwork({onReconnect: validateAndSubmit}); - useEffect(() => { - if (!props.hasError) { - return; - } - - // Focus the last input if an error occurred to allow for corrections - inputRefs.current.focus(); - }, [props.hasError, props.maxLength]); - useEffect(() => { validateAndSubmit(); From 8ec88ffc25bc14794a91eb87dc657bfc5452e158 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Tue, 26 Sep 2023 01:02:24 +0200 Subject: [PATCH 081/110] fix policy when report is wrong --- 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 015707db71f2..5a75caac37a6 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -393,7 +393,7 @@ export default compose( // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, }, }), )(MoneyRequestConfirmPage); From fb1dde82dc4f24022f1b5ad2d448d0bfa49d4963 Mon Sep 17 00:00:00 2001 From: Steven Maksym <131836113+StevenKKC@users.noreply.github.com> Date: Mon, 25 Sep 2023 19:26:38 -0400 Subject: [PATCH 082/110] use network hook --- .../home/report/ContextMenu/ContextMenuActions.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 8504ee036a0a..5eefe6a486d0 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -25,7 +25,7 @@ import * as Task from '../../../../libs/actions/Task'; import * as Localize from '../../../../libs/Localize'; import * as TransactionUtils from '../../../../libs/TransactionUtils'; import * as CurrencyUtils from '../../../../libs/CurrencyUtils'; -import * as NetworkStore from '../../../../libs/Network/NetworkStore'; +import useNetwork from '../../../../hooks/useNetwork'; /** * Gets the HTML version of the message in an action. @@ -44,6 +44,8 @@ const CONTEXT_MENU_TYPES = { REPORT: 'REPORT', }; +const {isOffline} = useNetwork(); + // A list of all the context actions in this menu. export default [ { @@ -102,13 +104,7 @@ export default [ shouldShow: (type, reportAction) => { const isAttachment = ReportActionsUtils.isReportActionAttachment(reportAction); const messageHtml = lodashGet(reportAction, ['message', 0, 'html']); - return ( - isAttachment && - messageHtml !== CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML && - reportAction.reportActionID && - !ReportActionsUtils.isMessageDeleted(reportAction) && - !NetworkStore.isOffline() - ); + return isAttachment && messageHtml !== CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML && reportAction.reportActionID && !ReportActionsUtils.isMessageDeleted(reportAction) && !isOffline; }, onPress: (closePopover, {reportAction}) => { const message = _.last(lodashGet(reportAction, 'message', [{}])); From be4cdece7b7fb50db78b8f34cda23d99f775dcf4 Mon Sep 17 00:00:00 2001 From: Yonathan Evan Christy Date: Tue, 26 Sep 2023 12:24:24 +0700 Subject: [PATCH 083/110] Add `/get-assistance/*` url to show Smart App Banner --- .well-known/apple-app-site-association | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.well-known/apple-app-site-association b/.well-known/apple-app-site-association index c871764117ed..a9e2b0383691 100644 --- a/.well-known/apple-app-site-association +++ b/.well-known/apple-app-site-association @@ -68,6 +68,10 @@ "/": "/workspace/*", "comment": "Workspace Details" }, + { + "/": "/get-assistance/*", + "comment": "Get Assistance Pages" + }, { "/": "/teachersunite/*", "comment": "Teachers Unite!" From d717a24ceec835f5488745acdbda9ae5cb6781ae Mon Sep 17 00:00:00 2001 From: Steven Maksym <131836113+StevenKKC@users.noreply.github.com> Date: Tue, 26 Sep 2023 02:01:57 -0400 Subject: [PATCH 084/110] Revert wrong update --- .../home/report/ContextMenu/ContextMenuActions.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 5eefe6a486d0..8504ee036a0a 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -25,7 +25,7 @@ import * as Task from '../../../../libs/actions/Task'; import * as Localize from '../../../../libs/Localize'; import * as TransactionUtils from '../../../../libs/TransactionUtils'; import * as CurrencyUtils from '../../../../libs/CurrencyUtils'; -import useNetwork from '../../../../hooks/useNetwork'; +import * as NetworkStore from '../../../../libs/Network/NetworkStore'; /** * Gets the HTML version of the message in an action. @@ -44,8 +44,6 @@ const CONTEXT_MENU_TYPES = { REPORT: 'REPORT', }; -const {isOffline} = useNetwork(); - // A list of all the context actions in this menu. export default [ { @@ -104,7 +102,13 @@ export default [ shouldShow: (type, reportAction) => { const isAttachment = ReportActionsUtils.isReportActionAttachment(reportAction); const messageHtml = lodashGet(reportAction, ['message', 0, 'html']); - return isAttachment && messageHtml !== CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML && reportAction.reportActionID && !ReportActionsUtils.isMessageDeleted(reportAction) && !isOffline; + return ( + isAttachment && + messageHtml !== CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML && + reportAction.reportActionID && + !ReportActionsUtils.isMessageDeleted(reportAction) && + !NetworkStore.isOffline() + ); }, onPress: (closePopover, {reportAction}) => { const message = _.last(lodashGet(reportAction, 'message', [{}])); From 71acc4fb0a534b8de1aca1bb23e818410f1cf4a5 Mon Sep 17 00:00:00 2001 From: Steven Maksym <131836113+StevenKKC@users.noreply.github.com> Date: Tue, 26 Sep 2023 02:07:49 -0400 Subject: [PATCH 085/110] use network hook - (2) --- src/components/AttachmentModal.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 6464c18b727c..d643cd647709 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -30,7 +30,7 @@ import useWindowDimensions from '../hooks/useWindowDimensions'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import useNativeDriver from '../libs/useNativeDriver'; -import * as NetworkStore from '../libs/Network/NetworkStore'; +import useNetwork from '../hooks/useNetwork'; /** * Modal render prop component that exposes modal launching triggers that can be used @@ -122,6 +122,7 @@ function AttachmentModal(props) { : undefined, ); const {translate} = useLocalize(); + const {isOffline} = useNetwork(); const onCarouselAttachmentChange = props.onCarouselAttachmentChange; @@ -351,7 +352,7 @@ function AttachmentModal(props) { downloadAttachment(source)} shouldShowCloseButton={!props.isSmallScreenWidth} shouldShowBackButton={props.isSmallScreenWidth} From 982672c487917a9216b74bb3a45e4ef2831367fd Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 26 Sep 2023 09:48:11 +0200 Subject: [PATCH 086/110] Adjust code after review --- src/libs/ErrorUtils.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 66dbd923a82d..bf4fc0d810a4 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -2,9 +2,10 @@ import CONST from '../CONST'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; import Response from '../types/onyx/Response'; -import {ErrorFields} from '../types/onyx/OnyxCommon'; +import {ErrorFields, Errors} from '../types/onyx/OnyxCommon'; +import {TranslationFlatObject} from '../languages/types'; -function getAuthenticateErrorMessage(response: Response): string { +function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatObject { switch (response.jsonCode) { case CONST.JSON_CODE.UNABLE_TO_RETRY: return 'session.offlineMessageRetry'; @@ -42,7 +43,7 @@ function getMicroSecondOnyxError(error: string): Record { } type OnyxDataWithErrors = { - errors: Record; + errors?: Errors; }; function getLatestErrorMessage(onyxData: TOnyxData): string { @@ -58,11 +59,11 @@ function getLatestErrorMessage(onyxData: T } type OnyxDataWithErrorFields = { - errorFields: ErrorFields; + errorFields?: ErrorFields; }; function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Record { - const errorsForField = onyxData.errorFields[fieldName] ?? {}; + const errorsForField = onyxData.errorFields?.[fieldName] ?? {}; if (Object.keys(errorsForField).length === 0) { return {}; @@ -74,7 +75,7 @@ function getLatestErrorField(onyxData } function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Record { - const errorsForField = onyxData.errorFields[fieldName] ?? {}; + const errorsForField = onyxData.errorFields?.[fieldName] ?? {}; if (Object.keys(errorsForField).length === 0) { return {}; From 54e6efeb9a2dcf7b607e85a18d381f909a1dd802 Mon Sep 17 00:00:00 2001 From: Jason Li <30815269+jliexpensify@users.noreply.github.com> Date: Tue, 26 Sep 2023 17:53:51 +1000 Subject: [PATCH 087/110] Update Create-Expenses.md in Expensify Help --- .../get-paid-back/expenses/Create-Expenses.md | 125 +++++++++++++++++- 1 file changed, 122 insertions(+), 3 deletions(-) diff --git a/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md b/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md index 8323be7b8e3f..e565e59dc754 100644 --- a/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md @@ -1,5 +1,124 @@ --- -title: Create Expenses -description: Create Expenses +title: Create-Expenses.md +description: This is an article that shows you all the ways that you can create Expenses in Expensify! --- -## Resource Coming Soon! + + +# About +Whether you're using SmartScan for automatic expense creation, or manually creating, splitting, or duplicating expenses, you can rest assured your expenses will be correctly tracked in Expensify. + +# How-to Create Expenses +## Using SmartScan +Use the big green camera button within the Expensify mobile app to snap a photo of your physical receipt to have it SmartScanned. +For digital or emailed receipts, simply forward them to receipts@expensify.com and it will be SmartScanned and added to your Expensify account. + +There’s no need to keep the app open and most SmartScans are finished within the hour. If more details are needed, Concierge will reach out to you with a friendly message. +## Using the Mobile App +Simply tap the **+** icon in the top-right corner +Choose **Expense** and then select **Manually Create**. +If you don't have a receipt handy or want to add it later, fill in your expense details and click the **Save** button. +## Using the Expensify Website +Log into the Expensify website +Click on the **Expenses** page and find the **New Expense** dropdown. +Select your expense type, hit the **Save** button and you're all set. +You can then add details like the Merchant and Category, attach a receipt image, and even add a description. +# How to Split an Expense +Splitting an expense in Expensify allows you to break down a single expense into multiple expenses. Each split expense is treated as an individual expense which can be categorized and tagged separately. The same receipt image will be attached to all of the split expenses, allowing you to divide a single expense into smaller, more manageable expenses. +To split an expense on the mobile app: + +1. Open an expense. +2. At the bottom of the screen, tap **More Options**. +3. Then, use the **Split** button to divide the expense. + +To split an expense on the Expensify website: + +1. Click on the expense you want to split. +2. Click on the **Split** button. + - On the Expenses page, this button is at the top. + - Within an individual expense, you'll find it at the bottom. +3. This will automatically be split in two, but you can decide how many expenses you want to split it into by clicking on the **Add Split** button. + - Remember, the total of all pieces must add up to the original expense amount, and no piece can have a $0.00 amount (or you won't be able to save the changes). + +# How to Create Bulk Expenses + +If you have multiple saved receipt images or PDFs to upload, you can drag and drop them onto your Expenses page in batches of ten - this will start the SmartScan process for all of them. + +You can also create a number of future 'placeholder' expenses for your recurring expenses (such as recurring bills or subscriptions) which you don't have receipts for by clicking *New Expense > Create Multiple* to quickly add multiple expenses in batches of up to ten. + +# How to Edit Bulk Expenses +Editing expenses in bulk will allow you to apply the same coding across multiple expenses and is a web-only feature. To bulk edit expenses: +Go to the Expenses page. +To narrow down your selection, use the filters (e.g. "Merchant" and "Open") to find the specific expenses you want to edit. +Select all the expenses you want to edit. +Click on the **Edit Multiple** button at the top of the page. +# How to Edit Expenses on a Report +If you’d like to edit expenses within an Open report: + +1. Click on the Report containing all the expenses. +2. Click on **Details**. +3. Click on the Pencil icon. +3. Select the **Edit Multiple** button. + +If you've already submitted your report, you'll need to Retract it or have it Unapproved first before you can edit the expenses. + + +# FAQ +## Does Expensify account for duplicates? + +Yes, Expensify will account for duplicates. Expensify works behind the scenes to identify duplicate expenses before they are submitted, warning employees when they exist. If a duplicate expense is submitted, the same warning will be shown to the approver responsible for reviewing the report. + +If two expenses are SmartScanned on the same day for the same amount, they will be flagged as duplicates unless: +The expenses were split from a single expense, +The expenses were imported from a credit card, or +Matching email receipts sent to receipts@expensify.com were received with different timestamps. +## How do I resolve a duplicate expense? + +If Concierge has let you know it's flagged a receipt as a duplicate, scanning the receipt again will trigger the same duplicate flagging.Users have the ability to resolve duplicates by either deleting the duplicated transactions, merging them, or ignoring them (if they are legitimately separate expenses of the same date and amount). + +## How do I recover a duplicate or undelete an expense? + +To recover a duplicate or undelete an expense: +Log into your Expensify account on the website and navigate to the Expenses page +Use the filters to search for deleted expenses by selecting the "Deleted" filter +Select the checkbox next to the expenses you want to restore +Click the **Undelete** button and you're all set. You’ll find the expense on your Expenses page again. + +# Deep Dive + +## What are the different Expense statuses? + +There are a number of different expense statuses in Expensify: +1. **Unreported**: Unreported expenses are not yet part of a report (and therefore unsubmitted) and are not viewable by anyone but the expense creator/owner. +2. **Open**: Open expenses are on a report that's still in progress, and are unsubmitted. Your Policy Admin will be able to view them, making it a collaborative step toward reimbursement. +3. **Processing**: Processing expenses are submitted, but waiting for approval. +4. **Approved**: If it's a non-reimbursable expense, the workflow is complete at this point. If it's a reimbursable expense, you're one step closer to getting paid. +5. **Reimbursed**: Reimbursed expenses are fully settled. You can check the Report Comments to see when you'll get paid. +6. **Closed**: Sometimes an expense accidentally ends up on your Individual Policy, falling into the Closed status. You’ll need to reopen the report and change the Policy by clicking on the **Details** tab in order to resubmit your report. +## What are Violations? + +Violations represent errors or discrepancies that Expensify has picked up and need to be corrected before a report can be successfully submitted. The one exception is when an expense comment is added, it will override the violation - as the user is providing a valid reason for submission. + +To enable or configure violations according to your policy, go to **Settings > Policies > _Policy Name_ > Expenses > Expense Violations**. Keep in mind that Expensify includes certain system mandatory violations that can't be disabled, even if your policy has violations turned off. + +You can spot violations by the exclamation marks (!) attached to expenses. Hovering over the symbol will provide a brief description and you can find more detailed information below the list of expenses. The two types of violations are: +**Red**: These indicate violations directly tied to your report's Policy settings. They are clear rule violations that must be addressed before submission. +**Yellow**: Concierge will highlight items that require attention but may not necessarily need corrective action. For example, if a receipt was SmartScanned and then the amount was modified, we’ll bring it to your attention so that it can be manually reviewed. +## How to Track Attendees + +Attendee tracking makes it easy to track shared expenses and maintain transparency in your group spending. + +Internal attendees are considered users within your policies or domain. To add internal attendees on mobile or web: +1. Click or tap the **Attendee** field within your expense. +2. Select the internal attendees you'd like to add from the list of searchable users. +3. You can continue adding more attendees or save the Expense. + +External attendees are considered users outside your group policy or domain. To add external attendees: +1. Click or tap the **Attendee** field within your expense. +2. Type in the individual's name or email address. +3. Tap **Add** to include the attendee. +You can continue adding more attendees or save the Expense. +To remove an attendee from an expense: +Open the expense. +Click or tap the **Attendees** field to display the list of attendees. +From the list, de-select the attendees you'd like to remove from the expense. + From 114e04737df0eef4fe6b931dbbba93c551e539c3 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 26 Sep 2023 11:14:30 +0300 Subject: [PATCH 088/110] Adding unit tests for isToday, isTomorrow and isYesterday --- src/libs/DateUtils.js | 3 +++ tests/unit/DateUtilsTest.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index 8ec19834e790..376c38c06e39 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -384,6 +384,9 @@ const DateUtils = { subtractMillisecondsFromDateTime, getDateStringFromISOTimestamp, getStatusUntilDate, + isToday, + isTomorrow, + isYesterday, }; export default DateUtils; diff --git a/tests/unit/DateUtilsTest.js b/tests/unit/DateUtilsTest.js index d17c1c052929..aca4b16e3823 100644 --- a/tests/unit/DateUtilsTest.js +++ b/tests/unit/DateUtilsTest.js @@ -1,5 +1,5 @@ import Onyx from 'react-native-onyx'; -import {format as tzFormat} from 'date-fns-tz'; +import {format as tzFormat, utcToZonedTime} from 'date-fns-tz'; import {addMinutes, subHours, subMinutes, subSeconds, format, setMinutes, setHours, subDays, addDays} from 'date-fns'; import CONST from '../../src/CONST'; import DateUtils from '../../src/libs/DateUtils'; @@ -130,6 +130,34 @@ describe('DateUtils', () => { expect(result).toBe(expectedDateTime); }); + describe('Date Comparison Functions', () => { + const today = new Date(); + const tomorrow = addDays(today, 1); + const yesterday = subDays(today, 1); + + const todayInTimezone = utcToZonedTime(today, timezone); + const tomorrowInTimezone = utcToZonedTime(tomorrow, timezone); + const yesterdayInTimezone = utcToZonedTime(yesterday, timezone); + + it('isToday should correctly identify today', () => { + expect(DateUtils.isToday(todayInTimezone, timezone)).toBe(true); + expect(DateUtils.isToday(tomorrowInTimezone, timezone)).toBe(false); + expect(DateUtils.isToday(yesterdayInTimezone, timezone)).toBe(false); + }); + + it('isTomorrow should correctly identify tomorrow', () => { + expect(DateUtils.isTomorrow(tomorrowInTimezone, timezone)).toBe(true); + expect(DateUtils.isTomorrow(todayInTimezone, timezone)).toBe(false); + expect(DateUtils.isTomorrow(yesterdayInTimezone, timezone)).toBe(false); + }); + + it('isYesterday should correctly identify yesterday', () => { + expect(DateUtils.isYesterday(yesterdayInTimezone, timezone)).toBe(true); + expect(DateUtils.isYesterday(todayInTimezone, timezone)).toBe(false); + expect(DateUtils.isYesterday(tomorrowInTimezone, timezone)).toBe(false); + }); + }); + describe('getDBTime', () => { it('should return the date in the format expected by the database', () => { const getDBTime = DateUtils.getDBTime(); From e619c60dd0499eec7ae15b7c14ca74963bde396d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Sep 2023 15:17:41 +0700 Subject: [PATCH 089/110] merge main --- src/ROUTES.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 01c7c25caa01..78d5f4d54888 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -243,12 +243,12 @@ export default { IOU_SEND_ADD_DEBIT_CARD: 'send/new/add-debit-card', IOU_SEND_ENABLE_PAYMENTS: 'send/new/enable-payments', - NEW_TASK: 'new/task', - NEW_TASK_ASSIGNEE: 'new/task/assignee', - NEW_TASK_SHARE_DESTINATION: 'new/task/share-destination', - NEW_TASK_DETAILS: 'new/task/details', - NEW_TASK_TITLE: 'new/task/title', - NEW_TASK_DESCRIPTION: 'new/task/description', + NEW_TASK: 'new/task', + NEW_TASK_ASSIGNEE: 'new/task/assignee', + NEW_TASK_SHARE_DESTINATION: 'new/task/share-destination', + NEW_TASK_DETAILS: 'new/task/details', + NEW_TASK_TITLE: 'new/task/title', + NEW_TASK_DESCRIPTION: 'new/task/description', TEACHERS_UNITE: 'teachersunite', I_KNOW_A_TEACHER: 'teachersunite/i-know-a-teacher', From 6aa803f3127f5eaa70b6894d295d9b86c1dd2be7 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz <42337257+wojtus7@users.noreply.github.com> Date: Tue, 26 Sep 2023 10:40:16 +0200 Subject: [PATCH 090/110] Update src/components/MagicCodeInput.js Co-authored-by: Daniel Gale-Rosen <5487802+dangrous@users.noreply.github.com> --- src/components/MagicCodeInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 433dbe2433c3..1db1acddc5d7 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -302,7 +302,7 @@ function MagicCodeInput(props) { onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); }} > - {/* Android does not handle touch on invisible Views so I created wrapper around inivisble TextInput just to handle taps */} + {/* Android does not handle touch on invisible Views so I created a wrapper around invisible TextInput just to handle taps */} Date: Tue, 26 Sep 2023 15:44:11 +0700 Subject: [PATCH 091/110] fix jest test --- src/ROUTES.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 78d5f4d54888..1b2e9330869f 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -244,6 +244,7 @@ export default { IOU_SEND_ENABLE_PAYMENTS: 'send/new/enable-payments', NEW_TASK: 'new/task', + NEW_TASK_WITH_REPORT_ID: 'new/task/:reportID?', NEW_TASK_ASSIGNEE: 'new/task/assignee', NEW_TASK_SHARE_DESTINATION: 'new/task/share-destination', NEW_TASK_DETAILS: 'new/task/details', From 184edfcc41a27ac142fff758af4267526ce8f22e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Sep 2023 15:44:57 +0700 Subject: [PATCH 092/110] fix lint --- src/ROUTES.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1b2e9330869f..78d5f4d54888 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -244,7 +244,6 @@ export default { IOU_SEND_ENABLE_PAYMENTS: 'send/new/enable-payments', NEW_TASK: 'new/task', - NEW_TASK_WITH_REPORT_ID: 'new/task/:reportID?', NEW_TASK_ASSIGNEE: 'new/task/assignee', NEW_TASK_SHARE_DESTINATION: 'new/task/share-destination', NEW_TASK_DETAILS: 'new/task/details', From 8d571a705a668d1fc153ada5be96ab2fb6840c7f Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 26 Sep 2023 16:47:39 +0700 Subject: [PATCH 093/110] remove PressableWithFeedback wrap button choose file --- src/pages/iou/ReceiptSelector/index.js | 31 +++++++++++--------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index a817195fe8a3..27e5d01847d5 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -8,7 +8,6 @@ import * as IOU from '../../../libs/actions/IOU'; import reportPropTypes from '../../reportPropTypes'; import CONST from '../../../CONST'; import ReceiptUpload from '../../../../assets/images/receipt-upload.svg'; -import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback'; import Button from '../../../components/Button'; import styles from '../../../styles/styles'; import CopyTextToClipboard from '../../../components/CopyTextToClipboard'; @@ -155,24 +154,20 @@ function ReceiptSelector(props) { {({openPicker}) => ( - -