From cd8ca07937025d621e7fb6802526e2a64b08aff5 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 7 Oct 2023 12:53:52 +0530 Subject: [PATCH 001/699] Convert StatePicker modal to StateSelection page --- src/ROUTES.ts | 16 ++ .../StatePicker/StateSelectorModal.js | 112 -------------- src/components/StatePicker/index.js | 97 ------------- src/components/StateSelector.js | 87 +++++++++++ .../AppNavigator/ModalStackNavigators.js | 1 + src/libs/Navigation/linkingConfig.js | 4 + src/libs/getStateFromRoute.ts | 13 ++ src/pages/ReimbursementAccount/AddressForm.js | 4 +- src/pages/ReimbursementAccount/CompanyStep.js | 23 ++- .../ReimbursementAccountPage.js | 1 + .../Profile/PersonalDetails/AddressPage.js | 22 ++- .../PersonalDetails/StateSelectionPage.js | 137 ++++++++++++++++++ src/pages/settings/Wallet/AddDebitCardPage.js | 19 ++- src/stories/Form.stories.js | 6 +- 14 files changed, 317 insertions(+), 225 deletions(-) delete mode 100644 src/components/StatePicker/StateSelectorModal.js delete mode 100644 src/components/StatePicker/index.js create mode 100644 src/components/StateSelector.js create mode 100644 src/libs/getStateFromRoute.ts create mode 100644 src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b2dafa643b22..642e8ec8a2d0 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -94,6 +94,22 @@ export default { return route; }, }, + SETTINGS_PERSONAL_DETAILS_ADDRESS_STATE: { + route: 'settings/profile/personal-details/address/state', + getRoute: (state: string, backTo?: string, label?: string, stateParamName = 'state') => { + let route = `settings/profile/personal-details/address/state?state=${state}`; + if (backTo) { + route += `&backTo=${encodeURIComponent(backTo)}`; + } + if (label) { + route += `&label=${encodeURIComponent(label)}`; + } + if (stateParamName) { + route += `&stateParamName=${encodeURIComponent(stateParamName)}`; + } + return route; + }, + }, SETTINGS_CONTACT_METHODS: 'settings/profile/contact-methods', SETTINGS_CONTACT_METHOD_DETAILS: { route: 'settings/profile/contact-methods/:contactMethod/details', diff --git a/src/components/StatePicker/StateSelectorModal.js b/src/components/StatePicker/StateSelectorModal.js deleted file mode 100644 index 378dcc4ebc8b..000000000000 --- a/src/components/StatePicker/StateSelectorModal.js +++ /dev/null @@ -1,112 +0,0 @@ -import _ from 'underscore'; -import React, {useMemo, useEffect} from 'react'; -import PropTypes from 'prop-types'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import CONST from '../../CONST'; -import Modal from '../Modal'; -import HeaderWithBackButton from '../HeaderWithBackButton'; -import SelectionList from '../SelectionList'; -import useLocalize from '../../hooks/useLocalize'; -import ScreenWrapper from '../ScreenWrapper'; -import styles from '../../styles/styles'; -import searchCountryOptions from '../../libs/searchCountryOptions'; -import StringUtils from '../../libs/StringUtils'; - -const propTypes = { - /** Whether the modal is visible */ - isVisible: PropTypes.bool.isRequired, - - /** State value selected */ - currentState: PropTypes.string, - - /** Function to call when the user selects a State */ - onStateSelected: PropTypes.func, - - /** Function to call when the user closes the State modal */ - onClose: PropTypes.func, - - /** The search value from the selection list */ - searchValue: PropTypes.string.isRequired, - - /** Function to call when the user types in the search input */ - setSearchValue: PropTypes.func.isRequired, - - /** Label to display on field */ - label: PropTypes.string, -}; - -const defaultProps = { - currentState: '', - onClose: () => {}, - onStateSelected: () => {}, - label: undefined, -}; - -function StateSelectorModal({currentState, isVisible, onClose, onStateSelected, searchValue, setSearchValue, label}) { - const {translate} = useLocalize(); - - useEffect(() => { - if (isVisible) { - return; - } - setSearchValue(''); - }, [isVisible, setSearchValue]); - - const countryStates = useMemo( - () => - _.map(_.keys(COMMON_CONST.STATES), (state) => { - const stateName = translate(`allStates.${state}.stateName`); - const stateISO = translate(`allStates.${state}.stateISO`); - return { - value: stateISO, - keyForList: stateISO, - text: stateName, - isSelected: currentState === stateISO, - searchValue: StringUtils.sanitizeString(`${stateISO}${stateName}`), - }; - }), - [translate, currentState], - ); - - const searchResults = searchCountryOptions(searchValue, countryStates); - const headerMessage = searchValue.trim() && !searchResults.length ? translate('common.noResultsFound') : ''; - - return ( - - - - - - - ); -} - -StateSelectorModal.propTypes = propTypes; -StateSelectorModal.defaultProps = defaultProps; -StateSelectorModal.displayName = 'StateSelectorModal'; - -export default StateSelectorModal; diff --git a/src/components/StatePicker/index.js b/src/components/StatePicker/index.js deleted file mode 100644 index f7f894af2a07..000000000000 --- a/src/components/StatePicker/index.js +++ /dev/null @@ -1,97 +0,0 @@ -import React, {useState} from 'react'; -import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import styles from '../../styles/styles'; -import MenuItemWithTopDescription from '../MenuItemWithTopDescription'; -import useLocalize from '../../hooks/useLocalize'; -import FormHelpMessage from '../FormHelpMessage'; -import StateSelectorModal from './StateSelectorModal'; -import refPropTypes from '../refPropTypes'; - -const propTypes = { - /** Error text to display */ - errorText: PropTypes.string, - - /** State to display */ - value: PropTypes.string, - - /** Callback to call when the input changes */ - onInputChange: PropTypes.func, - - /** A ref to forward to MenuItemWithTopDescription */ - forwardedRef: refPropTypes, - - /** Label to display on field */ - label: PropTypes.string, -}; - -const defaultProps = { - value: undefined, - forwardedRef: undefined, - errorText: '', - onInputChange: () => {}, - label: undefined, -}; - -function StatePicker({value, errorText, onInputChange, forwardedRef, label}) { - const {translate} = useLocalize(); - const [isPickerVisible, setIsPickerVisible] = useState(false); - const [searchValue, setSearchValue] = useState(''); - - const showPickerModal = () => { - setIsPickerVisible(true); - }; - - const hidePickerModal = () => { - setIsPickerVisible(false); - }; - - const updateStateInput = (state) => { - if (state.value !== value) { - onInputChange(state.value); - } - hidePickerModal(); - }; - - const title = value && _.keys(COMMON_CONST.STATES).includes(value) ? translate(`allStates.${value}.stateName`) : ''; - const descStyle = title.length === 0 ? styles.textNormal : null; - - return ( - - - - - - - - ); -} - -StatePicker.propTypes = propTypes; -StatePicker.defaultProps = defaultProps; -StatePicker.displayName = 'StatePicker'; - -export default React.forwardRef((props, ref) => ( - -)); diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js new file mode 100644 index 000000000000..c004fb8bf375 --- /dev/null +++ b/src/components/StateSelector.js @@ -0,0 +1,87 @@ +import React, {useEffect} from 'react'; +import PropTypes from 'prop-types'; +import {View} from 'react-native'; +import _ from 'underscore'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; +import styles from '../styles/styles'; +import Navigation from '../libs/Navigation/Navigation'; +import ROUTES from '../ROUTES'; +import useLocalize from '../hooks/useLocalize'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; +import FormHelpMessage from './FormHelpMessage'; + +const propTypes = { + /** Form error text. e.g when no country is selected */ + errorText: PropTypes.string, + + /** Callback called when the country changes. */ + onInputChange: PropTypes.func.isRequired, + + /** Current selected country */ + value: PropTypes.string, + + /** inputID used by the Form component */ + // eslint-disable-next-line react/no-unused-prop-types + inputID: PropTypes.string.isRequired, + + /** React ref being forwarded to the MenuItemWithTopDescription */ + forwardedRef: PropTypes.func, + + /** Label of state in the url */ + paramName: PropTypes.string, + + /** Label to display on field */ + label: PropTypes.string, +}; + +const defaultProps = { + errorText: '', + value: undefined, + forwardedRef: () => {}, + label: undefined, + paramName: 'state', +}; + +function StateSelector({errorText, value: stateCode, label, paramName, onInputChange, forwardedRef}) { + const {translate} = useLocalize(); + + const title = stateCode && _.keys(COMMON_CONST.STATES).includes(stateCode) ? translate(`allStates.${stateCode}.stateName`) : ''; + const descStyle = title.length === 0 ? styles.textNormal : null; + + useEffect(() => { + // This will cause the form to revalidate and remove any error related to country name + onInputChange(stateCode); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [stateCode]); + + return ( + + { + const activeRoute = Navigation.getActiveRoute(); + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_STATE.getRoute(stateCode, activeRoute, label, paramName)); + }} + /> + + + + + ); +} + +StateSelector.propTypes = propTypes; +StateSelector.defaultProps = defaultProps; +StateSelector.displayName = 'StateSelector'; + +export default React.forwardRef((props, ref) => ( + +)); diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 6636702592c0..f682d438f270 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -127,6 +127,7 @@ const SettingsModalStackNavigator = createModalStackNavigator({ Settings_PersonalDetails_DateOfBirth: () => require('../../../pages/settings/Profile/PersonalDetails/DateOfBirthPage').default, Settings_PersonalDetails_Address: () => require('../../../pages/settings/Profile/PersonalDetails/AddressPage').default, Settings_PersonalDetails_Address_Country: () => require('../../../pages/settings/Profile/PersonalDetails/CountrySelectionPage').default, + Settings_PersonalDetails_Address_State: () => require('../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default, Settings_ContactMethods: () => require('../../../pages/settings/Profile/Contacts/ContactMethodsPage').default, Settings_ContactMethodDetails: () => require('../../../pages/settings/Profile/Contacts/ContactMethodDetailsPage').default, Settings_NewContactMethod: () => require('../../../pages/settings/Profile/Contacts/NewContactMethodPage').default, diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index bf069aba314e..1b39987e7257 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -155,6 +155,10 @@ export default { path: ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_COUNTRY.route, exact: true, }, + Settings_PersonalDetails_Address_State: { + path: ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_STATE.route, + exact: true, + }, Settings_TwoFactorAuth: { path: ROUTES.SETTINGS_2FA, exact: true, diff --git a/src/libs/getStateFromRoute.ts b/src/libs/getStateFromRoute.ts new file mode 100644 index 000000000000..ff01068eae3e --- /dev/null +++ b/src/libs/getStateFromRoute.ts @@ -0,0 +1,13 @@ +// eslint-disable-next-line you-dont-need-lodash-underscore/get +import lodashGet from 'lodash/get'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; + +type RouteProps = { + params?: Record; +}; + +export default function getStateFromRoute(route: RouteProps, stateParamName = 'state'): string { + const stateFromUrlTemp = lodashGet(route, `params.${stateParamName}`) as unknown as string; + // check if state is valid + return lodashGet(COMMON_CONST.STATES, stateFromUrlTemp) ? stateFromUrlTemp : ''; +} diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js index d8fbc0290136..09e365b58c63 100644 --- a/src/pages/ReimbursementAccount/AddressForm.js +++ b/src/pages/ReimbursementAccount/AddressForm.js @@ -5,7 +5,7 @@ import TextInput from '../../components/TextInput'; import AddressSearch from '../../components/AddressSearch'; import styles from '../../styles/styles'; import CONST from '../../CONST'; -import StatePicker from '../../components/StatePicker'; +import StateSelector from '../../components/StateSelector'; const propTypes = { /** Translate key for Street name */ @@ -123,7 +123,7 @@ function AddressForm(props) { /> - - lodashGet(privatePersonalDetails, 'address') || {}, [privatePersonalDetails]); - const countryFromUrl = lodashGet(route, 'params.country'); + + const countryFromUrlTemp = lodashGet(route, 'params.country'); + // check if country is valid + const countryFromUrl = lodashGet(CONST.ALL_COUNTRIES, countryFromUrlTemp) ? countryFromUrlTemp : ''; + + const stateFromUrl = getStateFromRoute(route); const [currentCountry, setCurrentCountry] = useState(address.country); const zipSampleFormat = lodashGet(CONST.COUNTRY_ZIP_REGEX_DATA, [currentCountry, 'samples'], ''); const zipFormat = translate('common.zipCodeExampleFormat', {zipSampleFormat}); @@ -160,6 +166,13 @@ function AddressPage({privatePersonalDetails, route}) { handleAddressChange(countryFromUrl, 'country'); }, [countryFromUrl, handleAddressChange, currentCountry]); + useEffect(() => { + if (!stateFromUrl || stateFromUrl === state) { + return; + } + handleAddressChange(stateFromUrl, 'state'); + }, [state, handleAddressChange, stateFromUrl]); + return ( {isUSAForm ? ( - ) : ( diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js new file mode 100644 index 000000000000..34364421b4ac --- /dev/null +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js @@ -0,0 +1,137 @@ +import React, {useState, useMemo, useCallback} from 'react'; +import PropTypes from 'prop-types'; +import _ from 'underscore'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; +import lodashGet from 'lodash/get'; +import Navigation from '../../../../libs/Navigation/Navigation'; +import ScreenWrapper from '../../../../components/ScreenWrapper'; +import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; +import SelectionList from '../../../../components/SelectionList'; +import searchCountryOptions from '../../../../libs/searchCountryOptions'; +import StringUtils from '../../../../libs/StringUtils'; +import useLocalize from '../../../../hooks/useLocalize'; +import styles from '../../../../styles/styles'; + +const propTypes = { + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** Currently selected country */ + country: PropTypes.string, + + /** Route to navigate back after selecting a currency */ + backTo: PropTypes.string, + }), + }).isRequired, + + /** Navigation from react-navigation */ + navigation: PropTypes.shape({ + /** getState function retrieves the current navigation state from react-navigation's navigation property */ + getState: PropTypes.func.isRequired, + }).isRequired, +}; + +/** + * Appends or updates a query parameter in a given URL. + * + * @param {string} url - The original URL. + * @param {string} paramName - The name of the query parameter to append or update. + * @param {string} paramValue - The value of the query parameter to append or update. + * @returns {string} The updated URL with the appended or updated query parameter. + */ +function appendParam(url, paramName, paramValue) { + if (url.includes(`${paramName}=`)) { + // If parameter exists, replace it + const regex = new RegExp(`${paramName}=([^&]*)`); + return url.replace(regex, `${paramName}=${paramValue}`); + } + // If parameter doesn't exist, append it + const separator = url.includes('?') ? '&' : '?'; + return `${url}${separator}${paramName}=${paramValue}`; +} + +function StateSelectionPage({route, navigation}) { + const [searchValue, setSearchValue] = useState(''); + const {translate} = useLocalize(); + const currentState = lodashGet(route, 'params.state'); + const stateParamName = lodashGet(route, 'params.stateParamName') || 'state'; + const label = lodashGet(route, 'params.label'); + + const countryStates = useMemo( + () => + _.map(_.keys(COMMON_CONST.STATES), (state) => { + const stateName = translate(`allStates.${state}.stateName`); + const stateISO = translate(`allStates.${state}.stateISO`); + return { + value: stateISO, + keyForList: stateISO, + text: stateName, + isSelected: currentState === stateISO, + searchValue: StringUtils.sanitizeString(`${stateISO}${stateName}`), + }; + }), + [translate, currentState], + ); + + const searchResults = searchCountryOptions(searchValue, countryStates); + const headerMessage = searchValue.trim() && !searchResults.length ? translate('common.noResultsFound') : ''; + + const selectCountryState = useCallback( + (option) => { + const backTo = lodashGet(route, 'params.backTo', ''); + + // Check the navigation state and "backTo" parameter to decide navigation behavior + if (navigation.getState().routes.length === 1 && _.isEmpty(backTo)) { + // If there is only one route and "backTo" is empty, go back in navigation + Navigation.goBack(); + } else if (!_.isEmpty(backTo) && navigation.getState().routes.length === 1) { + // If "backTo" is not empty and there is only one route, go back to the specific route defined in "backTo" with a country parameter + Navigation.goBack(appendParam(backTo, stateParamName, option.value)); + } else { + // Otherwise, navigate to the specific route defined in "backTo" with a country parameter + Navigation.navigate(appendParam(backTo, stateParamName, option.value)); + } + }, + [route, navigation, stateParamName], + ); + + return ( + + { + const backTo = lodashGet(route, 'params.backTo', ''); + let backToRoute = ''; + + if (backTo) { + backToRoute = appendParam(backTo, stateParamName, currentState); + } + + Navigation.goBack(backToRoute); + }} + /> + + + + ); +} + +StateSelectionPage.displayName = 'StateSelectionPage'; +StateSelectionPage.propTypes = propTypes; + +export default StateSelectionPage; diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index e75c3b2c517e..201a4e763693 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -11,7 +11,6 @@ import useLocalize from '../../../hooks/useLocalize'; import * as PaymentMethods from '../../../libs/actions/PaymentMethods'; import * as ValidationUtils from '../../../libs/ValidationUtils'; import CheckboxWithLabel from '../../../components/CheckboxWithLabel'; -import StatePicker from '../../../components/StatePicker'; import TextInput from '../../../components/TextInput'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; @@ -22,6 +21,8 @@ import ROUTES from '../../../ROUTES'; import usePrevious from '../../../hooks/usePrevious'; import NotFoundPage from '../../ErrorPage/NotFoundPage'; import Permissions from '../../../libs/Permissions'; +import StateSelector from '../../../components/StateSelector'; +import getStateFromRoute from '../../../libs/getStateFromRoute'; const propTypes = { /* Onyx Props */ @@ -31,6 +32,15 @@ const propTypes = { /** List of betas available to current user */ betas: PropTypes.arrayOf(PropTypes.string), + + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** Currently selected country */ + country: PropTypes.string, + }), + }).isRequired, }; const defaultProps = { @@ -104,6 +114,8 @@ function DebitCardPage(props) { return ; } + const stateFromUrl = getStateFromRoute(props.route); + return ( nameOnCardRef.current && nameOnCardRef.current.focus()} @@ -182,7 +194,10 @@ function DebitCardPage(props) { containerStyles={[styles.mt4]} /> - + From 1284e59cf00621f0012d38187e0738b1e7c3e8dc Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 7 Oct 2023 14:43:44 +0530 Subject: [PATCH 002/699] Incorporation state resets bugfix --- src/pages/ReimbursementAccount/CompanyStep.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index ef7674894445..4bac76dbd164 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -262,7 +262,7 @@ function CompanyStep({reimbursementAccount, route, reimbursementAccountDraft, ge label={translate('companyStep.incorporationState')} shouldSaveDraft paramName="incorporationState" - value={incorporationState} + {...(incorporationState ? {value: incorporationState} : {})} /> Date: Sat, 7 Oct 2023 15:29:42 +0530 Subject: [PATCH 003/699] Modify pages to accomodate for StateSelection --- src/pages/EnablePayments/AdditionalDetailsStep.js | 15 ++++++++++++++- src/pages/EnablePayments/EnablePaymentsPage.js | 4 ++-- src/pages/ReimbursementAccount/ACHContractStep.js | 13 +++++++++++++ src/pages/ReimbursementAccount/CompanyStep.js | 4 ++-- .../ReimbursementAccountPage.js | 2 ++ src/pages/ReimbursementAccount/RequestorStep.js | 15 ++++++++++++++- .../Profile/PersonalDetails/AddressPage.js | 3 +++ .../Profile/PersonalDetails/StateSelectionPage.js | 4 ++-- src/pages/settings/Wallet/AddDebitCardPage.js | 4 ++-- 9 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js index c304103f69c7..f521346b5fcf 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.js @@ -24,6 +24,7 @@ import Form from '../../components/Form'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../components/withCurrentUserPersonalDetails'; import * as PersonalDetails from '../../libs/actions/PersonalDetails'; import OfflineIndicator from '../../components/OfflineIndicator'; +import getStateFromRoute from '../../libs/getStateFromRoute'; const propTypes = { ...withLocalizePropTypes, @@ -55,6 +56,15 @@ const propTypes = { /** Error code to determine additional behavior */ errorCode: PropTypes.string, }), + + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** Currently selected state */ + state: PropTypes.string, + }), + }).isRequired, }; const defaultProps = { @@ -79,7 +89,7 @@ const fieldNameTranslationKeys = { ssnFull9: 'common.ssnFull9', }; -function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserPersonalDetails}) { +function AdditionalDetailsStep({walletAdditionalDetails, translate, route, currentUserPersonalDetails}) { const minDate = moment().subtract(CONST.DATE_BIRTH.MAX_AGE, 'Y').toDate(); const maxDate = moment().subtract(CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT, 'Y').toDate(); const shouldAskForFullSSN = walletAdditionalDetails.errorCode === CONST.WALLET.ERROR.SSN; @@ -163,6 +173,8 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP ); } + const state = getStateFromRoute(route); + return ( <> @@ -209,6 +221,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP state: 'addressState', zipCode: 'addressZipCode', }} + values={state ? {state} : {}} translate={translate} streetTranslationKey={fieldNameTranslationKeys.addressStreet} shouldSaveDraft diff --git a/src/pages/EnablePayments/EnablePaymentsPage.js b/src/pages/EnablePayments/EnablePaymentsPage.js index f7ef2a174208..8feb8342ac8e 100644 --- a/src/pages/EnablePayments/EnablePaymentsPage.js +++ b/src/pages/EnablePayments/EnablePaymentsPage.js @@ -29,7 +29,7 @@ const defaultProps = { userWallet: {}, }; -function EnablePaymentsPage({userWallet}) { +function EnablePaymentsPage({userWallet, route}) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); @@ -72,7 +72,7 @@ function EnablePaymentsPage({userWallet}) { switch (currentStep) { case CONST.WALLET.STEP.ADDITIONAL_DETAILS: case CONST.WALLET.STEP.ADDITIONAL_DETAILS_KBA: - return ; + return ; case CONST.WALLET.STEP.ONFIDO: return ; case CONST.WALLET.STEP.TERMS: diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index 761be71d864a..7209d6250a94 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -19,12 +19,22 @@ import Form from '../../components/Form'; import * as FormActions from '../../libs/actions/FormActions'; import ScreenWrapper from '../../components/ScreenWrapper'; import StepPropTypes from './StepPropTypes'; +import getStateFromRoute from '../../libs/getStateFromRoute'; const propTypes = { ...StepPropTypes, /** Name of the company */ companyName: PropTypes.string.isRequired, + + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** Currently selected state */ + state: PropTypes.string, + }), + }).isRequired, }; function ACHContractStep(props) { @@ -144,6 +154,8 @@ function ACHContractStep(props) { }); }; + const state = getStateFromRoute(props.route); + return ( { return errors; }; -function RequestorStep({reimbursementAccount, shouldShowOnfido, reimbursementAccountDraft, onBackButtonPress, getDefaultStateForField}) { +function RequestorStep({reimbursementAccount, route, shouldShowOnfido, reimbursementAccountDraft, onBackButtonPress, getDefaultStateForField}) { const {translate} = useLocalize(); const defaultValues = useMemo( @@ -118,6 +128,8 @@ function RequestorStep({reimbursementAccount, shouldShowOnfido, reimbursementAcc ); } + const state = getStateFromRoute(route); + return ( Date: Sun, 8 Oct 2023 08:24:10 +0530 Subject: [PATCH 004/699] Refactor to use useGeographicalStateFromRoute hook --- src/hooks/useGeographicalStateFromRoute.ts | 7 +++++++ src/libs/getStateFromRoute.ts | 3 ++- .../EnablePayments/AdditionalDetailsStep.js | 17 ++++------------- src/pages/EnablePayments/EnablePaymentsPage.js | 4 ++-- .../ReimbursementAccount/ACHContractStep.js | 13 ++----------- src/pages/ReimbursementAccount/CompanyStep.js | 18 +++++------------- .../ReimbursementAccountPage.js | 3 --- .../ReimbursementAccount/RequestorStep.js | 17 ++++------------- .../Profile/PersonalDetails/AddressPage.js | 7 ++----- src/pages/settings/Wallet/AddDebitCardPage.js | 15 +++------------ 10 files changed, 31 insertions(+), 73 deletions(-) create mode 100644 src/hooks/useGeographicalStateFromRoute.ts diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts new file mode 100644 index 000000000000..33866e8b0cf1 --- /dev/null +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -0,0 +1,7 @@ +import {useRoute} from '@react-navigation/native'; +import getStateFromRoute from '../libs/getStateFromRoute'; + +export default function useGeographicalStateFromRoute(stateParamName = 'state') { + const route = useRoute(); + return getStateFromRoute(route, stateParamName); +} diff --git a/src/libs/getStateFromRoute.ts b/src/libs/getStateFromRoute.ts index ff01068eae3e..19d6de46014f 100644 --- a/src/libs/getStateFromRoute.ts +++ b/src/libs/getStateFromRoute.ts @@ -3,7 +3,8 @@ import lodashGet from 'lodash/get'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; type RouteProps = { - params?: Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params?: Record; }; export default function getStateFromRoute(route: RouteProps, stateParamName = 'state'): string { diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js index 1d8691333069..5ab59899b139 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.js @@ -24,7 +24,7 @@ import Form from '../../components/Form'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../components/withCurrentUserPersonalDetails'; import * as PersonalDetails from '../../libs/actions/PersonalDetails'; import OfflineIndicator from '../../components/OfflineIndicator'; -import getStateFromRoute from '../../libs/getStateFromRoute'; +import useGeographicalStateFromRoute from '../../hooks/useGeographicalStateFromRoute'; const propTypes = { ...withLocalizePropTypes, @@ -56,15 +56,6 @@ const propTypes = { /** Error code to determine additional behavior */ errorCode: PropTypes.string, }), - - /** Route from navigation */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** Currently selected state */ - state: PropTypes.string, - }), - }).isRequired, }; const defaultProps = { @@ -89,7 +80,7 @@ const fieldNameTranslationKeys = { ssnFull9: 'common.ssnFull9', }; -function AdditionalDetailsStep({walletAdditionalDetails, route, translate, currentUserPersonalDetails}) { +function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserPersonalDetails}) { const currentDate = new Date(); const minDate = subYears(currentDate, CONST.DATE_BIRTH.MAX_AGE); const maxDate = subYears(currentDate, CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); @@ -155,6 +146,8 @@ function AdditionalDetailsStep({walletAdditionalDetails, route, translate, curre Wallet.updatePersonalDetails(personalDetails); }; + const state = useGeographicalStateFromRoute(); + if (!_.isEmpty(walletAdditionalDetails.questions)) { return ( diff --git a/src/pages/EnablePayments/EnablePaymentsPage.js b/src/pages/EnablePayments/EnablePaymentsPage.js index 8feb8342ac8e..f7ef2a174208 100644 --- a/src/pages/EnablePayments/EnablePaymentsPage.js +++ b/src/pages/EnablePayments/EnablePaymentsPage.js @@ -29,7 +29,7 @@ const defaultProps = { userWallet: {}, }; -function EnablePaymentsPage({userWallet, route}) { +function EnablePaymentsPage({userWallet}) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); @@ -72,7 +72,7 @@ function EnablePaymentsPage({userWallet, route}) { switch (currentStep) { case CONST.WALLET.STEP.ADDITIONAL_DETAILS: case CONST.WALLET.STEP.ADDITIONAL_DETAILS_KBA: - return ; + return ; case CONST.WALLET.STEP.ONFIDO: return ; case CONST.WALLET.STEP.TERMS: diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index 7209d6250a94..b619abd68a33 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -19,22 +19,13 @@ import Form from '../../components/Form'; import * as FormActions from '../../libs/actions/FormActions'; import ScreenWrapper from '../../components/ScreenWrapper'; import StepPropTypes from './StepPropTypes'; -import getStateFromRoute from '../../libs/getStateFromRoute'; +import useGeographicalStateFromRoute from '../../hooks/useGeographicalStateFromRoute'; const propTypes = { ...StepPropTypes, /** Name of the company */ companyName: PropTypes.string.isRequired, - - /** Route from navigation */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** Currently selected state */ - state: PropTypes.string, - }), - }).isRequired, }; function ACHContractStep(props) { @@ -154,7 +145,7 @@ function ACHContractStep(props) { }); }; - const state = getStateFromRoute(props.route); + const state = useGeographicalStateFromRoute(); return ( diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 2dc71f7b80fa..c6f53c2474f4 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -429,7 +429,6 @@ class ReimbursementAccountPage extends React.Component { if (currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY) { return ( { return errors; }; -function RequestorStep({reimbursementAccount, route, shouldShowOnfido, reimbursementAccountDraft, onBackButtonPress, getDefaultStateForField}) { +function RequestorStep({reimbursementAccount, shouldShowOnfido, reimbursementAccountDraft, onBackButtonPress, getDefaultStateForField}) { const {translate} = useLocalize(); const defaultValues = useMemo( @@ -117,6 +108,8 @@ function RequestorStep({reimbursementAccount, route, shouldShowOnfido, reimburse ); + const state = useGeographicalStateFromRoute(); + if (shouldShowOnfido) { return ( ; } - const stateFromUrl = getStateFromRoute(props.route); - return ( nameOnCardRef.current && nameOnCardRef.current.focus()} From 0f83cff67014fc53ffa0d005247a0cc29f1a8a3f Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 28 Oct 2023 02:35:21 +0530 Subject: [PATCH 005/699] move getStateFromRoute to useGeographicalStateFromRoute --- src/hooks/useGeographicalStateFromRoute.ts | 8 ++++++-- src/libs/getStateFromRoute.ts | 14 -------------- 2 files changed, 6 insertions(+), 16 deletions(-) delete mode 100644 src/libs/getStateFromRoute.ts diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index 33866e8b0cf1..3726499edf8f 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -1,7 +1,11 @@ import {useRoute} from '@react-navigation/native'; -import getStateFromRoute from '../libs/getStateFromRoute'; +// eslint-disable-next-line you-dont-need-lodash-underscore/get +import lodashGet from 'lodash/get'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; export default function useGeographicalStateFromRoute(stateParamName = 'state') { const route = useRoute(); - return getStateFromRoute(route, stateParamName); + const stateFromUrlTemp = lodashGet(route, `params.${stateParamName}`) as unknown as string; + // check if state is valid + return lodashGet(COMMON_CONST.STATES, stateFromUrlTemp) ? stateFromUrlTemp : ''; } diff --git a/src/libs/getStateFromRoute.ts b/src/libs/getStateFromRoute.ts deleted file mode 100644 index 19d6de46014f..000000000000 --- a/src/libs/getStateFromRoute.ts +++ /dev/null @@ -1,14 +0,0 @@ -// eslint-disable-next-line you-dont-need-lodash-underscore/get -import lodashGet from 'lodash/get'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; - -type RouteProps = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params?: Record; -}; - -export default function getStateFromRoute(route: RouteProps, stateParamName = 'state'): string { - const stateFromUrlTemp = lodashGet(route, `params.${stateParamName}`) as unknown as string; - // check if state is valid - return lodashGet(COMMON_CONST.STATES, stateFromUrlTemp) ? stateFromUrlTemp : ''; -} From 0951d5d75b56a38009fe7bbd91d576bbd976cb25 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 28 Oct 2023 02:46:35 +0530 Subject: [PATCH 006/699] Add displayname to StateSelectorWithRef --- src/components/StateSelector.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index c004fb8bf375..ba7705802982 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -78,10 +78,14 @@ StateSelector.propTypes = propTypes; StateSelector.defaultProps = defaultProps; StateSelector.displayName = 'StateSelector'; -export default React.forwardRef((props, ref) => ( +const StateSelectorWithRef = React.forwardRef((props, ref) => ( )); + +StateSelectorWithRef.displayName = 'StateSelectorWithRef'; + +export default StateSelectorWithRef; From 598c7c6c3378fe402e3b811a83f70b4cf16c38de Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 28 Oct 2023 03:57:15 +0530 Subject: [PATCH 007/699] Fix AddressPage: State not changing on autocomplete --- src/pages/settings/Profile/PersonalDetails/AddressPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index 1eec60d7d57f..112372abebcd 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -167,11 +167,11 @@ function AddressPage({privatePersonalDetails, route}) { }, [countryFromUrl, handleAddressChange]); useEffect(() => { - if (!stateFromUrl || stateFromUrl === state) { + if (!stateFromUrl) { return; } handleAddressChange(stateFromUrl, 'state'); - }, [state, handleAddressChange, stateFromUrl]); + }, [handleAddressChange, stateFromUrl]); return ( Date: Sat, 28 Oct 2023 04:56:23 +0530 Subject: [PATCH 008/699] Refactor StateSelector logic --- src/components/StateSelector.js | 30 +++++++++++++++---- .../EnablePayments/AdditionalDetailsStep.js | 4 --- .../ReimbursementAccount/ACHContractStep.js | 4 --- src/pages/ReimbursementAccount/CompanyStep.js | 3 +- .../ReimbursementAccount/RequestorStep.js | 4 --- src/pages/settings/Wallet/AddDebitCardPage.js | 8 +---- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index ba7705802982..52df1f61d7e4 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React, {useEffect, useState} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import _ from 'underscore'; @@ -9,6 +9,7 @@ import ROUTES from '../ROUTES'; import useLocalize from '../hooks/useLocalize'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import FormHelpMessage from './FormHelpMessage'; +import useGeographicalStateFromRoute from '../hooks/useGeographicalStateFromRoute'; const propTypes = { /** Form error text. e.g when no country is selected */ @@ -32,6 +33,9 @@ const propTypes = { /** Label to display on field */ label: PropTypes.string, + + /** whether to use state from url */ + useStateFromUrl: PropTypes.bool, }; const defaultProps = { @@ -40,20 +44,36 @@ const defaultProps = { forwardedRef: () => {}, label: undefined, paramName: 'state', + useStateFromUrl: true, }; -function StateSelector({errorText, value: stateCode, label, paramName, onInputChange, forwardedRef}) { +function StateSelector({errorText, useStateFromUrl, value: stateCode, label, paramName, onInputChange, forwardedRef}) { const {translate} = useLocalize(); - const title = stateCode && _.keys(COMMON_CONST.STATES).includes(stateCode) ? translate(`allStates.${stateCode}.stateName`) : ''; - const descStyle = title.length === 0 ? styles.textNormal : null; + const stateFromUrl = useGeographicalStateFromRoute(paramName); + + const [stateToDisplay, setStateToDisplay] = useState(''); + + useEffect(() => { + if (useStateFromUrl) { + // This will cause the form to revalidate and remove any error related to country name + stateFromUrl && onInputChange(stateFromUrl); + stateFromUrl && setStateToDisplay(stateFromUrl); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [stateFromUrl, useStateFromUrl]); useEffect(() => { // This will cause the form to revalidate and remove any error related to country name - onInputChange(stateCode); + stateCode && onInputChange(stateCode); + stateCode && setStateToDisplay(stateCode); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [stateCode]); + const title = stateToDisplay && _.keys(COMMON_CONST.STATES).includes(stateToDisplay) ? translate(`allStates.${stateToDisplay}.stateName`) : ''; + const descStyle = title.length === 0 ? styles.textNormal : null; + return ( diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index 875c884420ae..86f1d5077eca 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -17,7 +17,6 @@ import Form from '../../components/Form'; import ScreenWrapper from '../../components/ScreenWrapper'; import useLocalize from '../../hooks/useLocalize'; import {reimbursementAccountPropTypes} from './reimbursementAccountPropTypes'; -import useGeographicalStateFromRoute from '../../hooks/useGeographicalStateFromRoute'; const propTypes = { onBackButtonPress: PropTypes.func.isRequired, @@ -106,8 +105,6 @@ function RequestorStep({reimbursementAccount, shouldShowOnfido, onBackButtonPres ); - const state = useGeographicalStateFromRoute(); - if (shouldShowOnfido) { return ( ; } @@ -185,10 +182,7 @@ function DebitCardPage(props) { containerStyles={[styles.mt4]} /> - + Date: Sat, 28 Oct 2023 05:02:05 +0530 Subject: [PATCH 009/699] Refactor AddressPage --- src/pages/settings/Profile/PersonalDetails/AddressPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index 112372abebcd..1e21a210dd40 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -232,6 +232,7 @@ function AddressPage({privatePersonalDetails, route}) { {isUSAForm ? ( From 8a293b5ceeaec50be8bcd7d262a097b46b0b783f Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 28 Oct 2023 05:34:30 +0530 Subject: [PATCH 010/699] linting fixes StateSelector --- src/components/StateSelector.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index 52df1f61d7e4..517142527552 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -55,18 +55,21 @@ function StateSelector({errorText, useStateFromUrl, value: stateCode, label, par const [stateToDisplay, setStateToDisplay] = useState(''); useEffect(() => { - if (useStateFromUrl) { - // This will cause the form to revalidate and remove any error related to country name - stateFromUrl && onInputChange(stateFromUrl); - stateFromUrl && setStateToDisplay(stateFromUrl); - } + if (!useStateFromUrl || !stateFromUrl) return; + + // This will cause the form to revalidate and remove any error related to country name + onInputChange(stateFromUrl); + setStateToDisplay(stateFromUrl); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [stateFromUrl, useStateFromUrl]); useEffect(() => { + if (!stateCode) return; + // This will cause the form to revalidate and remove any error related to country name - stateCode && onInputChange(stateCode); - stateCode && setStateToDisplay(stateCode); + onInputChange(stateCode); + setStateToDisplay(stateCode); // eslint-disable-next-line react-hooks/exhaustive-deps }, [stateCode]); From c946cbb839e04468d5c2acedaac81905bb9233a9 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 28 Oct 2023 09:07:58 +0530 Subject: [PATCH 011/699] linting --- src/components/StateSelector.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index 517142527552..aaa905d8940f 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -55,7 +55,9 @@ function StateSelector({errorText, useStateFromUrl, value: stateCode, label, par const [stateToDisplay, setStateToDisplay] = useState(''); useEffect(() => { - if (!useStateFromUrl || !stateFromUrl) return; + if (!useStateFromUrl || !stateFromUrl) { + return; + } // This will cause the form to revalidate and remove any error related to country name onInputChange(stateFromUrl); @@ -65,7 +67,9 @@ function StateSelector({errorText, useStateFromUrl, value: stateCode, label, par }, [stateFromUrl, useStateFromUrl]); useEffect(() => { - if (!stateCode) return; + if (!stateCode) { + return; + } // This will cause the form to revalidate and remove any error related to country name onInputChange(stateCode); From 144eea9e55e6bc9190ab71da057a0d20e0b04e39 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sun, 29 Oct 2023 10:28:58 +0530 Subject: [PATCH 012/699] StateSelector and related files refactoring --- src/components/StateSelector.js | 2 +- src/pages/ReimbursementAccount/CompanyStep.js | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index aaa905d8940f..c1ebdaefc493 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -34,7 +34,7 @@ const propTypes = { /** Label to display on field */ label: PropTypes.string, - /** whether to use state from url */ + /** whether to use state from url, for cases when url value is passed from parent */ useStateFromUrl: PropTypes.bool, }; diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 7e86ebf139b2..03a222abe3d7 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -25,7 +25,6 @@ import Form from '../../components/Form'; import ScreenWrapper from '../../components/ScreenWrapper'; import StepPropTypes from './StepPropTypes'; import StateSelector from '../../components/StateSelector'; -import useGeographicalStateFromRoute from '../../hooks/useGeographicalStateFromRoute'; const propTypes = { ...StepPropTypes, @@ -140,8 +139,6 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul const shouldDisableCompanyName = Boolean(bankAccountID && getDefaultStateForField('companyName')); const shouldDisableCompanyTaxID = Boolean(bankAccountID && getDefaultStateForField('companyTaxID')); - const incorporationState = useGeographicalStateFromRoute('incorporationState') || getDefaultStateForField('incorporationState'); - return ( Date: Sun, 29 Oct 2023 11:18:33 +0530 Subject: [PATCH 013/699] Fix incorrect merge --- .../Profile/PersonalDetails/AddressPage.js | 7 +------ .../PersonalDetails/StateSelectionPage.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index d3b9bdfba141..9c102e566b06 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -4,12 +4,6 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import AddressSearch from '@components/AddressSearch'; import CountrySelector from '@components/CountrySelector'; @@ -28,6 +22,7 @@ import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; const propTypes = { /* Onyx Props */ diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js index ea443edc581d..37acb6e38397 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js @@ -3,14 +3,14 @@ import PropTypes from 'prop-types'; import _ from 'underscore'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import lodashGet from 'lodash/get'; -import Navigation from '../../../../libs/Navigation/Navigation'; -import ScreenWrapper from '../../../../components/ScreenWrapper'; -import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; -import SelectionList from '../../../../components/SelectionList'; -import searchCountryOptions from '../../../../libs/searchCountryOptions'; -import StringUtils from '../../../../libs/StringUtils'; -import useLocalize from '../../../../hooks/useLocalize'; -import styles from '../../../../styles/styles'; +import Navigation from '@libs/Navigation/Navigation'; +import ScreenWrapper from '@components/ScreenWrapper'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import SelectionList from '@components/SelectionList'; +import searchCountryOptions from '@libs/searchCountryOptions'; +import StringUtils from '@libs/StringUtils'; +import useLocalize from '@hooks/useLocalize'; +import styles from '@styles/styles'; const propTypes = { /** Route from navigation */ From 9fb4f72463d13aca2f897cbc0e17a4aa399c4b6a Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sun, 29 Oct 2023 11:22:35 +0530 Subject: [PATCH 014/699] Reverted scripts/shellCheck.sh to state at 33f15e8d85b3366b1a09bca914f61d5caa4a2fb5 --- scripts/shellCheck.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shellCheck.sh b/scripts/shellCheck.sh index 18206a419509..d148958900d4 100755 --- a/scripts/shellCheck.sh +++ b/scripts/shellCheck.sh @@ -45,4 +45,4 @@ if [ $EXIT_CODE == 0 ]; then success "ShellCheck passed for all files!" fi -exit $EXIT_CODE \ No newline at end of file +exit $EXIT_CODE From d6a643c7b7f4db06243c420606afae458fb9ade8 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sun, 29 Oct 2023 11:27:55 +0530 Subject: [PATCH 015/699] Linting --- src/components/StateSelector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index b5b1c3903c8c..c27c0e57e4c9 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -7,9 +7,9 @@ import styles from '@styles/styles'; import Navigation from '@libs/Navigation/Navigation'; import ROUTES from '@src/ROUTES'; import useLocalize from '@hooks/useLocalize'; +import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import FormHelpMessage from './FormHelpMessage'; -import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; const propTypes = { /** Form error text. e.g when no country is selected */ From 9bb88ee2129ebebdc9274598a72ab69ba84ce3c4 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Tue, 31 Oct 2023 21:29:36 +0530 Subject: [PATCH 016/699] Comment formatting src/hooks/useGeographicalStateFromRoute.ts Co-authored-by: Rajat Parashar --- src/hooks/useGeographicalStateFromRoute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index 3726499edf8f..d86b9df6ce24 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -6,6 +6,6 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; export default function useGeographicalStateFromRoute(stateParamName = 'state') { const route = useRoute(); const stateFromUrlTemp = lodashGet(route, `params.${stateParamName}`) as unknown as string; - // check if state is valid + // Check if state is valid return lodashGet(COMMON_CONST.STATES, stateFromUrlTemp) ? stateFromUrlTemp : ''; } From 5e3844785f3ef11d3bdb411078d2482490aa35bc Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 31 Oct 2023 22:00:09 +0530 Subject: [PATCH 017/699] Rename useStateFomrUrl to shouldUseStateFromUrl --- src/components/StateSelector.js | 10 +++++----- .../settings/Profile/PersonalDetails/AddressPage.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index c27c0e57e4c9..fb4046ad848b 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -35,7 +35,7 @@ const propTypes = { label: PropTypes.string, /** whether to use state from url, for cases when url value is passed from parent */ - useStateFromUrl: PropTypes.bool, + shouldUseStateFromUrl: PropTypes.bool, }; const defaultProps = { @@ -44,10 +44,10 @@ const defaultProps = { forwardedRef: () => {}, label: undefined, paramName: 'state', - useStateFromUrl: true, + shouldUseStateFromUrl: true, }; -function StateSelector({errorText, useStateFromUrl, value: stateCode, label, paramName, onInputChange, forwardedRef}) { +function StateSelector({errorText, shouldUseStateFromUrl, value: stateCode, label, paramName, onInputChange, forwardedRef}) { const {translate} = useLocalize(); const stateFromUrl = useGeographicalStateFromRoute(paramName); @@ -55,7 +55,7 @@ function StateSelector({errorText, useStateFromUrl, value: stateCode, label, par const [stateToDisplay, setStateToDisplay] = useState(''); useEffect(() => { - if (!useStateFromUrl || !stateFromUrl) { + if (!shouldUseStateFromUrl || !stateFromUrl) { return; } @@ -64,7 +64,7 @@ function StateSelector({errorText, useStateFromUrl, value: stateCode, label, par setStateToDisplay(stateFromUrl); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [stateFromUrl, useStateFromUrl]); + }, [stateFromUrl, shouldUseStateFromUrl]); useEffect(() => { if (!stateCode) { diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index 9c102e566b06..a9b64b2420ed 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -232,7 +232,7 @@ function AddressPage({privatePersonalDetails, route}) { {isUSAForm ? ( From 470ec03d5e71e0580643a852a14fe2ee61e4f241 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 31 Oct 2023 22:07:00 +0530 Subject: [PATCH 018/699] forwardedRef type correction StateSelector --- src/components/StateSelector.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index fb4046ad848b..59f974251143 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -10,6 +10,7 @@ import useLocalize from '@hooks/useLocalize'; import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import FormHelpMessage from './FormHelpMessage'; +import refPropTypes from './refPropTypes'; const propTypes = { /** Form error text. e.g when no country is selected */ @@ -26,7 +27,7 @@ const propTypes = { inputID: PropTypes.string.isRequired, /** React ref being forwarded to the MenuItemWithTopDescription */ - forwardedRef: PropTypes.func, + forwardedRef: refPropTypes, /** Label of state in the url */ paramName: PropTypes.string, From 8f28580d6dca027d9698a8510a0ebaf28a6ba38d Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 31 Oct 2023 22:08:46 +0530 Subject: [PATCH 019/699] StateSelector: rename paramName to queryParam --- src/components/StateSelector.js | 10 +++++----- src/pages/ReimbursementAccount/CompanyStep.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index 59f974251143..13202f9d79e1 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -30,7 +30,7 @@ const propTypes = { forwardedRef: refPropTypes, /** Label of state in the url */ - paramName: PropTypes.string, + queryParam: PropTypes.string, /** Label to display on field */ label: PropTypes.string, @@ -44,14 +44,14 @@ const defaultProps = { value: undefined, forwardedRef: () => {}, label: undefined, - paramName: 'state', + queryParam: 'state', shouldUseStateFromUrl: true, }; -function StateSelector({errorText, shouldUseStateFromUrl, value: stateCode, label, paramName, onInputChange, forwardedRef}) { +function StateSelector({errorText, shouldUseStateFromUrl, value: stateCode, label, queryParam, onInputChange, forwardedRef}) { const {translate} = useLocalize(); - const stateFromUrl = useGeographicalStateFromRoute(paramName); + const stateFromUrl = useGeographicalStateFromRoute(queryParam); const [stateToDisplay, setStateToDisplay] = useState(''); @@ -92,7 +92,7 @@ function StateSelector({errorText, shouldUseStateFromUrl, value: stateCode, labe description={label || translate('common.state')} onPress={() => { const activeRoute = Navigation.getActiveRoute(); - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_STATE.getRoute(stateCode, activeRoute, label, paramName)); + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_STATE.getRoute(stateCode, activeRoute, label, queryParam)); }} /> diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index fa806521e5d0..04010f1db0c1 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -248,7 +248,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul label={translate('companyStep.incorporationState')} defaultValue={getDefaultStateForField('incorporationState')} shouldSaveDraft - paramName="incorporationState" + queryParam="incorporationState" /> Date: Wed, 1 Nov 2023 08:24:52 +0530 Subject: [PATCH 020/699] Remove lodashGet from useGeographicalStateFromRoute --- src/hooks/useGeographicalStateFromRoute.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index d86b9df6ce24..112e0e8db69c 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -1,11 +1,14 @@ -import {useRoute} from '@react-navigation/native'; -// eslint-disable-next-line you-dont-need-lodash-underscore/get -import lodashGet from 'lodash/get'; +import {ParamListBase, RouteProp, useRoute} from '@react-navigation/native'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; +type CustomParamList = ParamListBase & Record>; + export default function useGeographicalStateFromRoute(stateParamName = 'state') { - const route = useRoute(); - const stateFromUrlTemp = lodashGet(route, `params.${stateParamName}`) as unknown as string; - // Check if state is valid - return lodashGet(COMMON_CONST.STATES, stateFromUrlTemp) ? stateFromUrlTemp : ''; + const route = useRoute>(); + const stateFromUrlTemp = route.params?.[stateParamName] as string | undefined; + + if (!stateFromUrlTemp) { + return ''; + } + return COMMON_CONST.STATES[stateFromUrlTemp as keyof typeof COMMON_CONST.STATES] ? stateFromUrlTemp : ''; } From 428e48c177adcf9a77f0067114781307be11ea62 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 1 Nov 2023 08:37:52 +0530 Subject: [PATCH 021/699] StateSelectionPage appendParam restructuring --- src/libs/Url.ts | 16 +++++++++- .../PersonalDetails/StateSelectionPage.js | 32 ++++--------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/libs/Url.ts b/src/libs/Url.ts index a21f007e8468..7e028aded60c 100644 --- a/src/libs/Url.ts +++ b/src/libs/Url.ts @@ -41,4 +41,18 @@ function hasSameExpensifyOrigin(url1: string, url2: string): boolean { } } -export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL}; +/** + * Appends or updates a query parameter in a given URL. + */ +function appendParam(url: string, paramName: string, paramValue: string) { + if (url.includes(`${paramName}=`)) { + // If parameter exists, replace it + const regex = new RegExp(`${paramName}=([^&]*)`); + return url.replace(regex, `${paramName}=${paramValue}`); + } + // If parameter doesn't exist, append it + const separator = url.includes('?') ? '&' : '?'; + return `${url}${separator}${paramName}=${paramValue}`; +} + +export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam}; diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js index 37acb6e38397..a506d7e61495 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js @@ -1,15 +1,16 @@ -import React, {useState, useMemo, useCallback} from 'react'; -import PropTypes from 'prop-types'; -import _ from 'underscore'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import lodashGet from 'lodash/get'; -import Navigation from '@libs/Navigation/Navigation'; -import ScreenWrapper from '@components/ScreenWrapper'; +import PropTypes from 'prop-types'; +import React, {useCallback, useMemo, useState} from 'react'; +import _ from 'underscore'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; +import useLocalize from '@hooks/useLocalize'; +import Navigation from '@libs/Navigation/Navigation'; import searchCountryOptions from '@libs/searchCountryOptions'; import StringUtils from '@libs/StringUtils'; -import useLocalize from '@hooks/useLocalize'; +import {appendParam} from '@libs/Url'; import styles from '@styles/styles'; const propTypes = { @@ -32,25 +33,6 @@ const propTypes = { }).isRequired, }; -/** - * Appends or updates a query parameter in a given URL. - * - * @param {string} url - The original URL. - * @param {string} paramName - The name of the query parameter to append or update. - * @param {string} paramValue - The value of the query parameter to append or update. - * @returns {string} The updated URL with the appended or updated query parameter. - */ -function appendParam(url, paramName, paramValue) { - if (url.includes(`${paramName}=`)) { - // If parameter exists, replace it - const regex = new RegExp(`${paramName}=([^&]*)`); - return url.replace(regex, `${paramName}=${paramValue}`); - } - // If parameter doesn't exist, append it - const separator = url.includes('?') ? '&' : '?'; - return `${url}${separator}${paramName}=${paramValue}`; -} - function StateSelectionPage({route, navigation}) { const [searchValue, setSearchValue] = useState(''); const {translate} = useLocalize(); From 7cd8b6fb2c30a45999f521355e97fdadfe6f4b2f Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 1 Nov 2023 08:43:07 +0530 Subject: [PATCH 022/699] Prettier formatting --- src/components/StateSelector.js | 12 ++++++------ .../settings/Profile/PersonalDetails/AddressPage.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index 13202f9d79e1..121037d91370 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -1,15 +1,15 @@ -import React, {useEffect, useState} from 'react'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import PropTypes from 'prop-types'; +import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import styles from '@styles/styles'; +import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; +import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; +import styles from '@styles/styles'; import ROUTES from '@src/ROUTES'; -import useLocalize from '@hooks/useLocalize'; -import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; -import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import FormHelpMessage from './FormHelpMessage'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import refPropTypes from './refPropTypes'; const propTypes = { diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index a9b64b2420ed..752c3d2984a2 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -13,6 +13,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import StateSelector from '@components/StateSelector'; import TextInput from '@components/TextInput'; +import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; import useLocalize from '@hooks/useLocalize'; import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails'; import Navigation from '@libs/Navigation/Navigation'; @@ -22,7 +23,6 @@ import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; const propTypes = { /* Onyx Props */ From d60ca6ccd76ca612d708d368b206364edb540400 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 01:06:47 +0100 Subject: [PATCH 023/699] remove unused split transaction draft --- src/components/MoneyRequestConfirmationList.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 13dce9337673..5807eb66f143 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -825,9 +825,6 @@ export default compose( key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, - splitTransactionDraft: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, - }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, From d849ea347c5fa1c488212e54e9f68411893d76c1 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 01:08:55 +0100 Subject: [PATCH 024/699] use StepScreenWrapper and backTo for back navigation --- .../step/IOURequestStepTaxAmountPage.js | 54 +++++++------------ 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js index 8ee3abb56d00..89f592b89e38 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js @@ -1,17 +1,11 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef} from 'react'; -import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; import taxPropTypes from '@components/taxPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; -import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; import MoneyRequestAmountForm from '@pages/iou/steps/MoneyRequestAmountForm'; @@ -21,6 +15,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; +import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; @@ -61,7 +56,6 @@ function IOURequestStepTaxAmountPage({ policyTaxRates, }) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const textInput = useRef(null); const isEditing = Navigation.getActiveRoute().includes('taxAmount'); @@ -82,7 +76,7 @@ function IOURequestStepTaxAmountPage({ ); const navigateBack = () => { - Navigation.goBack(isEditing ? ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID) : ROUTES.HOME); + Navigation.goBack(backTo || ROUTES.HOME); }; const navigateToCurrencySelectionPage = () => { @@ -117,37 +111,25 @@ function IOURequestStepTaxAmountPage({ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(iouType, transactionID, reportID)); }; - const content = ( - (textInput.current = e)} - onCurrencyButtonPress={navigateToCurrencySelectionPage} - onSubmitButtonPress={updateTaxAmount} - /> - ); - return ( - - {({safeAreaPaddingBottomStyle}) => ( - - - - {content} - - - )} - + (textInput.current = e)} + onCurrencyButtonPress={navigateToCurrencySelectionPage} + onSubmitButtonPress={updateTaxAmount} + /> + ); } From f558497bacc30d13ae591bbc1667e6809b5b5419 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 01:09:10 +0100 Subject: [PATCH 025/699] use StepScreenWrapper and backTo for back navigation --- .../request/step/IOURequestStepTaxRatePage.js | 54 +++++++------------ 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js index bae08cd8cb62..ba3c9c7a321c 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js @@ -1,9 +1,6 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; import TaxPicker from '@components/TaxPicker'; import taxPropTypes from '@components/taxPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -16,21 +13,14 @@ import * as TransactionUtils from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; +import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; const propTypes = { - /** Route from navigation */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string, - - /** The report ID of the IOU */ - reportID: PropTypes.string, - }), - }).isRequired, + /** Navigation route context info provided by react navigation */ + route: IOURequestStepRoutePropTypes.isRequired, /* Onyx Props */ /** Collection of tax rates attached to a policy */ @@ -52,16 +42,16 @@ const getTaxAmount = (taxRates, selectedTaxRate, amount) => { function IOURequestStepTaxRatePage({ route: { - params: {iouType, reportID}, + params: {iouType, reportID, backTo}, }, policyTaxRates, transaction, }) { const {translate} = useLocalize(); - function navigateBack() { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); - } + const navigateBack = () => { + Navigation.goBack(backTo || ROUTES.HOME); + }; const defaultTaxKey = policyTaxRates.defaultExternalID; const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; @@ -77,26 +67,18 @@ function IOURequestStepTaxRatePage({ }; return ( - - {({insets}) => ( - <> - navigateBack()} - /> - - - )} - + + ); } From de255d7a896b951659503bd7a483ee874d187dc8 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 09:16:34 +0100 Subject: [PATCH 026/699] remove unused transaction prop --- src/pages/iou/request/step/IOURequestStepTaxAmountPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js index 89f592b89e38..106119cff994 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js @@ -124,7 +124,6 @@ function IOURequestStepTaxAmountPage({ currency={currency} amount={transaction.taxAmount} taxAmount={getTaxAmount(transaction, policyTaxRates.defaultValue)} - transaction={transaction} ref={(e) => (textInput.current = e)} onCurrencyButtonPress={navigateToCurrencySelectionPage} onSubmitButtonPress={updateTaxAmount} From b1df50f1f0b0a9272d462484dd9827df7d9dc494 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 09:17:55 +0100 Subject: [PATCH 027/699] use policyTaxRates directly in taxPicker --- src/components/TaxPicker/index.js | 8 +++++++- src/pages/iou/request/step/IOURequestStepTaxRatePage.js | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/TaxPicker/index.js b/src/components/TaxPicker/index.js index f25a1b84bf64..287805692bcf 100644 --- a/src/components/TaxPicker/index.js +++ b/src/components/TaxPicker/index.js @@ -1,5 +1,6 @@ import lodashGet from 'lodash/get'; import React, {useMemo, useState} from 'react'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import OptionsSelector from '@components/OptionsSelector'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, propTypes} from './taxPickerPropTypes'; function TaxPicker({selectedTaxRate, policyTaxRates, insets, onSubmit}) { @@ -87,4 +89,8 @@ TaxPicker.displayName = 'TaxPicker'; TaxPicker.propTypes = propTypes; TaxPicker.defaultProps = defaultProps; -export default TaxPicker; +export default withOnyx({ + policyTaxRates: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, + }, +})(TaxPicker); diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js index ba3c9c7a321c..22f826d0457b 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js @@ -10,6 +10,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; +import reportPropTypes from '@pages/reportPropTypes'; import * as IOU from '@userActions/IOU'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -28,9 +29,13 @@ const propTypes = { /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, + + /** The report attached to the transaction */ + report: reportPropTypes, }; const defaultProps = { + report: {}, policyTaxRates: {}, transaction: {}, }; @@ -46,6 +51,7 @@ function IOURequestStepTaxRatePage({ }, policyTaxRates, transaction, + report, }) { const {translate} = useLocalize(); @@ -75,7 +81,7 @@ function IOURequestStepTaxRatePage({ > From 22c290eaddc9b2239995b034b84052810def0777 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 09:19:34 +0100 Subject: [PATCH 028/699] add EditRequestTaxAmount Page --- src/pages/EditRequestTaxAmountPage.js | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/pages/EditRequestTaxAmountPage.js diff --git a/src/pages/EditRequestTaxAmountPage.js b/src/pages/EditRequestTaxAmountPage.js new file mode 100644 index 000000000000..2d10ccfe6f2b --- /dev/null +++ b/src/pages/EditRequestTaxAmountPage.js @@ -0,0 +1,73 @@ +import {useFocusEffect} from '@react-navigation/native'; +import PropTypes from 'prop-types'; +import React, {useCallback, useRef} from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; +import MoneyRequestAmountForm from './iou/steps/MoneyRequestAmountForm'; + +const propTypes = { + /** Transaction default amount value */ + defaultAmount: PropTypes.number.isRequired, + + /** Transaction default tax amount value */ + defaultTaxAmount: PropTypes.number.isRequired, + + /** Transaction default currency value */ + defaultCurrency: PropTypes.string.isRequired, + + /** Callback to fire when the Save button is pressed */ + onSubmit: PropTypes.func.isRequired, + + /** Callback to fire when we press on the currency */ + onNavigateToCurrency: PropTypes.func.isRequired, +}; + +function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurrency, onNavigateToCurrency, onSubmit}) { + const {translate} = useLocalize(); + const textInput = useRef(null); + const isEditing = Navigation.getActiveRoute().includes('taxAmount'); + + const focusTimeoutRef = useRef(null); + + useFocusEffect( + useCallback(() => { + focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION); + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; + }, []), + ); + + return ( + + + (textInput.current = e)} + onCurrencyButtonPress={onNavigateToCurrency} + onSubmitButtonPress={onSubmit} + isEditing + /> + + ); +} + +EditRequestTaxAmountPage.propTypes = propTypes; +EditRequestTaxAmountPage.displayName = 'EditRequestTaxAmountPage'; + +export default EditRequestTaxAmountPage; From 42f79baa818863272e58e57ff6b87e7865c9b10b Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 09:19:59 +0100 Subject: [PATCH 029/699] add EditRequestTaxRate Page --- src/pages/EditRequestTaxRatePage.js | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/pages/EditRequestTaxRatePage.js diff --git a/src/pages/EditRequestTaxRatePage.js b/src/pages/EditRequestTaxRatePage.js new file mode 100644 index 000000000000..965dfc1a65d7 --- /dev/null +++ b/src/pages/EditRequestTaxRatePage.js @@ -0,0 +1,47 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TaxPicker from '@components/TaxPicker'; +import useLocalize from '@hooks/useLocalize'; +import Navigation from '@libs/Navigation/Navigation'; + +const propTypes = { + /** Transaction default tax Rate value */ + defaultTaxRate: PropTypes.string.isRequired, + + /** The policyID we are getting categories for */ + policyID: PropTypes.string.isRequired, + + /** Callback to fire when the Save button is pressed */ + onSubmit: PropTypes.func.isRequired, +}; + +function EditRequestTaxRatePage({defaultTaxRate, policyID, onSubmit}) { + const {translate} = useLocalize(); + + return ( + + {({insets}) => ( + <> + + + + )} + + ); +} + +EditRequestTaxRatePage.propTypes = propTypes; +EditRequestTaxRatePage.displayName = 'EditRequestTaxRatePage'; + +export default EditRequestTaxRatePage; From 3226d9cf8ba70e0218032197dd925d75e252faa3 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 09:23:47 +0100 Subject: [PATCH 030/699] add edit Tax amount and rate page to edit request page --- src/CONST.ts | 2 ++ src/pages/EditRequestPage.js | 39 +++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index cec7cbc0b8a5..44bee149609f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1541,6 +1541,8 @@ const CONST = { RECEIPT: 'receipt', DISTANCE: 'distance', TAG: 'tag', + TAX_RATE: 'taxRate', + TAX_AMOUNT: 'taxAmount', }, FOOTER: { EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`, diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index e41f30779f22..a43ddc44c492 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -27,6 +27,8 @@ import EditRequestDistancePage from './EditRequestDistancePage'; import EditRequestMerchantPage from './EditRequestMerchantPage'; import EditRequestReceiptPage from './EditRequestReceiptPage'; import EditRequestTagPage from './EditRequestTagPage'; +import EditRequestTaxAmountPage from './EditRequestTaxAmountPage'; +import EditRequestTaxRatePage from './EditRequestTaxRatePage'; import reportActionPropTypes from './home/report/reportActionPropTypes'; import reportPropTypes from './reportPropTypes'; @@ -61,6 +63,12 @@ const propTypes = { /** Transaction that stores the request data */ transaction: transactionPropTypes, + + /** The policy of the report */ + policy: PropTypes.shape({ + /** Is Tax tracking Enabled */ + isTaxTrackingEnabled: PropTypes.bool, + }), }; const defaultProps = { @@ -70,9 +78,10 @@ const defaultProps = { policyTags: {}, parentReportActions: {}, transaction: {}, + policy: {}, }; -function EditRequestPage({report, route, parentReport, policyCategories, policyTags, parentReportActions, transaction}) { +function EditRequestPage({report, policy, route, parentReport, policyCategories, policyTags, parentReportActions, transaction}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); const { @@ -101,6 +110,9 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT // A flag for showing the tags page const shouldShowTags = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagList))); + // A flag for showing tax rate + const shouldShowTax = isPolicyExpenseChat && policy.isTaxTrackingEnabled; + // Decides whether to allow or disallow editing a money request useEffect(() => { // Do not dismiss the modal, when a current user can edit this property of the money request. @@ -251,6 +263,28 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT ); } + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAX_AMOUNT && shouldShowTax) { + return ( + {}} + onSubmit={() => {}} + /> + ); + } + + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAX_RATE && shouldShowTax) { + return ( + {}} + /> + ); + } + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) { return ( `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`, }, + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, + }, policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, From 9130f73d940ef8a8bb62fd3c8cf7914b15959dc2 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 09:24:32 +0100 Subject: [PATCH 031/699] add edit Tax amount and rate fields and route to pages --- .../ReportActionItem/MoneyRequestView.js | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 514dc71ffe2c..4c8cc0c3d131 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -12,6 +12,7 @@ import ReceiptEmptyState from '@components/ReceiptEmptyState'; import SpacerView from '@components/SpacerView'; import Switch from '@components/Switch'; import tagPropTypes from '@components/tagPropTypes'; +import taxPropTypes from '@components/taxPropTypes'; import Text from '@components/Text'; import transactionPropTypes from '@components/transactionPropTypes'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; @@ -64,6 +65,10 @@ const propTypes = { /** Collection of tags attached to a policy */ policyTags: tagPropTypes, + /* Onyx Props */ + /** Collection of tax rates attached to a policy */ + policyTaxRates: taxPropTypes, + ...withCurrentUserPersonalDetailsPropTypes, }; @@ -77,9 +82,10 @@ const defaultProps = { comment: {comment: ''}, }, policyTags: {}, + policyTaxRates: {}, }; -function MoneyRequestView({report, parentReport, parentReportActions, policyCategories, shouldShowHorizontalRule, transaction, policyTags, policy}) { +function MoneyRequestView({report, parentReport, parentReportActions, policyCategories, policyTaxRates, shouldShowHorizontalRule, transaction, policyTags, policy}) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -131,6 +137,9 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagsList))); const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); + // A flag for showing tax rate + const shouldShowTax = isPolicyExpenseChat && policy.isTaxTrackingEnabled; + let amountDescription = `${translate('iou.amount')}`; if (isCardTransaction) { @@ -295,6 +304,31 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate /> )} + {shouldShowTax && ( + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAX_RATE))} + /> + + )} + + {shouldShowTax && ( + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAX_AMOUNT))} + /> + + )} {shouldShowBillable && ( {translate('common.billable')} @@ -333,6 +367,9 @@ export default compose( policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`, }, + policyTaxRates: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${report.policyID}`, + }, parentReport: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, }, From 4b8a102c3cef9510991b97f4d96082e20eb159e0 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 09:37:44 +0100 Subject: [PATCH 032/699] fix lint --- src/pages/EditRequestTaxAmountPage.js | 1 - src/pages/EditRequestTaxRatePage.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/pages/EditRequestTaxAmountPage.js b/src/pages/EditRequestTaxAmountPage.js index 2d10ccfe6f2b..dabfdfcb519f 100644 --- a/src/pages/EditRequestTaxAmountPage.js +++ b/src/pages/EditRequestTaxAmountPage.js @@ -54,7 +54,6 @@ function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurre > Date: Thu, 4 Jan 2024 09:42:40 +0100 Subject: [PATCH 033/699] remove is editing check --- src/pages/EditRequestTaxAmountPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/EditRequestTaxAmountPage.js b/src/pages/EditRequestTaxAmountPage.js index dabfdfcb519f..b5c20aa82d7c 100644 --- a/src/pages/EditRequestTaxAmountPage.js +++ b/src/pages/EditRequestTaxAmountPage.js @@ -29,7 +29,6 @@ const propTypes = { function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurrency, onNavigateToCurrency, onSubmit}) { const {translate} = useLocalize(); const textInput = useRef(null); - const isEditing = Navigation.getActiveRoute().includes('taxAmount'); const focusTimeoutRef = useRef(null); From 74f63f2eea05b7cb922d81867ecf95e82a7b45e4 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 13:28:12 +0100 Subject: [PATCH 034/699] complete types for policyTaxRates and Transaction --- src/types/onyx/PolicyTaxRates.ts | 25 +++++++++++++++++++++---- src/types/onyx/Transaction.ts | 3 +++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/types/onyx/PolicyTaxRates.ts b/src/types/onyx/PolicyTaxRates.ts index d549b620f51f..bc0905a0ff49 100644 --- a/src/types/onyx/PolicyTaxRates.ts +++ b/src/types/onyx/PolicyTaxRates.ts @@ -1,4 +1,4 @@ -type PolicyTaxRate = { +type TaxRate = { /** Name of a tax */ name: string; @@ -9,6 +9,23 @@ type PolicyTaxRate = { isDisabled?: boolean; }; -type PolicyTaxRates = Record; -export default PolicyTaxRate; -export type {PolicyTaxRates}; +type TaxRates = Record; +type PolicyTaxRates = { + /** Name of the tax */ + name: string; + + /** Default policy tax ID */ + defaultExternalID: string; + + /** Default value of taxes */ + defaultValue: string; + + /** Default foreign policy tax ID */ + foreignTaxDefault: string; + + /** List of tax names and values */ + taxes: TaxRates; +}; + +export default TaxRate; +export type {TaxRates, PolicyTaxRates}; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 53bfc36a4e47..d67aec17a845 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -94,6 +94,9 @@ type Transaction = { /** If the transaction was made in a foreign currency, we send the original amount and currency */ originalAmount?: number; originalCurrency?: string; + taxRate?: { + text: string; + }; }; export default Transaction; From f2503f4e90b1456eb143f100b7b47159bde9be79 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 13:31:22 +0100 Subject: [PATCH 035/699] add getDefaultTaxName func. in TransactionUtils --- src/libs/TransactionUtils.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 615bea7ff18d..51a9f6ee188e 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -4,11 +4,12 @@ import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {RecentWaypoint, Report, ReportAction, Transaction} from '@src/types/onyx'; -import PolicyTaxRate, {PolicyTaxRates} from '@src/types/onyx/PolicyTaxRates'; +import TaxRate, {PolicyTaxRates, TaxRates} from '@src/types/onyx/PolicyTaxRates'; import {Comment, Receipt, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import {EmptyObject} from '@src/types/utils/EmptyObject'; import {isCorporateCard, isExpensifyCard} from './CardUtils'; import DateUtils from './DateUtils'; +import * as Localize from './Localize'; import * as NumberUtils from './NumberUtils'; type AdditionalTransactionChanges = {comment?: string; waypoints?: WaypointCollection}; @@ -529,13 +530,24 @@ function calculateTaxAmount(percentage: string, amount: number) { /** * Calculates count of all tax enabled options */ -function getEnabledTaxRateCount(options: PolicyTaxRates) { - return Object.values(options).filter((option: PolicyTaxRate) => !option.isDisabled).length; +function getEnabledTaxRateCount(options: TaxRates) { + return Object.values(options).filter((option: TaxRate) => !option.isDisabled).length; +} + +/** + * Calculates get's the default tax name + */ +function getDefaultTaxName(policyTaxRates: PolicyTaxRates, transaction: Transaction) { + const defaultTaxKey = policyTaxRates.defaultExternalID; + const defaultTaxName = + (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${Localize.translateLocal('common.default')}`) || ''; + return transaction?.taxRate?.text ?? defaultTaxName; } export { buildOptimisticTransaction, calculateTaxAmount, + getDefaultTaxName, getEnabledTaxRateCount, getUpdatedTransaction, getTransaction, From f10e413a895ac554ad7e1fe6f82a0ec290634c36 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 13:32:09 +0100 Subject: [PATCH 036/699] use getDefaultTaxName --- src/components/MoneyRequestConfirmationList.js | 5 +---- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 4 +--- src/pages/iou/request/step/IOURequestStepTaxRatePage.js | 4 +--- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 5807eb66f143..260c27205e94 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -262,10 +262,7 @@ function MoneyRequestConfirmationList(props) { props.isDistanceRequest ? currency : props.iouCurrencyCode, ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transaction.taxAmount, props.iouCurrencyCode); - - const defaultTaxKey = props.policyTaxRates.defaultExternalID; - const defaultTaxName = (defaultTaxKey && `${props.policyTaxRates.taxes[defaultTaxKey].name} (${props.policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; - const taxRateTitle = (props.transaction.taxRate && props.transaction.taxRate.text) || defaultTaxName; + const taxRateTitle = TransactionUtils.getDefaultTaxName(props.policyTaxRates, transaction); const isFocused = useIsFocused(); const [formError, setFormError] = useState(''); diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 7ec95aec951f..55890c022179 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -292,9 +292,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode); - const defaultTaxKey = policyTaxRates.defaultExternalID; - const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; - const taxRateTitle = (transaction.taxRate && transaction.taxRate.text) || defaultTaxName; + const taxRateTitle = TransactionUtils.getDefaultTaxName(policyTaxRates, transaction); const isFocused = useIsFocused(); const [formError, setFormError] = useState(''); diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js index 22f826d0457b..ac25f2320684 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js @@ -59,9 +59,7 @@ function IOURequestStepTaxRatePage({ Navigation.goBack(backTo || ROUTES.HOME); }; - const defaultTaxKey = policyTaxRates.defaultExternalID; - const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; - const selectedTaxRate = (transaction.taxRate && transaction.taxRate.text) || defaultTaxName; + const selectedTaxRate = TransactionUtils.getDefaultTaxName(policyTaxRates, transaction); const updateTaxRates = (taxes) => { const taxAmount = getTaxAmount(policyTaxRates, taxes.text, transaction.amount); From 13dccd87ddf5d6a5aa952aed3c5c8a549e9b47a9 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 13:32:33 +0100 Subject: [PATCH 037/699] remove unused Navigation import --- src/pages/EditRequestTaxAmountPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/EditRequestTaxAmountPage.js b/src/pages/EditRequestTaxAmountPage.js index b5c20aa82d7c..6a413bf12655 100644 --- a/src/pages/EditRequestTaxAmountPage.js +++ b/src/pages/EditRequestTaxAmountPage.js @@ -5,7 +5,6 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import MoneyRequestAmountForm from './iou/steps/MoneyRequestAmountForm'; From 7c154f116a91734baab62bbbfc6a480c7e9e9bf1 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 17:44:14 +0100 Subject: [PATCH 038/699] remove tax amount calculation implementation from requestStepAmount --- .../iou/request/step/IOURequestStepAmount.js | 47 +------------------ 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index 84e0ac8533c5..531d4d4d403b 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -1,21 +1,15 @@ import {useFocusEffect} from '@react-navigation/native'; -import PropTypes from 'prop-types'; import React, {useCallback, useRef} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import taxPropTypes from '@components/taxPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; import {getRequestType} from '@libs/TransactionUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; import MoneyRequestAmountForm from '@pages/iou/steps/MoneyRequestAmountForm'; import reportPropTypes from '@pages/reportPropTypes'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; import StepScreenWrapper from './StepScreenWrapper'; @@ -32,28 +26,11 @@ const propTypes = { /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, - - /* Onyx Props */ - /** Collection of tax rates attached to a policy */ - policyTaxRates: taxPropTypes, - - /** The policy of the report */ - policy: PropTypes.shape({ - /** Is Tax tracking Enabled */ - isTaxTrackingEnabled: PropTypes.bool, - }), }; const defaultProps = { report: {}, transaction: {}, - policyTaxRates: {}, - policy: {}, -}; - -const getTaxAmount = (transaction, defaultTaxValue, amount) => { - const percentage = (transaction.taxRate ? transaction.taxRate.data.value : defaultTaxValue) || ''; - return TransactionUtils.calculateTaxAmount(percentage, amount); }; function IOURequestStepAmount({ @@ -63,8 +40,6 @@ function IOURequestStepAmount({ }, transaction, transaction: {currency: originalCurrency}, - policyTaxRates, - policy, }) { const {translate} = useLocalize(); const textInput = useRef(null); @@ -72,9 +47,6 @@ function IOURequestStepAmount({ const iouRequestType = getRequestType(transaction); const currency = selectedCurrency || originalCurrency; - const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)); - const isTaxTrackingEnabled = isPolicyExpenseChat && policy.isTaxTrackingEnabled; - useFocusEffect( useCallback(() => { focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION); @@ -101,12 +73,6 @@ function IOURequestStepAmount({ const navigateToNextPage = ({amount}) => { const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); - if ((iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL || backTo) && isTaxTrackingEnabled) { - const taxAmount = getTaxAmount(transaction, policyTaxRates.defaultValue, amountInSmallestCurrencyUnits); - const taxAmountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); - IOU.setMoneyRequestTaxAmount(transaction.transactionID, taxAmountInSmallestCurrencyUnits); - } - IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD); if (backTo) { @@ -153,15 +119,4 @@ IOURequestStepAmount.propTypes = propTypes; IOURequestStepAmount.defaultProps = defaultProps; IOURequestStepAmount.displayName = 'IOURequestStepAmount'; -export default compose( - withWritableReportOrNotFound, - withFullTransactionOrNotFound, - withOnyx({ - policyTaxRates: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${report ? report.policyID : '0'}`, - }, - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, - }, - }), -)(IOURequestStepAmount); +export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepAmount); From d501ad75a419ea632796612cc5beb347706df0ac Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 17:44:50 +0100 Subject: [PATCH 039/699] set tax Amount --- ...poraryForRefactorRequestConfirmationList.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 55890c022179..79dd9e679263 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -8,6 +8,7 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; +import usePrevious from '@hooks/usePrevious'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; @@ -202,6 +203,11 @@ const defaultProps = { policyTaxRates: {}, }; +const getTaxAmount = (transaction, defaultTaxValue) => { + const percentage = (transaction.taxRate ? transaction.taxRate.data.value : defaultTaxValue) || ''; + return TransactionUtils.calculateTaxAmount(percentage, transaction.amount); +}; + function MoneyTemporaryForRefactorRequestConfirmationList({ bankAccountRoute, canModifyParticipants, @@ -294,6 +300,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const taxRateTitle = TransactionUtils.getDefaultTaxName(policyTaxRates, transaction); + const previousTransactionTaxAmount = usePrevious(transaction.taxAmount); + const isFocused = useIsFocused(); const [formError, setFormError] = useState(''); @@ -351,6 +359,16 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ IOU.setMoneyRequestAmount_temporaryForRefactor(transaction.transactionID, amount, currency); }, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency]); + // calculate and set tax amount in transaction draft + useEffect(() => { + const taxAmount = getTaxAmount(transaction, policyTaxRates.defaultValue); + const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); + if (previousTransactionTaxAmount !== transaction.taxAmount && amountInSmallestCurrencyUnits !== transaction.taxAmount) { + return; + } + IOU.setMoneyRequestTaxAmount(transaction.transactionID, amountInSmallestCurrencyUnits); + }, [policyTaxRates.defaultValue, transaction, previousTransactionTaxAmount]); + /** * Returns the participants with amount * @param {Array} participants From 13deb831497581a1a314f42e975127cb6bde2883 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 4 Jan 2024 22:19:40 +0100 Subject: [PATCH 040/699] add Tax amount and Code API for updates --- src/libs/actions/IOU.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index b999435cc3e7..dc087ba42eac 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1051,6 +1051,36 @@ function updateMoneyRequestDate(transactionID, transactionThreadReportID, val) { API.write('UpdateMoneyRequestDate', params, onyxData); } +/** + * Updates the created tax amount of a money request + * + * @param {String} transactionID + * @param {String} optimisticReportActionID + * @param {Number} val + */ +function updateMoneyRequestTaxAmount(transactionID, optimisticReportActionID, val) { + const transactionChanges = { + taxAmount: val, + }; + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, true); + API.write('UpdateMoneyRequestTaxAmount', params, onyxData); +} + +/** + * Updates the created tax rate of a money request + * + * @param {String} transactionID + * @param {String} optimisticReportActionID + * @param {String} val + */ +function updateMoneyRequestTaxRate(transactionID, optimisticReportActionID, val) { + const transactionChanges = { + taxCode: val, + }; + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, true); + API.write('UpdateMoneyRequestTaxRate', params, onyxData); +} + /** * Edits an existing distance request * @@ -3490,6 +3520,8 @@ export { setUpDistanceTransaction, navigateToNextPage, updateMoneyRequestDate, + updateMoneyRequestTaxAmount, + updateMoneyRequestTaxRate, updateMoneyRequestAmountAndCurrency, replaceReceipt, detachReceipt, From fc3e59356aa037f68c525100a5b83358120fe0d9 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 8 Jan 2024 16:46:55 +0100 Subject: [PATCH 041/699] add function to check if tax rate has enabled options --- src/libs/OptionsListUtils.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 0dc12c720f31..3aa9ffc690bb 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1058,6 +1058,17 @@ function getTagListSections(rawTags, recentlyUsedTags, selectedOptions, searchIn return tagSections; } +/** + * Verifies that there is at least one enabled option + * + * @param {Object[]} options - an initial strings array + * @property {boolean} [isDisabled] - Indicates if the tax rate is disabled. + * @returns {Boolean} + */ +function hasEnabledOptionsForTaxRate(options) { + return _.some(options, (option) => !option.isDisabled); +} + /** * Represents the data for a single tax rate. * @@ -1953,6 +1964,7 @@ export { getLastMessageTextForReport, getEnabledCategoriesCount, hasEnabledOptions, + hasEnabledOptionsForTaxRate, sortCategories, getCategoryOptionTree, formatMemberForList, From a6daa02f50f5ffb618e366dc6f10bbb3f7eedd4c Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 8 Jan 2024 16:48:15 +0100 Subject: [PATCH 042/699] add defaults for tax rate and amount and amount title --- .../ReportActionItem/MoneyRequestView.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 0b662f9d3578..a38b711e7192 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -155,6 +155,12 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const isCardTransaction = TransactionUtils.isCardTransaction(transaction); const cardProgramName = isCardTransaction ? CardUtils.getCardDescription(transactionCardID) : ''; + const transactionTaxAmount = (transaction.taxAmount && transaction.taxAmount) || 0; + const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transactionTaxAmount, transactionCurrency); + + const transactionTaxCode = transaction.taxCode && transaction.taxCode; + const taxRateTitle = (transactionTaxCode && policyTaxRates.taxes[transactionTaxCode].name) || ''; + // Flags for allowing or disallowing editing a money request const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const isCancelled = moneyRequestReport && moneyRequestReport.isCancelledIOU; @@ -181,7 +187,11 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); // A flag for showing tax rate - const shouldShowTax = isPolicyExpenseChat && policy.isTaxTrackingEnabled; + const shouldShowTax = + isPolicyExpenseChat && + policy && + policy.isTaxTrackingEnabled && + ((transactionTaxCode && transactionTaxAmount) || OptionsListUtils.hasEnabledOptionsForTaxRate(lodashValues(policyTaxRates.taxes))); const {getViolationsForField} = useViolations(transactionViolations); const hasViolations = useCallback((field) => canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); @@ -359,7 +369,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate {shouldShowTax && ( Date: Mon, 8 Jan 2024 16:50:03 +0100 Subject: [PATCH 043/699] add defaults for tax rate and amount and amount title --- src/pages/EditRequestPage.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 7e91b6787e5f..f47950eb71ff 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -7,6 +7,7 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView import categoryPropTypes from '@components/categoryPropTypes'; import ScreenWrapper from '@components/ScreenWrapper'; import tagPropTypes from '@components/tagPropTypes'; +import taxPropTypes from '@components/taxPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; @@ -66,6 +67,10 @@ const propTypes = { /** Is Tax tracking Enabled */ isTaxTrackingEnabled: PropTypes.bool, }), + + /* Onyx Props */ + /** Collection of tax rates attached to a policy */ + policyTaxRates: taxPropTypes, }; const defaultProps = { @@ -75,9 +80,10 @@ const defaultProps = { parentReportActions: {}, transaction: {}, policy: {}, + policyTaxRates: {}, }; -function EditRequestPage({report, policy, route, policyCategories, policyTags, parentReportActions, transaction}) { +function EditRequestPage({report, policy, policyTaxRates, route, policyCategories, policyTags, parentReportActions, transaction}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); const { @@ -92,6 +98,11 @@ function EditRequestPage({report, policy, route, policyCategories, policyTags, p const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; const fieldToEdit = lodashGet(route, ['params', 'field'], ''); + const transactionTaxAmount = (transaction.taxAmount && transaction.taxAmount) || 0; + + const transactionTaxCode = transaction.taxCode && transaction.taxCode; + const taxRateTitle = (transactionTaxCode && policyTaxRates.taxes[transactionTaxCode].name) || ''; + // For now, it always defaults to the first tag of the policy const policyTag = PolicyUtils.getTag(policyTags); const policyTagList = lodashGet(policyTag, 'tags', {}); @@ -267,10 +278,13 @@ function EditRequestPage({report, policy, route, policyCategories, policyTags, p if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAX_AMOUNT && shouldShowTax) { return ( {}} + defaultAmount={transactionAmount} + defaultTaxAmount={transactionTaxAmount} + defaultCurrency={defaultCurrency} + onNavigateToCurrency={() => { + const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); + Navigation.navigate(ROUTES.EDIT_CURRENCY_REQUEST.getRoute(report.reportID, defaultCurrency, activeRoute)); + }} onSubmit={() => {}} /> ); @@ -279,7 +293,7 @@ function EditRequestPage({report, policy, route, policyCategories, policyTags, p if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAX_RATE && shouldShowTax) { return ( {}} /> @@ -336,6 +350,9 @@ export default compose( policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, + policyTaxRates: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${report.policyID}`, + }, parentReportActions: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, canEvict: false, From 24e7c072e69f5853ac6499d2d1345f7cf807bf7d Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 8 Jan 2024 17:17:19 +0100 Subject: [PATCH 044/699] add update functions to taxAmount and taxRate --- src/pages/EditRequestPage.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index f47950eb71ff..df13c727e430 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -139,6 +139,26 @@ function EditRequestPage({report, policy, policyTaxRates, route, policyCategorie Navigation.dismissModal(report.reportID); } + const updateTaxAmount = (transactionChanges) => { + if (transactionChanges.amount === transactionTaxAmount) { + return; + } + + const newTaxAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(transactionChanges.amount)); + IOU.updateMoneyRequestTaxAmount(transaction.transactionID, report.reportID, newTaxAmount); + Navigation.dismissModal(report.reportID); + }; + + const updateTaxRate = (transactionChanges) => { + const newTaxCode = transactionChanges.data.code; + if (newTaxCode === transactionTaxCode) { + return; + } + + IOU.updateMoneyRequestTaxRate(transaction.transactionID, report.reportID, newTaxCode); + Navigation.dismissModal(report.reportID); + }; + const saveAmountAndCurrency = useCallback( ({amount, currency: newCurrency}) => { const newAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); @@ -285,7 +305,7 @@ function EditRequestPage({report, policy, policyTaxRates, route, policyCategorie const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); Navigation.navigate(ROUTES.EDIT_CURRENCY_REQUEST.getRoute(report.reportID, defaultCurrency, activeRoute)); }} - onSubmit={() => {}} + onSubmit={updateTaxAmount} /> ); } @@ -295,7 +315,7 @@ function EditRequestPage({report, policy, policyTaxRates, route, policyCategorie {}} + onSubmit={updateTaxRate} /> ); } From 101aa5b69eceb36e96d8ba09d1208be8f369fbee Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 8 Jan 2024 19:59:18 +0100 Subject: [PATCH 045/699] update default amount and taxAmount --- src/pages/EditRequestPage.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index df13c727e430..3f346d649bca 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -82,7 +82,10 @@ const defaultProps = { policy: {}, policyTaxRates: {}, }; - +const getTaxAmount = (transaction, transactionTaxCode, policyTaxRates) => { + const percentage = (transactionTaxCode ? policyTaxRates.taxes[transactionTaxCode].value : policyTaxRates.defaultValue) || ''; + return CurrencyUtils.convertToBackendAmount(Number.parseFloat(TransactionUtils.calculateTaxAmount(percentage, transaction.amount))); +}; function EditRequestPage({report, policy, policyTaxRates, route, policyCategories, policyTags, parentReportActions, transaction}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); @@ -298,8 +301,8 @@ function EditRequestPage({report, policy, policyTaxRates, route, policyCategorie if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAX_AMOUNT && shouldShowTax) { return ( { const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); From 61bf4767d02c4f00ed8eecd6f343567dad43a5dc Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 9 Jan 2024 07:25:51 +0100 Subject: [PATCH 046/699] use function for tax policy --- src/components/MoneyRequestConfirmationList.js | 5 +++-- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 3 ++- src/components/ReportActionItem/MoneyRequestView.js | 6 ++---- src/libs/PolicyUtils.ts | 5 +++++ src/pages/EditRequestPage.js | 5 +++-- src/types/onyx/Policy.ts | 3 +++ 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 260c27205e94..385d411427ba 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -20,6 +20,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import {isTaxPolicyEnabled} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -247,8 +248,8 @@ function MoneyRequestConfirmationList(props) { // A flag for showing the tags field const shouldShowTags = props.isPolicyExpenseChat && (props.iouTag || OptionsListUtils.hasEnabledOptions(_.values(policyTagList))); - // A flag for showing tax fields - tax rate and tax amount - const shouldShowTax = props.isPolicyExpenseChat && props.policy.isTaxTrackingEnabled; + // A flag for showing tax rate + const shouldShowTax = isTaxPolicyEnabled(props.isPolicyExpenseChat, props.policy); // A flag for showing the billable field const shouldShowBillable = !lodashGet(props.policy, 'disabledFields.defaultBillable', true); diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 335a9e2f6801..5bd42ff11cf7 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -20,6 +20,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import {isTaxPolicyEnabled} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -283,7 +284,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowTags = isPolicyExpenseChat && OptionsListUtils.hasEnabledOptions(_.values(policyTagList)); // A flag for showing tax rate - const shouldShowTax = isPolicyExpenseChat && policy && policy.isTaxTrackingEnabled; + const shouldShowTax = isTaxPolicyEnabled(isPolicyExpenseChat, policy); // A flag for showing the billable field const shouldShowBillable = !lodashGet(policy, 'disabledFields.defaultBillable', true); diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index a38b711e7192..bd22c3f42415 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -30,6 +30,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import {isTaxPolicyEnabled} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -188,10 +189,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate // A flag for showing tax rate const shouldShowTax = - isPolicyExpenseChat && - policy && - policy.isTaxTrackingEnabled && - ((transactionTaxCode && transactionTaxAmount) || OptionsListUtils.hasEnabledOptionsForTaxRate(lodashValues(policyTaxRates.taxes))); + isTaxPolicyEnabled(isPolicyExpenseChat, policy) && ((transactionTaxCode && transactionTaxAmount) || OptionsListUtils.hasEnabledOptionsForTaxRate(lodashValues(policyTaxRates.taxes))); const {getViolationsForField} = useViolations(transactionViolations); const hasViolations = useCallback((field) => canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 0cab97299324..831dc625cb95 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -202,6 +202,10 @@ function isPaidGroupPolicy(policy: OnyxEntry): boolean { return policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE; } +function isTaxPolicyEnabled(isPolicyExpenseChat: boolean, policy: OnyxEntry): boolean { + return (isPolicyExpenseChat && policy?.isTaxTrackingEnabled) ?? false; +} + export { getActivePolicies, hasPolicyMemberError, @@ -215,6 +219,7 @@ export { isExpensifyTeam, isExpensifyGuideTeam, isPolicyAdmin, + isTaxPolicyEnabled, getMemberAccountIDsForWorkspace, getIneligibleInvitees, getTag, diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 7f9839f39111..8f1830e43a5f 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -14,6 +14,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import {isTaxPolicyEnabled} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; @@ -83,7 +84,7 @@ const defaultProps = { policyTaxRates: {}, }; const getTaxAmount = (transaction, transactionTaxCode, policyTaxRates) => { - const percentage = (transactionTaxCode ? policyTaxRates.taxes[transactionTaxCode].value : policyTaxRates.defaultValue) || ''; + const percentage = (transactionTaxCode ? policyTaxRates.taxes[transactionTaxCode].value : policyTaxRates.defaultValue) || ''; return CurrencyUtils.convertToBackendAmount(Number.parseFloat(TransactionUtils.calculateTaxAmount(percentage, transaction.amount))); }; function EditRequestPage({report, policy, policyTaxRates, route, policyCategories, policyTags, parentReportActions, transaction}) { @@ -121,7 +122,7 @@ function EditRequestPage({report, policy, policyTaxRates, route, policyCategorie const shouldShowTags = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagList))); // A flag for showing tax rate - const shouldShowTax = isPolicyExpenseChat && policy && policy.isTaxTrackingEnabled; + const shouldShowTax = isTaxPolicyEnabled(isPolicyExpenseChat, policy); // Decides whether to allow or disallow editing a money request useEffect(() => { diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index ff3a5e1dd23c..fc2eb02171da 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -65,6 +65,9 @@ type Policy = { /** Whether chat rooms can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ areChatRoomsEnabled: boolean; + /** Is Tax tracking Enabled */ + isTaxTrackingEnabled: boolean; + /** Whether policy expense chats can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ isPolicyExpenseChatEnabled: boolean; From e85912965de70ef3c933b9b2ed7c9d22a80f8671 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 9 Jan 2024 07:35:50 +0100 Subject: [PATCH 047/699] make isTaxTrackingEnabled optional --- src/types/onyx/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index fc2eb02171da..fc7f273de76d 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -66,7 +66,7 @@ type Policy = { areChatRoomsEnabled: boolean; /** Is Tax tracking Enabled */ - isTaxTrackingEnabled: boolean; + isTaxTrackingEnabled?: boolean; /** Whether policy expense chats can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ isPolicyExpenseChatEnabled: boolean; From fab239109cc035481c0429affee138a4ac7d01b8 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 05:08:53 +0100 Subject: [PATCH 048/699] add tax amount and code type to Transaction types --- src/types/onyx/Transaction.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 399f1414db70..4aa61b8923c5 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -51,6 +51,8 @@ type Routes = Record; type Transaction = { amount: number; + taxAmount?: number; + taxCode?: string; billable: boolean; category: string; comment: Comment; From 9fd85f95026327b8f919252ba26f8320eb045bb2 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 05:11:34 +0100 Subject: [PATCH 049/699] get tax amount abs value and tax code --- src/libs/TransactionUtils.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 5f8443b126c5..1b78d6f8ab6d 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -282,6 +282,27 @@ function getAmount(transaction: OnyxEntry, isFromExpenseReport: boo return amount ? -amount : 0; } +/** + * Return the tax amount field from the transaction. + */ +function getTaxAmount(transaction: OnyxEntry, isFromExpenseReport: boolean): number { + // IOU requests cannot have negative values but they can be stored as negative values, let's return absolute value + if (!isFromExpenseReport) { + return Math.abs(transaction?.taxAmount ?? 0); + } + + // To avoid -0 being shown, lets only change the sign if the value is other than 0. + const amount = transaction?.taxAmount ?? 0; + return amount ? -amount : 0; +} + +/** + * Return the tax code from the transaction. + */ +function getTaxCode(transaction: OnyxEntry): string { + return transaction?.taxCode ?? ''; +} + /** * Return the currency field from the transaction, return the modifiedCurrency if present. */ @@ -559,6 +580,8 @@ export { isManualRequest, isScanRequest, getAmount, + getTaxAmount, + getTaxCode, getCurrency, getDistance, getCardID, From 33cf5c438ec307cedb1fc1ef6b17691b046050b6 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 05:13:32 +0100 Subject: [PATCH 050/699] add new fields in getTransactionDetails --- src/libs/ReportUtils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0d7658adf180..e4d7cb728e82 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -281,6 +281,8 @@ type TransactionDetails = | { created: string; amount: number; + taxAmount?: number; + taxCode?: string; currency: string; merchant: string; waypoints?: WaypointCollection; @@ -1839,6 +1841,8 @@ function getTransactionDetails(transaction: OnyxEntry, createdDateF return { created: TransactionUtils.getCreated(transaction, createdDateFormat), amount: TransactionUtils.getAmount(transaction, isNotEmptyObject(report) && isExpenseReport(report)), + taxAmount: TransactionUtils.getTaxAmount(transaction, isNotEmptyObject(report) && isExpenseReport(report)), + taxCode: TransactionUtils.getTaxCode(transaction), currency: TransactionUtils.getCurrency(transaction), comment: TransactionUtils.getDescription(transaction), merchant: TransactionUtils.getMerchant(transaction), From 9d6e5b0372519213cf0b9dd92cd7d2a3370e6e9d Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 05:16:06 +0100 Subject: [PATCH 051/699] get and use new tax amount and code fields from getTransactionDetails --- .../ReportActionItem/MoneyRequestView.js | 15 ++++++------ src/pages/EditRequestPage.js | 23 +++++++------------ 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 73bbda7bd3b7..c656ff45add7 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -135,6 +135,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const { created: transactionDate, amount: transactionAmount, + taxAmount: transactionTaxAmount, + taxCode: transactionTaxCode, currency: transactionCurrency, comment: transactionDescription, merchant: transactionMerchant, @@ -156,11 +158,11 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const isCardTransaction = TransactionUtils.isCardTransaction(transaction); const cardProgramName = isCardTransaction ? CardUtils.getCardDescription(transactionCardID) : ''; - const transactionTaxAmount = (transaction.taxAmount && transaction.taxAmount) || 0; - const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transactionTaxAmount, transactionCurrency); + const formattedTaxAmount = transactionTaxAmount ? CurrencyUtils.convertToDisplayString(transactionTaxAmount, transactionCurrency) : ''; - const transactionTaxCode = transaction.taxCode && transaction.taxCode; - const taxRateTitle = (transactionTaxCode && policyTaxRates.taxes[transactionTaxCode].name) || ''; + const taxName = `${policyTaxRates.taxes[transactionTaxCode].name}`; + const taxValue = `${policyTaxRates.taxes[transactionTaxCode].value}`; + const taxRateTitle = transactionTaxCode ? `${taxName} (${taxValue})` : ''; // Flags for allowing or disallowing editing a money request const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); @@ -188,8 +190,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); // A flag for showing tax rate - const shouldShowTax = - isTaxPolicyEnabled(isPolicyExpenseChat, policy) && ((transactionTaxCode && transactionTaxAmount) || OptionsListUtils.hasEnabledOptionsForTaxRate(lodashValues(policyTaxRates.taxes))); + const shouldShowTax = isTaxPolicyEnabled(isPolicyExpenseChat, policy) && transactionTaxCode && transactionTaxAmount; const {getViolationsForField} = useViolations(transactionViolations); const hasViolations = useCallback((field) => canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); @@ -393,7 +394,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate {shouldShowTax && ( { +const getTaxAmount = (transactionAmount, transactionTaxCode, policyTaxRates) => { const percentage = (transactionTaxCode ? policyTaxRates.taxes[transactionTaxCode].value : policyTaxRates.defaultValue) || ''; - return CurrencyUtils.convertToBackendAmount(Number.parseFloat(TransactionUtils.calculateTaxAmount(percentage, transaction.amount))); + return CurrencyUtils.convertToBackendAmount(Number.parseFloat(TransactionUtils.calculateTaxAmount(percentage, transactionAmount))); }; function EditRequestPage({report, policy, policyTaxRates, route, policyCategories, policyTags, parentReportActions, transaction}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); const { amount: transactionAmount, + taxAmount: transactionTaxAmount, + taxCode: transactionTaxCode, currency: transactionCurrency, comment: transactionDescription, merchant: transactionMerchant, @@ -102,10 +104,9 @@ function EditRequestPage({report, policy, policyTaxRates, route, policyCategorie const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; const fieldToEdit = lodashGet(route, ['params', 'field'], ''); - const transactionTaxAmount = (transaction.taxAmount && transaction.taxAmount) || 0; - - const transactionTaxCode = transaction.taxCode && transaction.taxCode; - const taxRateTitle = (transactionTaxCode && policyTaxRates.taxes[transactionTaxCode].name) || ''; + const taxName = `${policyTaxRates.taxes[transactionTaxCode].name}`; + const taxValue = `${policyTaxRates.taxes[transactionTaxCode].value}`; + const taxRateTitle = transactionTaxCode ? `${taxName} (${taxValue})` : ''; // For now, it always defaults to the first tag of the policy const policyTag = PolicyUtils.getTag(policyTags); @@ -144,10 +145,6 @@ function EditRequestPage({report, policy, policyTaxRates, route, policyCategorie } const updateTaxAmount = (transactionChanges) => { - if (transactionChanges.amount === transactionTaxAmount) { - return; - } - const newTaxAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(transactionChanges.amount)); IOU.updateMoneyRequestTaxAmount(transaction.transactionID, report.reportID, newTaxAmount); Navigation.dismissModal(report.reportID); @@ -155,10 +152,6 @@ function EditRequestPage({report, policy, policyTaxRates, route, policyCategorie const updateTaxRate = (transactionChanges) => { const newTaxCode = transactionChanges.data.code; - if (newTaxCode === transactionTaxCode) { - return; - } - IOU.updateMoneyRequestTaxRate(transaction.transactionID, report.reportID, newTaxCode); Navigation.dismissModal(report.reportID); }; @@ -310,7 +303,7 @@ function EditRequestPage({report, policy, policyTaxRates, route, policyCategorie return ( { const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); From 25cb89edceac9386323076eb7eda8b53a5e9a17c Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 05:29:28 +0100 Subject: [PATCH 052/699] add getTaxName to TransactionUtils --- src/libs/TransactionUtils.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 1b78d6f8ab6d..8aa9f05f813b 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -558,7 +558,7 @@ function getEnabledTaxRateCount(options: TaxRates) { } /** - * Calculates get's the default tax name + * Get's the default tax name */ function getDefaultTaxName(policyTaxRates: PolicyTaxRates, transaction: Transaction) { const defaultTaxKey = policyTaxRates.defaultExternalID; @@ -567,9 +567,19 @@ function getDefaultTaxName(policyTaxRates: PolicyTaxRates, transaction: Transact return transaction?.taxRate?.text ?? defaultTaxName; } +/** + * Get's the tax name + */ +function getTaxName(taxes: TaxRates, transactionTaxCode: string) { + const taxName = `${taxes[transactionTaxCode].name}`; + const taxValue = `${taxes[transactionTaxCode].value}`; + return transactionTaxCode ? `${taxName} (${taxValue})` : ''; +} + export { buildOptimisticTransaction, calculateTaxAmount, + getTaxName, getDefaultTaxName, getEnabledTaxRateCount, getUpdatedTransaction, From 9a9b1820bd7d3d8b157ad0d5cdc846a36a1bbbad Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 05:30:35 +0100 Subject: [PATCH 053/699] use getTaxName --- src/components/ReportActionItem/MoneyRequestView.js | 4 +--- src/pages/EditRequestPage.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index c656ff45add7..60ed51d10506 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -160,9 +160,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const formattedTaxAmount = transactionTaxAmount ? CurrencyUtils.convertToDisplayString(transactionTaxAmount, transactionCurrency) : ''; - const taxName = `${policyTaxRates.taxes[transactionTaxCode].name}`; - const taxValue = `${policyTaxRates.taxes[transactionTaxCode].value}`; - const taxRateTitle = transactionTaxCode ? `${taxName} (${taxValue})` : ''; + const taxRateTitle = TransactionUtils.getTaxName(policyTaxRates.taxes, transactionTaxCode); // Flags for allowing or disallowing editing a money request const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index a3375efbbdd5..7508e0796845 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -104,9 +104,7 @@ function EditRequestPage({report, policy, policyTaxRates, route, policyCategorie const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; const fieldToEdit = lodashGet(route, ['params', 'field'], ''); - const taxName = `${policyTaxRates.taxes[transactionTaxCode].name}`; - const taxValue = `${policyTaxRates.taxes[transactionTaxCode].value}`; - const taxRateTitle = transactionTaxCode ? `${taxName} (${taxValue})` : ''; + const taxRateTitle = TransactionUtils.getTaxName(policyTaxRates.taxes, transactionTaxCode); // For now, it always defaults to the first tag of the policy const policyTag = PolicyUtils.getTag(policyTags); From f723d0f68bf58f750df537c856f73845d95f044a Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 13:45:33 +0100 Subject: [PATCH 054/699] Update src/components/MoneyTemporaryForRefactorRequestConfirmationList.js Co-authored-by: Michael (Mykhailo) Kravchenko --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 5bd42ff11cf7..85948dbaa530 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -360,7 +360,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ IOU.setMoneyRequestAmount_temporaryForRefactor(transaction.transactionID, amount, currency); }, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency]); - // calculate and set tax amount in transaction draft + // Calculate and set tax amount in transaction draft useEffect(() => { const taxAmount = getTaxAmount(transaction, policyTaxRates.defaultValue); const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); From cc2831e8358a09c23db14511811df9141fcdde3c Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 13:45:48 +0100 Subject: [PATCH 055/699] Update src/libs/TransactionUtils.ts Co-authored-by: Michael (Mykhailo) Kravchenko --- src/libs/TransactionUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 8aa9f05f813b..87c23974ab8a 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -568,7 +568,7 @@ function getDefaultTaxName(policyTaxRates: PolicyTaxRates, transaction: Transact } /** - * Get's the tax name + * Gets the tax name */ function getTaxName(taxes: TaxRates, transactionTaxCode: string) { const taxName = `${taxes[transactionTaxCode].name}`; From 1270f49460c6e83d4e429c868723f583da8fe4ce Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 13:45:56 +0100 Subject: [PATCH 056/699] Update src/libs/TransactionUtils.ts Co-authored-by: Michael (Mykhailo) Kravchenko --- src/libs/TransactionUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 87c23974ab8a..64e3ab79ab2b 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -558,7 +558,7 @@ function getEnabledTaxRateCount(options: TaxRates) { } /** - * Get's the default tax name + * Gets the default tax name */ function getDefaultTaxName(policyTaxRates: PolicyTaxRates, transaction: Transaction) { const defaultTaxKey = policyTaxRates.defaultExternalID; From 37d651384db96233599b2410a582a35cca5173d0 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 13:46:42 +0100 Subject: [PATCH 057/699] Update src/types/onyx/Policy.ts Co-authored-by: Michael (Mykhailo) Kravchenko --- src/types/onyx/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 5d315508f35a..3a7aacd5808a 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -62,7 +62,7 @@ type Policy = { /** The custom units data for this policy */ customUnits?: Record; - /** Is Tax tracking Enabled */ + /** Is tax tracking enabled */ isTaxTrackingEnabled?: boolean; /** Whether policy expense chats can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ From 584df427d6b35fc5c2b1e70e03ef88a9429320b2 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 10 Jan 2024 13:47:06 +0100 Subject: [PATCH 058/699] Update src/pages/EditRequestPage.js Co-authored-by: Michael (Mykhailo) Kravchenko --- src/pages/EditRequestPage.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 7508e0796845..46fb719d26b5 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -62,14 +62,13 @@ const propTypes = { /** Transaction that stores the request data */ transaction: transactionPropTypes, - + /* Onyx Props */ /** The policy of the report */ policy: PropTypes.shape({ /** Is Tax tracking Enabled */ isTaxTrackingEnabled: PropTypes.bool, }), - /* Onyx Props */ /** Collection of tax rates attached to a policy */ policyTaxRates: taxPropTypes, }; From 1e8ffbc1da439bc23602f9bd67985dbbc0c51ed2 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 16 Jan 2024 10:59:42 +0100 Subject: [PATCH 059/699] fix prettier --- src/libs/actions/IOU.js | 44 ++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 47d959db9f16..816763037c3d 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1184,33 +1184,33 @@ function updateMoneyRequestTag(transactionID, transactionThreadReportID, tag) { } /** -* Updates the created tax amount of a money request -* -* @param {String} transactionID -* @param {String} optimisticReportActionID -* @param {Number} val -*/ + * Updates the created tax amount of a money request + * + * @param {String} transactionID + * @param {String} optimisticReportActionID + * @param {Number} val + */ function updateMoneyRequestTaxAmount(transactionID, optimisticReportActionID, val) { - const transactionChanges = { - taxAmount: val, - }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, true); - API.write('UpdateMoneyRequestTaxAmount', params, onyxData); + const transactionChanges = { + taxAmount: val, + }; + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, true); + API.write('UpdateMoneyRequestTaxAmount', params, onyxData); } /** -* Updates the created tax rate of a money request -* -* @param {String} transactionID -* @param {String} optimisticReportActionID -* @param {String} val -*/ + * Updates the created tax rate of a money request + * + * @param {String} transactionID + * @param {String} optimisticReportActionID + * @param {String} val + */ function updateMoneyRequestTaxRate(transactionID, optimisticReportActionID, val) { - const transactionChanges = { - taxCode: val, - }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, true); - API.write('UpdateMoneyRequestTaxRate', params, onyxData); + const transactionChanges = { + taxCode: val, + }; + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, true); + API.write('UpdateMoneyRequestTaxRate', params, onyxData); } /** From 404732f89f3c9c3032db907ef6b4acb47b5c9059 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Mon, 22 Jan 2024 13:34:48 -0300 Subject: [PATCH 060/699] Kill ReconnectToReport --- src/libs/actions/Report.ts | 59 ---------------------- src/pages/home/report/ReportActionsView.js | 29 +---------- 2 files changed, 2 insertions(+), 86 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 2ac85dfafa27..72b977d01e7d 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -767,58 +767,6 @@ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction: P } } -/** Get the latest report history without marking the report as read. */ -function reconnect(reportID: string) { - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - reportName: allReports?.[reportID]?.reportName ?? CONST.REPORT.DEFAULT_REPORT_NAME, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, - value: { - isLoadingInitialReportActions: true, - isLoadingNewerReportActions: false, - isLoadingOlderReportActions: false, - }, - }, - ]; - - const successData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, - value: { - isLoadingInitialReportActions: false, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, - value: { - isLoadingInitialReportActions: false, - }, - }, - ]; - - type ReconnectToReportParameters = { - reportID: string; - }; - - const parameters: ReconnectToReportParameters = { - reportID, - }; - - API.write('ReconnectToReport', parameters, {optimisticData, successData, failureData}); -} - /** * Gets the older actions that have not been read yet. * Normally happens when you scroll up on a chat, and the actions have not been read yet. @@ -1095,12 +1043,6 @@ function handleReportChanged(report: OnyxEntry) { conciergeChatReportID = report.reportID; } } - - // A report can be missing a name if a comment is received via pusher event and the report does not yet exist in Onyx (eg. a new DM created with the logged in person) - // In this case, we call reconnect so that we can fetch the report data without marking it as read - if (report.reportID && report.reportName === undefined) { - reconnect(report.reportID); - } } Onyx.connect({ @@ -2654,7 +2596,6 @@ export { searchInServer, addComment, addAttachment, - reconnect, updateWelcomeMessage, updateWriteCapabilityAndNavigate, updateNotificationPreference, diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 2758437a3962..d2ec0f124a62 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -88,10 +88,7 @@ function ReportActionsView(props) { const isFirstRender = useRef(true); const hasCachedActions = useInitialValue(() => _.size(props.reportActions) > 0); const mostRecentIOUReportActionID = useInitialValue(() => ReportActionsUtils.getMostRecentIOURequestActionID(props.reportActions)); - - const prevNetworkRef = useRef(props.network); const prevAuthTokenType = usePrevious(props.session.authTokenType); - const prevIsSmallScreenWidthRef = useRef(props.isSmallScreenWidth); const isFocused = useIsFocused(); @@ -118,36 +115,14 @@ function ReportActionsView(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - const prevNetwork = prevNetworkRef.current; - // When returning from offline to online state we want to trigger a request to OpenReport which - // will fetch the reportActions data and mark the report as read. If the report is not fully visible - // then we call ReconnectToReport which only loads the reportActions data without marking the report as read. - const wasNetworkChangeDetected = lodashGet(prevNetwork, 'isOffline') && !lodashGet(props.network, 'isOffline'); - if (wasNetworkChangeDetected) { - if (isReportFullyVisible) { - openReportIfNecessary(); - } else { - Report.reconnect(reportID); - } - } - // update ref with current network state - prevNetworkRef.current = props.network; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.network, props.report, isReportFullyVisible]); useEffect(() => { const wasLoginChangedDetected = prevAuthTokenType === 'anonymousAccount' && !props.session.authTokenType; if (wasLoginChangedDetected && didUserLogInDuringSession() && isUserCreatedPolicyRoom(props.report)) { - if (isReportFullyVisible) { - openReportIfNecessary(); - } else { - Report.reconnect(reportID); - } + openReportIfNecessary(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.session, props.report, isReportFullyVisible]); - + }, [props.session, props.report]); useEffect(() => { const prevIsSmallScreenWidth = prevIsSmallScreenWidthRef.current; // If the view is expanded from mobile to desktop layout From 0b4b6669a894f02f91af23fcac12e66e309e9494 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Mon, 22 Jan 2024 16:51:10 -0300 Subject: [PATCH 061/699] Lint --- src/pages/home/report/ReportActionsView.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index d2ec0f124a62..adb771ed4f81 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -115,7 +115,6 @@ function ReportActionsView(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { const wasLoginChangedDetected = prevAuthTokenType === 'anonymousAccount' && !props.session.authTokenType; if (wasLoginChangedDetected && didUserLogInDuringSession() && isUserCreatedPolicyRoom(props.report)) { From be682af3bd4311fcf6a82eb7b44b8d565655937b Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 26 Jan 2024 12:07:04 +0000 Subject: [PATCH 062/699] refactor(typescript): migrate iou page --- src/ONYXKEYS.ts | 1 + src/libs/Navigation/types.ts | 7 +- src/libs/actions/IOU.js | 1 - .../withReportAndReportActionOrNotFound.tsx | 22 +- src/pages/iou/NewDistanceRequestPage.js | 85 -------- src/pages/iou/NewDistanceRequestPage.tsx | 61 ++++++ src/pages/iou/SplitBillDetailsPage.js | 194 ------------------ src/pages/iou/SplitBillDetailsPage.tsx | 170 +++++++++++++++ ...AmountPage.js => NewRequestAmountPage.tsx} | 97 ++++----- 9 files changed, 294 insertions(+), 344 deletions(-) delete mode 100644 src/pages/iou/NewDistanceRequestPage.js create mode 100644 src/pages/iou/NewDistanceRequestPage.tsx delete mode 100644 src/pages/iou/SplitBillDetailsPage.js create mode 100644 src/pages/iou/SplitBillDetailsPage.tsx rename src/pages/iou/steps/{NewRequestAmountPage.js => NewRequestAmountPage.tsx} (66%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 9693c907a5fe..5906328b889c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -473,6 +473,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup; [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.TRANSACTION_DRAFT]: OnyxTypes.Transaction; + [ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolation[]; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index dd5a7720f00d..cf0582070747 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -180,7 +180,11 @@ type RoomInviteNavigatorParamList = { type MoneyRequestNavigatorParamList = { [SCREENS.MONEY_REQUEST.ROOT]: undefined; - [SCREENS.MONEY_REQUEST.AMOUNT]: undefined; + [SCREENS.MONEY_REQUEST.AMOUNT]: { + iouType: string; + reportID: string; + currency: string; + }; [SCREENS.MONEY_REQUEST.PARTICIPANTS]: { iouType: string; reportID: string; @@ -282,6 +286,7 @@ type EnablePaymentsNavigatorParamList = { type SplitDetailsNavigatorParamList = { [SCREENS.SPLIT_DETAILS.ROOT]: { + reportID: string; reportActionID: string; }; [SCREENS.SPLIT_DETAILS.EDIT_REQUEST]: undefined; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index d258b5419103..6f9fb9945f65 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3626,7 +3626,6 @@ function setUpDistanceTransaction() { * @param {Object} iou * @param {String} iouType * @param {Object} report - * @param {String} report.reportID * @param {String} path */ function navigateToNextPage(iou, iouType, report, path = '') { diff --git a/src/pages/home/report/withReportAndReportActionOrNotFound.tsx b/src/pages/home/report/withReportAndReportActionOrNotFound.tsx index ed686852158b..b3d1f3e5ae09 100644 --- a/src/pages/home/report/withReportAndReportActionOrNotFound.tsx +++ b/src/pages/home/report/withReportAndReportActionOrNotFound.tsx @@ -1,5 +1,6 @@ /* eslint-disable rulesdir/no-negated-variables */ import type {RouteProp} from '@react-navigation/native'; +import {StackScreenProps} from '@react-navigation/stack'; import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {useCallback, useEffect} from 'react'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; @@ -37,12 +38,21 @@ type OnyxProps = { isLoadingReportData: OnyxEntry; }; -type ComponentProps = OnyxProps & - WindowDimensionsProps & { - route: RouteProp<{params: {reportID: string; reportActionID: string}}>; - }; +type WithReportAndReportActionOrNotFound = OnyxProps & + WindowDimensionsProps & + StackScreenProps< + Record< + string, + { + reportID: string; + reportActionID: string; + } + > + >; -export default function (WrappedComponent: ComponentType>): ComponentType> { +export default function ( + WrappedComponent: ComponentType>, +): ComponentType> { function WithReportOrNotFound(props: TProps, ref: ForwardedRef) { const getReportAction = useCallback(() => { let reportAction: OnyxTypes.ReportAction | Record | undefined = props.reportActions?.[`${props.route.params.reportActionID}`]; @@ -118,3 +128,5 @@ export default function (WrappedComponent: withWindowDimensions, )(React.forwardRef(WithReportOrNotFound)); } + +export type {WithReportAndReportActionOrNotFound}; diff --git a/src/pages/iou/NewDistanceRequestPage.js b/src/pages/iou/NewDistanceRequestPage.js deleted file mode 100644 index 750ac5d0141e..000000000000 --- a/src/pages/iou/NewDistanceRequestPage.js +++ /dev/null @@ -1,85 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import DistanceRequest from '@components/DistanceRequest'; -import Navigation from '@libs/Navigation/Navigation'; -import reportPropTypes from '@pages/reportPropTypes'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import {iouPropTypes} from './propTypes'; - -const propTypes = { - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, - - /** The report on which the request is initiated on */ - report: reportPropTypes, - - /** Passed from the navigator */ - route: PropTypes.shape({ - /** Parameters the route gets */ - params: PropTypes.shape({ - /** Type of IOU */ - iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)), - /** Id of the report on which the distance request is being created */ - reportID: PropTypes.string, - }), - }), -}; - -const defaultProps = { - iou: {}, - report: {}, - route: { - params: { - iouType: '', - reportID: '', - }, - }, -}; - -// This component is responsible for getting the transactionID from the IOU key, or creating the transaction if it doesn't exist yet, and then passing the transactionID. -// You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that DistanceRequest can subscribe to the transaction. -function NewDistanceRequestPage({iou, report, route}) { - const iouType = lodashGet(route, 'params.iouType', 'request'); - const isEditingNewRequest = Navigation.getActiveRoute().includes('address'); - - useEffect(() => { - if (iou.transactionID) { - return; - } - IOU.setUpDistanceTransaction(); - }, [iou.transactionID]); - - const onSubmit = useCallback(() => { - if (isEditingNewRequest) { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID)); - return; - } - IOU.navigateToNextPage(iou, iouType, report); - }, [iou, iouType, isEditingNewRequest, report]); - - return ( - - ); -} - -NewDistanceRequestPage.displayName = 'NewDistanceRequestPage'; -NewDistanceRequestPage.propTypes = propTypes; -NewDistanceRequestPage.defaultProps = defaultProps; -export default withOnyx({ - iou: {key: ONYXKEYS.IOU}, - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID')}`, - }, -})(NewDistanceRequestPage); diff --git a/src/pages/iou/NewDistanceRequestPage.tsx b/src/pages/iou/NewDistanceRequestPage.tsx new file mode 100644 index 000000000000..f40eb5d4fbe1 --- /dev/null +++ b/src/pages/iou/NewDistanceRequestPage.tsx @@ -0,0 +1,61 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback, useEffect} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import DistanceRequest from '@components/DistanceRequest'; +import Navigation from '@libs/Navigation/Navigation'; +import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; +import * as IOU from '@userActions/IOU'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {IOU as IOUType, Report} from '@src/types/onyx'; + +type NewDistanceRequestPageOnyxProps = { + iou: OnyxEntry; + report: OnyxEntry; +}; + +type NewDistanceRequestPageProps = NewDistanceRequestPageOnyxProps & StackScreenProps; + +// This component is responsible for getting the transactionID from the IOU key, or creating the transaction if it doesn't exist yet, and then passing the transactionID. +// You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that DistanceRequest can subscribe to the transaction. +function NewDistanceRequestPage({iou, report, route}: NewDistanceRequestPageProps) { + const iouType = route.params.iouType ?? 'request'; + const isEditingNewRequest = Navigation.getActiveRoute().includes('address'); + + useEffect(() => { + if (iou?.transactionID) { + return; + } + IOU.setUpDistanceTransaction(); + }, [iou?.transactionID]); + + const onSubmit = useCallback(() => { + if (isEditingNewRequest) { + Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report?.reportID)); + return; + } + IOU.navigateToNextPage(iou ?? {}, iouType, report ?? {}); + }, [iou, iouType, isEditingNewRequest, report]); + + return ( + + ); +} + +NewDistanceRequestPage.displayName = 'NewDistanceRequestPage'; + +export default withOnyx({ + iou: {key: ONYXKEYS.IOU}, + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, + }, +})(NewDistanceRequestPage); diff --git a/src/pages/iou/SplitBillDetailsPage.js b/src/pages/iou/SplitBillDetailsPage.js deleted file mode 100644 index 94c2f1c31242..000000000000 --- a/src/pages/iou/SplitBillDetailsPage.js +++ /dev/null @@ -1,194 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import MoneyRequestConfirmationList from '@components/MoneyRequestConfirmationList'; -import MoneyRequestHeaderStatusBar from '@components/MoneyRequestHeaderStatusBar'; -import ScreenWrapper from '@components/ScreenWrapper'; -import transactionPropTypes from '@components/transactionPropTypes'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; -import withReportAndReportActionOrNotFound from '@pages/home/report/withReportAndReportActionOrNotFound'; -import personalDetailsPropType from '@pages/personalDetailsPropType'; -import reportPropTypes from '@pages/reportPropTypes'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; - -const propTypes = { - /* Onyx Props */ - - /** The personal details of the person who is logged in */ - personalDetails: personalDetailsPropType, - - /** The active report */ - report: reportPropTypes.isRequired, - - /** Array of report actions for this report */ - reportActions: PropTypes.shape(reportActionPropTypes), - - /** The current transaction */ - transaction: transactionPropTypes.isRequired, - - /** The draft transaction that holds data to be persisited on the current transaction */ - draftTransaction: transactionPropTypes, - - /** Route params */ - route: PropTypes.shape({ - params: PropTypes.shape({ - /** Report ID passed via route r/:reportID/split/details */ - reportID: PropTypes.string, - - /** ReportActionID passed via route r/split/:reportActionID */ - reportActionID: PropTypes.string, - }), - }).isRequired, - - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user accountID */ - accountID: PropTypes.number, - - /** Currently logged in user email */ - email: PropTypes.string, - }).isRequired, -}; - -const defaultProps = { - personalDetails: {}, - reportActions: {}, - draftTransaction: undefined, -}; - -function SplitBillDetailsPage(props) { - const styles = useThemeStyles(); - const {reportID} = props.report; - const {translate} = useLocalize(); - const reportAction = props.reportActions[`${props.route.params.reportActionID.toString()}`]; - const participantAccountIDs = reportAction.originalMessage.participantAccountIDs; - - // In case this is workspace split bill, we manually add the workspace as the second participant of the split bill - // because we don't save any accountID in the report action's originalMessage other than the payee's accountID - let participants; - if (ReportUtils.isPolicyExpenseChat(props.report)) { - participants = [ - OptionsListUtils.getParticipantsOption({accountID: participantAccountIDs[0], selected: true}, props.personalDetails), - OptionsListUtils.getPolicyExpenseReportOption({...props.report, selected: true}), - ]; - } else { - participants = _.map(participantAccountIDs, (accountID) => OptionsListUtils.getParticipantsOption({accountID, selected: true}, props.personalDetails)); - } - const payeePersonalDetails = props.personalDetails[reportAction.actorAccountID]; - const participantsExcludingPayee = _.filter(participants, (participant) => participant.accountID !== reportAction.actorAccountID); - - const isScanning = TransactionUtils.hasReceipt(props.transaction) && TransactionUtils.isReceiptBeingScanned(props.transaction); - const hasSmartScanFailed = TransactionUtils.hasReceipt(props.transaction) && props.transaction.receipt.state === CONST.IOU.RECEIPT_STATE.SCANFAILED; - const isEditingSplitBill = props.session.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(props.transaction); - - const { - amount: splitAmount, - currency: splitCurrency, - comment: splitComment, - merchant: splitMerchant, - created: splitCreated, - category: splitCategory, - tag: splitTag, - } = isEditingSplitBill && props.draftTransaction ? ReportUtils.getTransactionDetails(props.draftTransaction) : ReportUtils.getTransactionDetails(props.transaction); - - const onConfirm = useCallback( - () => IOU.completeSplitBill(reportID, reportAction, props.draftTransaction, props.session.accountID, props.session.email), - [reportID, reportAction, props.draftTransaction, props.session.accountID, props.session.email], - ); - - return ( - - - - - {isScanning && ( - - )} - {Boolean(participants.length) && ( - - )} - - - - ); -} - -SplitBillDetailsPage.propTypes = propTypes; -SplitBillDetailsPage.defaultProps = defaultProps; -SplitBillDetailsPage.displayName = 'SplitBillDetailsPage'; - -export default compose( - withReportAndReportActionOrNotFound, - withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, - }, - reportActions: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${route.params.reportID}`, - canEvict: false, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - session: { - key: ONYXKEYS.SESSION, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - transaction: { - key: ({route, reportActions}) => { - const reportAction = reportActions[`${route.params.reportActionID.toString()}`]; - return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(reportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, - }, - draftTransaction: { - key: ({route, reportActions}) => { - const reportAction = reportActions[`${route.params.reportActionID.toString()}`]; - return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${lodashGet(reportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, - }, - }), -)(SplitBillDetailsPage); diff --git a/src/pages/iou/SplitBillDetailsPage.tsx b/src/pages/iou/SplitBillDetailsPage.tsx new file mode 100644 index 000000000000..742f6360e45b --- /dev/null +++ b/src/pages/iou/SplitBillDetailsPage.tsx @@ -0,0 +1,170 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import MoneyRequestConfirmationList from '@components/MoneyRequestConfirmationList'; +import MoneyRequestHeaderStatusBar from '@components/MoneyRequestHeaderStatusBar'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {SplitDetailsNavigatorParamList} from '@libs/Navigation/types'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; +import withReportAndReportActionOrNotFound from '@pages/home/report/withReportAndReportActionOrNotFound'; +import type {WithReportAndReportActionOrNotFound} from '@pages/home/report/withReportAndReportActionOrNotFound'; +import * as IOU from '@userActions/IOU'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import type {PersonalDetailsList, Report, Session, Transaction} from '@src/types/onyx'; +import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; +import type {ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type SplitBillDetailsPageOnyxProps = { + /** The personal details of the person who is logged in */ + personalDetails: OnyxEntry; + + /** The active report */ + report: OnyxEntry; + + /** Array of report actions for this report */ + reportActions: OnyxEntry; + + /** The current transaction */ + transaction: OnyxEntry; + + /** The draft transaction that holds data to be persisited on the current transaction */ + draftTransaction: OnyxEntry; + + /** Session info for the currently logged in user. */ + session: OnyxEntry; +}; + +type SplitBillDetailsPageProps = WithReportAndReportActionOrNotFound & SplitBillDetailsPageOnyxProps & StackScreenProps; + +function SplitBillDetailsPage({personalDetails, report, route, reportActions, transaction, draftTransaction, session}: SplitBillDetailsPageProps) { + const styles = useThemeStyles(); + const {reportID} = report ?? {}; + const {translate} = useLocalize(); + const reportAction = reportActions?.[route.params.reportActionID] as (ReportActionBase & OriginalMessageIOU) | undefined; + const participantAccountIDs = reportAction?.originalMessage.participantAccountIDs ?? []; + + // In case this is workspace split bill, we manually add the workspace as the second participant of the split bill + // because we don't save any accountID in the report action's originalMessage other than the payee's accountID + let participants; + if (ReportUtils.isPolicyExpenseChat(report)) { + participants = [ + // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. + OptionsListUtils.getParticipantsOption({accountID: participantAccountIDs[0], selected: true}, personalDetails), + OptionsListUtils.getPolicyExpenseReportOption({...report, selected: true}), + ]; + } else { + // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. + participants = participantAccountIDs.map((accountID) => OptionsListUtils.getParticipantsOption({accountID, selected: true}, personalDetails)); + } + const payeePersonalDetails = personalDetails?.[reportAction?.actorAccountID ?? 0]; + // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. + const participantsExcludingPayee = participants.filter((participant) => participant.accountID !== reportAction?.actorAccountID); + + const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); + const hasSmartScanFailed = TransactionUtils.hasReceipt(transaction) && transaction?.receipt?.state === CONST.IOU.RECEIPT_STATE.SCANFAILED; + const isEditingSplitBill = session?.accountID === reportAction?.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction ?? ({} as Transaction)); + + const { + amount: splitAmount, + currency: splitCurrency, + comment: splitComment, + merchant: splitMerchant, + created: splitCreated, + category: splitCategory, + tag: splitTag, + } = ReportUtils.getTransactionDetails(isEditingSplitBill && draftTransaction ? draftTransaction : transaction) ?? {}; + + const onConfirm = useCallback( + () => IOU.completeSplitBill(Number(reportID), reportAction ?? {}, draftTransaction ?? {}, session?.accountID ?? -1, session?.email ?? ''), + [reportID, reportAction, draftTransaction, session?.accountID, session?.email], + ); + + return ( + + + + + {isScanning && ( + + )} + {Boolean(participants.length) && ( + + )} + + + + ); +} + +SplitBillDetailsPage.displayName = 'SplitBillDetailsPage'; + +const WrappedComponent = withOnyx>({ + transaction: { + key: ({route, reportActions}) => { + const reportAction = reportActions?.[route.params.reportActionID] as (ReportActionBase & OriginalMessageIOU) | undefined; + return `${ONYXKEYS.COLLECTION.TRANSACTION}${reportAction?.originalMessage.IOUTransactionID ?? 0}`; + }, + }, + draftTransaction: { + key: ({route, reportActions}) => { + const reportAction = reportActions?.[route.params.reportActionID] as (ReportActionBase & OriginalMessageIOU) | undefined; + return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${reportAction?.originalMessage.IOUTransactionID ?? 0}`; + }, + }, +})(withReportAndReportActionOrNotFound(SplitBillDetailsPage)); + +export default withOnyx, Omit>({ + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, + }, + reportActions: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${route.params.reportID}`, + canEvict: false, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + session: { + key: ONYXKEYS.SESSION, + }, +})(WrappedComponent); diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.tsx similarity index 66% rename from src/pages/iou/steps/NewRequestAmountPage.js rename to src/pages/iou/steps/NewRequestAmountPage.tsx index 1df74569e4c3..4d530c050c22 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.tsx @@ -1,10 +1,11 @@ import {useFocusEffect} from '@react-navigation/native'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; +import type {TextInput as RNTextInput} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -14,66 +15,45 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as IOUUtils from '@libs/IOUUtils'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; -import reportPropTypes from '@pages/reportPropTypes'; +import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {IOU as IOUType, Report} from '@src/types/onyx'; import MoneyRequestAmountForm from './MoneyRequestAmountForm'; -const propTypes = { - /** React Navigation route */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string, - - /** The report ID of the IOU */ - reportID: PropTypes.string, - - /** Selected currency from IOUCurrencySelection */ - currency: PropTypes.string, - }), - }).isRequired, - - /** The report on which the request is initiated on */ - report: reportPropTypes, - - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, - - /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ - selectedTab: PropTypes.oneOf(_.values(CONST.TAB_REQUEST)), +type NewRequestAmountPageOnyxProps = { + iou: OnyxEntry; + report: OnyxEntry; + selectedTab: OnyxEntry; }; -const defaultProps = { - report: {}, - iou: iouDefaultProps, - selectedTab: CONST.TAB_REQUEST.MANUAL, -}; +type NewRequestAmountPageProps = NewRequestAmountPageOnyxProps & StackScreenProps; -function NewRequestAmountPage({route, iou, report, selectedTab}) { +function NewRequestAmountPage({route, iou, report, selectedTab}: NewRequestAmountPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const prevMoneyRequestID = useRef(iou.id); - const textInput = useRef(null); + const prevMoneyRequestID = useRef(iou?.id); + const textInput = useRef(null); - const iouType = lodashGet(route, 'params.iouType', ''); - const reportID = lodashGet(route, 'params.reportID', ''); + const iouType = route.params.iouType ?? ''; + const reportID = route.params.reportID ?? ''; const isEditing = Navigation.getActiveRoute().includes('amount'); - const currentCurrency = lodashGet(route, 'params.currency', ''); - const isDistanceRequestTab = MoneyRequestUtils.isDistanceRequest(iouType, selectedTab); + const currentCurrency = route.params.currency ?? ''; + const isDistanceRequestTab = MoneyRequestUtils.isDistanceRequest(iouType as ValueOf, selectedTab as ValueOf); - const currency = CurrencyUtils.isValidCurrencyCode(currentCurrency) ? currentCurrency : iou.currency; + const currency = CurrencyUtils.isValidCurrencyCode(currentCurrency) ? currentCurrency : iou?.currency ?? ''; - const focusTimeoutRef = useRef(null); + const focusTimeoutRef = useRef(null); useFocusEffect( useCallback(() => { - focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION); + focusTimeoutRef.current = setTimeout(() => { + textInput.current?.focus(); + }, CONST.ANIMATED_TRANSITION); return () => { if (!focusTimeoutRef.current) { return; @@ -88,29 +68,29 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { useEffect(() => { if (isEditing) { // ID in Onyx could change by initiating a new request in a separate browser tab or completing a request - if (prevMoneyRequestID.current !== iou.id) { + if (prevMoneyRequestID.current !== iou?.id) { // The ID is cleared on completing a request. In that case, we will do nothing. - if (!iou.id) { + if (!iou?.id) { return; } Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID), true); return; } const moneyRequestID = `${iouType}${reportID}`; - const shouldReset = iou.id !== moneyRequestID; + const shouldReset = iou?.id !== moneyRequestID; if (shouldReset) { IOU.resetMoneyRequestInfo(moneyRequestID); } - if (!isDistanceRequestTab && (_.isEmpty(iou.participants) || iou.amount === 0 || shouldReset)) { + if (!isDistanceRequestTab && (!iou?.participants?.length || iou?.amount === 0 || shouldReset)) { Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID), true); } } return () => { - prevMoneyRequestID.current = iou.id; + prevMoneyRequestID.current = iou?.id; }; - }, [iou.participants, iou.amount, iou.id, isEditing, iouType, reportID, isDistanceRequestTab]); + }, [isEditing, iouType, reportID, isDistanceRequestTab, iou]); const navigateBack = () => { Navigation.goBack(isEditing ? ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID) : ROUTES.HOME); @@ -128,7 +108,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { Navigation.navigate(ROUTES.MONEY_REQUEST_CURRENCY.getRoute(iouType, reportID, currency, activeRoute)); }; - const navigateToNextPage = ({amount}) => { + const navigateToNextPage = ({amount}: {amount: string}) => { const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); IOU.setMoneyRequestAmount(amountInSmallestCurrencyUnits); IOU.setMoneyRequestCurrency(currency); @@ -138,15 +118,18 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { return; } - IOU.navigateToNextPage(iou, iouType, report); + IOU.navigateToNextPage(iou ?? {}, iouType, report ?? {}); }; const content = ( (textInput.current = e)} + amount={iou?.amount} + ref={(e) => { + textInput.current = e; + }} onCurrencyButtonPress={navigateToCurrencySelectionPage} onSubmitButtonPress={navigateToNextPage} selectedTab={selectedTab} @@ -180,14 +163,12 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { ); } -NewRequestAmountPage.propTypes = propTypes; -NewRequestAmountPage.defaultProps = defaultProps; NewRequestAmountPage.displayName = 'NewRequestAmountPage'; -export default withOnyx({ +export default withOnyx({ iou: {key: ONYXKEYS.IOU}, report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`, + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? ''}`, }, selectedTab: { key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, From fb08033cd73698250232de21554945f06a8f569e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 31 Jan 2024 06:18:28 +0100 Subject: [PATCH 063/699] remove duplicate identifier --- src/types/onyx/Policy.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 23d7494f08cf..eca7e9d1ee06 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -76,9 +76,6 @@ type Policy = { /** The custom units data for this policy */ customUnits?: Record; - /** Is tax tracking enabled */ - isTaxTrackingEnabled?: boolean; - /** Whether chat rooms can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ areChatRoomsEnabled: boolean; From 70c02fd72cdb2cb34520bd5787c8bd40eef7796d Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 31 Jan 2024 06:37:34 +0100 Subject: [PATCH 064/699] fix lint and tsc --- src/libs/OptionsListUtils.ts | 10 ++-------- src/libs/TransactionUtils.ts | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index d550ae4305b0..51d1bc9699ad 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -4,7 +4,6 @@ import Str from 'expensify-common/lib/str'; import lodashGet from 'lodash/get'; import lodashOrderBy from 'lodash/orderBy'; import lodashSet from 'lodash/set'; -import lodashSome from 'lodash/some'; import lodashSortBy from 'lodash/sortBy'; import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; @@ -1148,13 +1147,9 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt /** * Verifies that there is at least one enabled option * - * @param {Object[]} options - an initial strings array - * @property {boolean} [isDisabled] - Indicates if the tax rate is disabled. - * @returns {Boolean} + * @param options - an initial strings array + * @returns boolean */ -function hasEnabledOptionsForTaxRate(options: TaxRates) { - return lodashSome(options, (option) => !option.isDisabled); -} /** * Transforms tax rates to a new object format - to add codes and new name with concatenated name and value. @@ -2001,7 +1996,6 @@ export { getLastMessageTextForReport, getEnabledCategoriesCount, hasEnabledOptions, - hasEnabledOptionsForTaxRate, sortCategories, getCategoryOptionTree, formatMemberForList, diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 711ff3fbbcd2..13a77183d1c5 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -5,7 +5,7 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {RecentWaypoint, Report, ReportAction, Transaction, TransactionViolation} from '@src/types/onyx'; -import type {TaxRate, TaxRates, PolicyTaxRates} from '@src/types/onyx/PolicyTaxRates'; +import type {PolicyTaxRates, TaxRate, TaxRates} from '@src/types/onyx/PolicyTaxRates'; import type {Comment, Receipt, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isCorporateCard, isExpensifyCard} from './CardUtils'; From 841e4bff4cdc3192dcfd77fbf7319dd75b18ca13 Mon Sep 17 00:00:00 2001 From: Dylan Date: Wed, 31 Jan 2024 16:09:06 +0700 Subject: [PATCH 065/699] remove MoneyRequestConfirmPage --- src/ROUTES.ts | 4 - src/SCREENS.ts | 1 - src/components/DistanceRequest/index.js | 2 +- .../AppNavigator/ModalStackNavigators.tsx | 1 - src/libs/Navigation/linkingConfig.ts | 1 - src/libs/Navigation/types.ts | 3 +- src/libs/actions/IOU.js | 4 +- src/pages/iou/MoneyRequestCategoryPage.js | 4 +- src/pages/iou/MoneyRequestDatePage.js | 2 +- src/pages/iou/MoneyRequestDescriptionPage.js | 2 +- src/pages/iou/MoneyRequestMerchantPage.js | 2 +- src/pages/iou/MoneyRequestTagPage.js | 2 +- src/pages/iou/NewDistanceRequestPage.js | 2 +- .../step/IOURequestStepConfirmation.js | 14 +- .../iou/steps/MoneyRequestConfirmPage.js | 473 ------------------ .../MoneyRequestParticipantsPage.js | 2 +- src/pages/iou/steps/NewRequestAmountPage.js | 4 +- 17 files changed, 17 insertions(+), 506 deletions(-) delete mode 100644 src/pages/iou/steps/MoneyRequestConfirmPage.js diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9c4375b84ab6..deabdc0ac853 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -267,10 +267,6 @@ const ROUTES = { route: ':iouType/new/participants/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/participants/${reportID}` as const, }, - MONEY_REQUEST_CONFIRMATION: { - route: ':iouType/new/confirmation/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/confirmation/${reportID}` as const, - }, MONEY_REQUEST_DATE: { route: ':iouType/new/date/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/date/${reportID}` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 2bf40caede57..5a8922ee01c3 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -141,7 +141,6 @@ const SCREENS = { ROOT: 'Money_Request', AMOUNT: 'Money_Request_Amount', PARTICIPANTS: 'Money_Request_Participants', - CONFIRMATION: 'Money_Request_Confirmation', CURRENCY: 'Money_Request_Currency', DATE: 'Money_Request_Date', DESCRIPTION: 'Money_Request_Description', diff --git a/src/components/DistanceRequest/index.js b/src/components/DistanceRequest/index.js index b63ce337a1d9..34295dda74e7 100644 --- a/src/components/DistanceRequest/index.js +++ b/src/components/DistanceRequest/index.js @@ -163,7 +163,7 @@ function DistanceRequest({transactionID, report, transaction, route, isEditingRe }, [waypoints, previousWaypoints]); const navigateBack = () => { - Navigation.goBack(isEditingNewRequest ? ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID) : ROUTES.HOME); + Navigation.goBack(isEditingNewRequest ? ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, transactionID, reportID) : ROUTES.HOME); }; /** diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index c9325206e5b2..3a843e400409 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -93,7 +93,6 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/MoneyRequestSelectorPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.AMOUNT]: () => require('../../../pages/iou/steps/NewRequestAmountPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.PARTICIPANTS]: () => require('../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default as React.ComponentType, - [SCREENS.MONEY_REQUEST.CONFIRMATION]: () => require('../../../pages/iou/steps/MoneyRequestConfirmPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.CURRENCY]: () => require('../../../pages/iou/IOUCurrencySelection').default as React.ComponentType, [SCREENS.MONEY_REQUEST.DATE]: () => require('../../../pages/iou/MoneyRequestDatePage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.DESCRIPTION]: () => require('../../../pages/iou/MoneyRequestDescriptionPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index 5df2bcf0e57b..d4e04d5402e2 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -428,7 +428,6 @@ const linkingConfig: LinkingOptions = { [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_RATE]: ROUTES.MONEY_REQUEST_STEP_TAX_RATE.route, [SCREENS.MONEY_REQUEST.PARTICIPANTS]: ROUTES.MONEY_REQUEST_PARTICIPANTS.route, - [SCREENS.MONEY_REQUEST.CONFIRMATION]: ROUTES.MONEY_REQUEST_CONFIRMATION.route, [SCREENS.MONEY_REQUEST.DATE]: ROUTES.MONEY_REQUEST_DATE.route, [SCREENS.MONEY_REQUEST.CURRENCY]: ROUTES.MONEY_REQUEST_CURRENCY.route, [SCREENS.MONEY_REQUEST.DESCRIPTION]: ROUTES.MONEY_REQUEST_DESCRIPTION.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 2371c764f42a..29f7cd4aac30 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -185,8 +185,9 @@ type MoneyRequestNavigatorParamList = { iouType: string; reportID: string; }; - [SCREENS.MONEY_REQUEST.CONFIRMATION]: { + [SCREENS.MONEY_REQUEST.STEP_CONFIRMATION]: { iouType: string; + transactionID: string; reportID: string; }; [SCREENS.MONEY_REQUEST.CURRENCY]: { diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index a9e1b09ed984..34fb20091493 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3684,7 +3684,7 @@ function navigateToNextPage(iou, iouType, report, path = '') { // If we're adding a receipt, that means the user came from the confirmation page and we need to navigate back to it. if (path.slice(1) === ROUTES.MONEY_REQUEST_RECEIPT.getRoute(iouType, report.reportID)) { - Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, '1', report.reportID)); return; } @@ -3705,7 +3705,7 @@ function navigateToNextPage(iou, iouType, report, path = '') { resetMoneyRequestCategory(); resetMoneyRequestTag(); } - Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, '1', report.reportID)); return; } Navigation.navigate(ROUTES.MONEY_REQUEST_PARTICIPANTS.getRoute(iouType)); diff --git a/src/pages/iou/MoneyRequestCategoryPage.js b/src/pages/iou/MoneyRequestCategoryPage.js index ceb2152d2b49..b3ca0a5d7ae0 100644 --- a/src/pages/iou/MoneyRequestCategoryPage.js +++ b/src/pages/iou/MoneyRequestCategoryPage.js @@ -50,7 +50,7 @@ function MoneyRequestCategoryPage({route, report, iou}) { const iouType = lodashGet(route, 'params.iouType', ''); const navigateBack = () => { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, '1', reportID)); }; const updateCategory = (category) => { @@ -60,7 +60,7 @@ function MoneyRequestCategoryPage({route, report, iou}) { IOU.setMoneyRequestCategory(category.searchText); } - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, '1', reportID)); }; return ( diff --git a/src/pages/iou/MoneyRequestDatePage.js b/src/pages/iou/MoneyRequestDatePage.js index f6159abd73f6..c99c07a80624 100644 --- a/src/pages/iou/MoneyRequestDatePage.js +++ b/src/pages/iou/MoneyRequestDatePage.js @@ -69,7 +69,7 @@ function MoneyRequestDatePage({iou, route, selectedTab}) { }, [iou.id, iou.participants, iou.amount, iou.receiptPath, iouType, reportID, isDistanceRequest]); function navigateBack() { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, '1', reportID)); } /** diff --git a/src/pages/iou/MoneyRequestDescriptionPage.js b/src/pages/iou/MoneyRequestDescriptionPage.js index fe3100b8c3bd..a2209818ee1a 100644 --- a/src/pages/iou/MoneyRequestDescriptionPage.js +++ b/src/pages/iou/MoneyRequestDescriptionPage.js @@ -92,7 +92,7 @@ function MoneyRequestDescriptionPage({iou, route, selectedTab}) { }, [iou.id, iou.participants, iou.amount, iou.receiptPath, iouType, reportID, isDistanceRequest]); function navigateBack() { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, '1', reportID)); } /** diff --git a/src/pages/iou/MoneyRequestMerchantPage.js b/src/pages/iou/MoneyRequestMerchantPage.js index ce96a09446b9..894bd317464c 100644 --- a/src/pages/iou/MoneyRequestMerchantPage.js +++ b/src/pages/iou/MoneyRequestMerchantPage.js @@ -68,7 +68,7 @@ function MoneyRequestMerchantPage({iou, route}) { }, [iou.id, iou.participants, iou.amount, iou.receiptPath, iouType, reportID]); function navigateBack() { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); + Navigation.goBack(ROUTES.MONEY_REQUEST__STEP_CONFIRMATION.getRoute(iouType, '1', reportID)); } const validate = useCallback((value) => { diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 60e40d665580..7fd08f20d652 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -60,7 +60,7 @@ function MoneyRequestTagPage({route, report, policyTags, iou}) { const policyTagListName = PolicyUtils.getTagListName(policyTags) || translate('common.tag'); const navigateBack = () => { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, '1', report.reportID)); }; const updateTag = (selectedTag) => { diff --git a/src/pages/iou/NewDistanceRequestPage.js b/src/pages/iou/NewDistanceRequestPage.js index 750ac5d0141e..87f92b8c1907 100644 --- a/src/pages/iou/NewDistanceRequestPage.js +++ b/src/pages/iou/NewDistanceRequestPage.js @@ -57,7 +57,7 @@ function NewDistanceRequestPage({iou, report, route}) { const onSubmit = useCallback(() => { if (isEditingNewRequest) { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, lodashGet(iou, 'transactionID', '1'), report.reportID)); return; } IOU.navigateToNextPage(iou, iouType, report); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 6028a735d132..cb9edfb5868b 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -23,7 +23,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import personalDetailsPropType from '@pages/personalDetailsPropType'; +import {usePersonalDetails} from '@components/OnyxProvider'; import reportPropTypes from '@pages/reportPropTypes'; import {policyPropTypes} from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; @@ -43,9 +43,6 @@ const propTypes = { /** The personal details of the current user */ ...withCurrentUserPersonalDetailsPropTypes, - /** Personal details of all users */ - personalDetails: personalDetailsPropType, - /** The policy of the report */ ...policyPropTypes, @@ -62,7 +59,6 @@ const propTypes = { transaction: transactionPropTypes, }; const defaultProps = { - personalDetails: {}, policy: {}, policyCategories: {}, policyTags: {}, @@ -72,7 +68,6 @@ const defaultProps = { }; function IOURequestStepConfirmation({ currentUserPersonalDetails, - personalDetails, policy, policyTags, policyCategories, @@ -86,6 +81,7 @@ function IOURequestStepConfirmation({ const {translate} = useLocalize(); const {windowWidth} = useWindowDimensions(); const {isOffline} = useNetwork(); + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const [receiptFile, setReceiptFile] = useState(); const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); @@ -385,12 +381,6 @@ export default compose( withCurrentUserPersonalDetails, withWritableReportOrNotFound, withFullTransactionOrNotFound, - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ policy: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js deleted file mode 100644 index 1738ac78df47..000000000000 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ /dev/null @@ -1,473 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import categoryPropTypes from '@components/categoryPropTypes'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import * as Expensicons from '@components/Icon/Expensicons'; -import MoneyRequestConfirmationList from '@components/MoneyRequestConfirmationList'; -import {usePersonalDetails} from '@components/OnyxProvider'; -import ScreenWrapper from '@components/ScreenWrapper'; -import tagPropTypes from '@components/tagPropTypes'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; -import withLocalize from '@components/withLocalize'; -import useInitialValue from '@hooks/useInitialValue'; -import useNetwork from '@hooks/useNetwork'; -import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; -import compose from '@libs/compose'; -import * as FileUtils from '@libs/fileDownload/FileUtils'; -import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; -import reportPropTypes from '@pages/reportPropTypes'; -import {policyDefaultProps, policyPropTypes} from '@pages/workspace/withPolicy'; -import * as IOU from '@userActions/IOU'; -import * as Policy from '@userActions/Policy'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; - -const propTypes = { - /** React Navigation route */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string, - - /** The report ID of the IOU */ - reportID: PropTypes.string, - }), - }).isRequired, - - report: reportPropTypes, - - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, - - /** The policy of the current request */ - policy: policyPropTypes, - - policyTags: tagPropTypes, - - policyCategories: PropTypes.objectOf(categoryPropTypes), - - ...withCurrentUserPersonalDetailsPropTypes, -}; - -const defaultProps = { - report: {}, - policyCategories: {}, - policyTags: {}, - iou: iouDefaultProps, - policy: policyDefaultProps, - ...withCurrentUserPersonalDetailsDefaultProps, -}; - -function MoneyRequestConfirmPage(props) { - const styles = useThemeStyles(); - const {isOffline} = useNetwork(); - const {windowWidth} = useWindowDimensions(); - const prevMoneyRequestId = useRef(props.iou.id); - const iouType = useInitialValue(() => lodashGet(props.route, 'params.iouType', '')); - const reportID = useInitialValue(() => lodashGet(props.route, 'params.reportID', '')); - const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType, props.selectedTab); - const isScanRequest = MoneyRequestUtils.isScanRequest(props.selectedTab); - const [receiptFile, setReceiptFile] = useState(); - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; - - const participants = useMemo( - () => - _.map(props.iou.participants, (participant) => { - const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); - return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails); - }), - [props.iou.participants, personalDetails], - ); - const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(props.report)), [props.report]); - const isManualRequestDM = props.selectedTab === CONST.TAB_REQUEST.MANUAL && iouType === CONST.IOU.TYPE.REQUEST; - - useEffect(() => { - const policyExpenseChat = _.find(participants, (participant) => participant.isPolicyExpenseChat); - if (policyExpenseChat) { - Policy.openDraftWorkspaceRequest(policyExpenseChat.policyID); - } - }, [isOffline, participants, props.iou.billable, props.policy]); - - const defaultBillable = lodashGet(props.policy, 'defaultBillable', false); - useEffect(() => { - IOU.setMoneyRequestBillable(defaultBillable); - }, [defaultBillable, isOffline]); - - useEffect(() => { - if (!props.iou.receiptPath || !props.iou.receiptFilename) { - return; - } - const onSuccess = (file) => { - const receipt = file; - receipt.state = file && isManualRequestDM ? CONST.IOU.RECEIPT_STATE.OPEN : CONST.IOU.RECEIPT_STATE.SCANREADY; - setReceiptFile(receipt); - }; - const onFailure = () => { - Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID)); - }; - FileUtils.readFileAsync(props.iou.receiptPath, props.iou.receiptFilename, onSuccess, onFailure); - }, [props.iou.receiptPath, props.iou.receiptFilename, isManualRequestDM, iouType, reportID]); - - useEffect(() => { - // ID in Onyx could change by initiating a new request in a separate browser tab or completing a request - if (!isDistanceRequest && prevMoneyRequestId.current !== props.iou.id) { - // The ID is cleared on completing a request. In that case, we will do nothing. - if (props.iou.id) { - Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID), true); - } - return; - } - - // Reset the money request Onyx if the ID in Onyx does not match the ID from params - const moneyRequestId = `${iouType}${reportID}`; - const shouldReset = !isDistanceRequest && props.iou.id !== moneyRequestId && !_.isEmpty(reportID); - if (shouldReset) { - IOU.resetMoneyRequestInfo(moneyRequestId); - } - - if (_.isEmpty(props.iou.participants) || (props.iou.amount === 0 && !props.iou.receiptPath && !isDistanceRequest) || shouldReset || ReportUtils.isArchivedRoom(props.report)) { - Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID), true); - } - - return () => { - prevMoneyRequestId.current = props.iou.id; - }; - }, [props.iou.participants, props.iou.amount, props.iou.id, props.iou.receiptPath, isDistanceRequest, props.report, iouType, reportID]); - - const navigateBack = () => { - let fallback; - if (reportID) { - fallback = ROUTES.MONEY_REQUEST.getRoute(iouType, reportID); - } else { - fallback = ROUTES.MONEY_REQUEST_PARTICIPANTS.getRoute(iouType); - } - Navigation.goBack(fallback); - }; - - /** - * @param {Array} selectedParticipants - * @param {String} trimmedComment - * @param {File} [receipt] - */ - const requestMoney = useCallback( - (selectedParticipants, trimmedComment, receipt) => { - IOU.requestMoney( - props.report, - props.iou.amount, - props.iou.currency, - props.iou.created, - props.iou.merchant, - props.currentUserPersonalDetails.login, - props.currentUserPersonalDetails.accountID, - selectedParticipants[0], - trimmedComment, - receipt, - props.iou.category, - props.iou.tag, - props.iou.billable, - props.policy, - props.policyTags, - props.policyCategories, - ); - }, - [ - props.report, - props.iou.amount, - props.iou.currency, - props.iou.created, - props.iou.merchant, - props.currentUserPersonalDetails.login, - props.currentUserPersonalDetails.accountID, - props.iou.category, - props.iou.tag, - props.iou.billable, - props.policy, - props.policyTags, - props.policyCategories, - ], - ); - - /** - * @param {Array} selectedParticipants - * @param {String} trimmedComment - */ - const createDistanceRequest = useCallback( - (selectedParticipants, trimmedComment) => { - IOU.createDistanceRequest( - props.report, - selectedParticipants[0], - trimmedComment, - props.iou.created, - props.iou.transactionID, - props.iou.category, - props.iou.tag, - props.iou.amount, - props.iou.currency, - props.iou.merchant, - props.iou.billable, - props.policy, - props.policyTags, - props.policyCategories, - ); - }, - [ - 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, - props.policy, - props.policyTags, - props.policyCategories, - ], - ); - - const createTransaction = useCallback( - (selectedParticipants) => { - const trimmedComment = props.iou.comment.trim(); - - // If we have a receipt let's start the split bill by creating only the action, the transaction, and the group DM if needed - if (iouType === CONST.IOU.TYPE.SPLIT && props.iou.receiptPath) { - const existingSplitChatReportID = CONST.REGEX.NUMBER.test(reportID) ? reportID : ''; - const onSuccess = (receipt) => { - IOU.startSplitBill( - selectedParticipants, - props.currentUserPersonalDetails.login, - props.currentUserPersonalDetails.accountID, - trimmedComment, - receipt, - existingSplitChatReportID, - ); - }; - FileUtils.readFileAsync(props.iou.receiptPath, props.iou.receiptFilename, onSuccess); - return; - } - - // IOUs created from a group report will have a reportID param in the route. - // Since the user is already viewing the report, we don't need to navigate them to the report - if (iouType === CONST.IOU.TYPE.SPLIT && CONST.REGEX.NUMBER.test(reportID)) { - IOU.splitBill( - selectedParticipants, - props.currentUserPersonalDetails.login, - props.currentUserPersonalDetails.accountID, - props.iou.amount, - trimmedComment, - props.iou.currency, - props.iou.category, - props.iou.tag, - reportID, - props.iou.merchant, - ); - return; - } - - // If the request is created from the global create menu, we also navigate the user to the group report - if (iouType === CONST.IOU.TYPE.SPLIT) { - IOU.splitBillAndOpenReport( - selectedParticipants, - props.currentUserPersonalDetails.login, - props.currentUserPersonalDetails.accountID, - props.iou.amount, - trimmedComment, - props.iou.currency, - props.iou.category, - props.iou.tag, - props.iou.merchant, - ); - return; - } - - if (receiptFile) { - requestMoney(selectedParticipants, trimmedComment, receiptFile); - return; - } - - if (isDistanceRequest) { - createDistanceRequest(selectedParticipants, trimmedComment); - return; - } - - requestMoney(selectedParticipants, trimmedComment); - }, - [ - props.iou.amount, - props.iou.comment, - props.currentUserPersonalDetails.login, - props.currentUserPersonalDetails.accountID, - props.iou.currency, - props.iou.category, - props.iou.tag, - props.iou.receiptPath, - props.iou.receiptFilename, - isDistanceRequest, - requestMoney, - createDistanceRequest, - receiptFile, - iouType, - reportID, - props.iou.merchant, - ], - ); - - /** - * Checks if user has a GOLD wallet then creates a paid IOU report on the fly - * - * @param {String} paymentMethodType - */ - const sendMoney = useCallback( - (paymentMethodType) => { - const currency = props.iou.currency; - const trimmedComment = props.iou.comment.trim(); - const participant = participants[0]; - - if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { - IOU.sendMoneyElsewhere(props.report, props.iou.amount, currency, trimmedComment, props.currentUserPersonalDetails.accountID, participant); - return; - } - - if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY) { - IOU.sendMoneyWithWallet(props.report, props.iou.amount, currency, trimmedComment, props.currentUserPersonalDetails.accountID, participant); - } - }, - [props.iou.amount, props.iou.comment, participants, props.iou.currency, props.currentUserPersonalDetails.accountID, props.report], - ); - - const headerTitle = () => { - if (isDistanceRequest) { - return props.translate('common.distance'); - } - - if (iouType === CONST.IOU.TYPE.SPLIT) { - return props.translate('iou.split'); - } - - if (iouType === CONST.IOU.TYPE.SEND) { - return props.translate('common.send'); - } - - if (isScanRequest) { - return props.translate('tabSelector.scan'); - } - - return props.translate('tabSelector.manual'); - }; - - return ( - - {({safeAreaPaddingBottomStyle}) => ( - - Navigation.navigate(ROUTES.MONEY_REQUEST_RECEIPT.getRoute(iouType, reportID)), - }, - ]} - /> - { - const newParticipants = _.map(props.iou.participants, (participant) => { - if (participant.accountID === option.accountID) { - return {...participant, selected: !participant.selected}; - } - return participant; - }); - IOU.setMoneyRequestParticipants(newParticipants); - }} - receiptPath={props.iou.receiptPath} - receiptFilename={props.iou.receiptFilename} - iouType={iouType} - reportID={reportID} - isPolicyExpenseChat={isPolicyExpenseChat} - // The participants can only be modified when the action is initiated from directly within a group chat and not the floating-action-button. - // This is because when there is a group of people, say they are on a trip, and you have some shared expenses with some of the people, - // but not all of them (maybe someone skipped out on dinner). Then it's nice to be able to select/deselect people from the group chat bill - // split rather than forcing the user to create a new group, just for that expense. The reportID is empty, when the action was initiated from - // the floating-action-button (since it is something that exists outside the context of a report). - canModifyParticipants={!_.isEmpty(reportID)} - policyID={props.report.policyID} - bankAccountRoute={ReportUtils.getBankAccountRoute(props.report)} - iouMerchant={props.iou.merchant} - iouCreated={props.iou.created} - isScanRequest={isScanRequest} - isDistanceRequest={isDistanceRequest} - shouldShowSmartScanFields={_.isEmpty(props.iou.receiptPath)} - /> - - )} - - ); -} - -MoneyRequestConfirmPage.displayName = 'MoneyRequestConfirmPage'; -MoneyRequestConfirmPage.propTypes = propTypes; -MoneyRequestConfirmPage.defaultProps = defaultProps; - -export default compose( - withCurrentUserPersonalDetails, - withLocalize, - withOnyx({ - iou: { - key: ONYXKEYS.IOU, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - report: { - key: ({route, iou}) => { - const reportID = IOU.getIOUReportID(iou, route); - - return `${ONYXKEYS.COLLECTION.REPORT}${reportID}`; - }, - }, - selectedTab: { - key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, - }, - }), - withOnyx({ - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, - }, - policyCategories: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`, - }, - policyTags: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, - }, - }), -)(MoneyRequestConfirmPage); diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index 216154be9cd4..1feb7a2c32fd 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -89,7 +89,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) { IOU.setMoneyRequestId(moneyRequestType); IOU.resetMoneyRequestCategory(); IOU.resetMoneyRequestTag(); - Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(moneyRequestType, reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(moneyRequestType, lodashGet(transaction, 'transactionID', 1), reportID)); }; const navigateBack = useCallback((forceFallback = false) => { diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index 1df74569e4c3..007a63bdb713 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -113,7 +113,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { }, [iou.participants, iou.amount, iou.id, isEditing, iouType, reportID, isDistanceRequestTab]); const navigateBack = () => { - Navigation.goBack(isEditing ? ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID) : ROUTES.HOME); + Navigation.goBack(isEditing ? ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, '1', reportID) : ROUTES.HOME); }; const navigateToCurrencySelectionPage = () => { @@ -134,7 +134,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { IOU.setMoneyRequestCurrency(currency); if (isEditing) { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, '1', reportID)); return; } From 8e4e4d803f40cd0c35b05ee1a51da2c4899154a3 Mon Sep 17 00:00:00 2001 From: Dylan Date: Wed, 31 Jan 2024 16:54:59 +0700 Subject: [PATCH 066/699] fix lint --- src/pages/iou/request/step/IOURequestStepConfirmation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index cb9edfb5868b..f867e57f9a13 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -8,6 +8,7 @@ import categoryPropTypes from '@components/categoryPropTypes'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MoneyRequestConfirmationList from '@components/MoneyTemporaryForRefactorRequestConfirmationList'; +import {usePersonalDetails} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import tagPropTypes from '@components/tagPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -23,7 +24,6 @@ import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import {usePersonalDetails} from '@components/OnyxProvider'; import reportPropTypes from '@pages/reportPropTypes'; import {policyPropTypes} from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; From ee1efe95e93aea8be888b50338534cf3f6f28bff Mon Sep 17 00:00:00 2001 From: Dylan Date: Wed, 31 Jan 2024 17:09:35 +0700 Subject: [PATCH 067/699] update send money flow --- .../SidebarScreen/FloatingActionButtonAndPopover.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index bbcdc5cebef4..7281868f47d9 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -182,7 +182,14 @@ function FloatingActionButtonAndPopover(props) { { icon: Expensicons.Send, text: props.translate('iou.sendMoney'), - onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), + onSelected: () => + interceptAnonymousUser(() => + Navigation.navigate( + // When starting to create a money request from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used + // for all of the routes in the creation flow. + ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.SEND, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, ReportUtils.generateReportID()), + ), + ), }, ...[ { From 8bb0b3406cdb3a294766e2c875935d757c48dc86 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 31 Jan 2024 15:27:39 +0100 Subject: [PATCH 068/699] migrate EditRequestTaxAmountPage to tsc --- ...ntPage.js => EditRequestTaxAmountPage.tsx} | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) rename src/pages/{EditRequestTaxAmountPage.js => EditRequestTaxAmountPage.tsx} (79%) diff --git a/src/pages/EditRequestTaxAmountPage.js b/src/pages/EditRequestTaxAmountPage.tsx similarity index 79% rename from src/pages/EditRequestTaxAmountPage.js rename to src/pages/EditRequestTaxAmountPage.tsx index 6a413bf12655..7c99465fdca0 100644 --- a/src/pages/EditRequestTaxAmountPage.js +++ b/src/pages/EditRequestTaxAmountPage.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; -import PropTypes from 'prop-types'; import React, {useCallback, useRef} from 'react'; +import {TextInput} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,28 +8,28 @@ import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import CONST from '@src/CONST'; import MoneyRequestAmountForm from './iou/steps/MoneyRequestAmountForm'; -const propTypes = { +type EditRequestTaxAmountPageProps = { /** Transaction default amount value */ - defaultAmount: PropTypes.number.isRequired, + defaultAmount: number; /** Transaction default tax amount value */ - defaultTaxAmount: PropTypes.number.isRequired, + defaultTaxAmount: number; /** Transaction default currency value */ - defaultCurrency: PropTypes.string.isRequired, + defaultCurrency: string; /** Callback to fire when the Save button is pressed */ - onSubmit: PropTypes.func.isRequired, + onSubmit: () => void; /** Callback to fire when we press on the currency */ - onNavigateToCurrency: PropTypes.func.isRequired, + onNavigateToCurrency: () => void; }; -function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurrency, onNavigateToCurrency, onSubmit}) { +function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurrency, onNavigateToCurrency, onSubmit}: EditRequestTaxAmountPageProps) { const {translate} = useLocalize(); - const textInput = useRef(null); + const textInput = useRef(null); - const focusTimeoutRef = useRef(null); + const focusTimeoutRef = useRef(null); useFocusEffect( useCallback(() => { @@ -52,10 +52,11 @@ function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurre > (textInput.current = e)} + ref={textInput} onCurrencyButtonPress={onNavigateToCurrency} onSubmitButtonPress={onSubmit} isEditing @@ -64,7 +65,6 @@ function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurre ); } -EditRequestTaxAmountPage.propTypes = propTypes; EditRequestTaxAmountPage.displayName = 'EditRequestTaxAmountPage'; export default EditRequestTaxAmountPage; From 1f1df34f216dfce421e436f4e1282a5771982bab Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 31 Jan 2024 15:28:00 +0100 Subject: [PATCH 069/699] migrate EditRequestTaxRatePage to tsc --- ...uestTaxRatePage.js => EditRequestTaxRatePage.tsx} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename src/pages/{EditRequestTaxRatePage.js => EditRequestTaxRatePage.tsx} (86%) diff --git a/src/pages/EditRequestTaxRatePage.js b/src/pages/EditRequestTaxRatePage.tsx similarity index 86% rename from src/pages/EditRequestTaxRatePage.js rename to src/pages/EditRequestTaxRatePage.tsx index 2a22ab087435..4991349d4225 100644 --- a/src/pages/EditRequestTaxRatePage.js +++ b/src/pages/EditRequestTaxRatePage.tsx @@ -5,18 +5,18 @@ import ScreenWrapper from '@components/ScreenWrapper'; import TaxPicker from '@components/TaxPicker'; import useLocalize from '@hooks/useLocalize'; -const propTypes = { +type EditRequestTaxRatePageProps = { /** Transaction default tax Rate value */ - defaultTaxRate: PropTypes.string.isRequired, + defaultTaxRate: string, /** The policyID we are getting categories for */ - policyID: PropTypes.string.isRequired, + policyID: string, /** Callback to fire when the Save button is pressed */ - onSubmit: PropTypes.func.isRequired, + onSubmit: () => void, }; -function EditRequestTaxRatePage({defaultTaxRate, policyID, onSubmit}) { +function EditRequestTaxRatePage({defaultTaxRate, policyID, onSubmit}: EditRequestTaxRatePageProps) { const {translate} = useLocalize(); return ( @@ -29,6 +29,7 @@ function EditRequestTaxRatePage({defaultTaxRate, policyID, onSubmit}) { <> Date: Wed, 31 Jan 2024 15:34:36 +0100 Subject: [PATCH 070/699] fix tsc errors --- src/pages/EditRequestTaxAmountPage.tsx | 4 ++-- src/pages/EditRequestTaxRatePage.tsx | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pages/EditRequestTaxAmountPage.tsx b/src/pages/EditRequestTaxAmountPage.tsx index 7c99465fdca0..437e84df652c 100644 --- a/src/pages/EditRequestTaxAmountPage.tsx +++ b/src/pages/EditRequestTaxAmountPage.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef} from 'react'; -import {TextInput} from 'react-native'; +import type {TextInput} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -52,7 +52,7 @@ function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurre > void, + onSubmit: () => void; }; function EditRequestTaxRatePage({defaultTaxRate, policyID, onSubmit}: EditRequestTaxRatePageProps) { @@ -29,7 +28,7 @@ function EditRequestTaxRatePage({defaultTaxRate, policyID, onSubmit}: EditReques <> Date: Wed, 31 Jan 2024 15:35:25 +0100 Subject: [PATCH 071/699] fix lint --- src/pages/EditRequestTaxRatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditRequestTaxRatePage.tsx b/src/pages/EditRequestTaxRatePage.tsx index 778f25ae0d98..1c1dc8219ae2 100644 --- a/src/pages/EditRequestTaxRatePage.tsx +++ b/src/pages/EditRequestTaxRatePage.tsx @@ -28,7 +28,7 @@ function EditRequestTaxRatePage({defaultTaxRate, policyID, onSubmit}: EditReques <> Date: Wed, 31 Jan 2024 12:58:51 -0800 Subject: [PATCH 072/699] Delete unused params for deleted command ReconnectToReport --- src/libs/API/parameters/ReconnectToReportParams.ts | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/libs/API/parameters/ReconnectToReportParams.ts diff --git a/src/libs/API/parameters/ReconnectToReportParams.ts b/src/libs/API/parameters/ReconnectToReportParams.ts deleted file mode 100644 index e7701cd36ca9..000000000000 --- a/src/libs/API/parameters/ReconnectToReportParams.ts +++ /dev/null @@ -1,5 +0,0 @@ -type ReconnectToReportParams = { - reportID: string; -}; - -export default ReconnectToReportParams; From 10360a32be5e48825a62ce4fd058f494a5020cc1 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Wed, 31 Jan 2024 15:21:02 -0800 Subject: [PATCH 073/699] Remove ReconnectToReportParams --- src/libs/API/parameters/index.ts | 1 - src/libs/API/types.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 039398c0fbf6..0fd627ec7f9f 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -76,7 +76,6 @@ export type {default as VerifyIdentityForBankAccountParams} from './VerifyIdenti export type {default as AnswerQuestionsForWalletParams} from './AnswerQuestionsForWalletParams'; export type {default as AddCommentOrAttachementParams} from './AddCommentOrAttachementParams'; export type {default as OptInOutToPushNotificationsParams} from './OptInOutToPushNotificationsParams'; -export type {default as ReconnectToReportParams} from './ReconnectToReportParams'; export type {default as ReadNewestActionParams} from './ReadNewestActionParams'; export type {default as MarkAsUnreadParams} from './MarkAsUnreadParams'; export type {default as TogglePinnedChatParams} from './TogglePinnedChatParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index f58ebc30b4a2..f17645a3e3ac 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -185,7 +185,6 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ADD_PERSONAL_BANK_ACCOUNT]: Parameters.AddPersonalBankAccountParams; [WRITE_COMMANDS.OPT_IN_TO_PUSH_NOTIFICATIONS]: Parameters.OptInOutToPushNotificationsParams; [WRITE_COMMANDS.OPT_OUT_OF_PUSH_NOTIFICATIONS]: Parameters.OptInOutToPushNotificationsParams; - [WRITE_COMMANDS.RECONNECT_TO_REPORT]: Parameters.ReconnectToReportParams; [WRITE_COMMANDS.READ_NEWEST_ACTION]: Parameters.ReadNewestActionParams; [WRITE_COMMANDS.MARK_AS_UNREAD]: Parameters.MarkAsUnreadParams; [WRITE_COMMANDS.TOGGLE_PINNED_CHAT]: Parameters.TogglePinnedChatParams; From a73e36e5bf801b65ade15afcbf284a9b7f1f5239 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Sun, 4 Feb 2024 22:20:22 +0100 Subject: [PATCH 074/699] export tax rates types in onyx types --- src/types/onyx/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index d0ac2ce395fa..138bc0ba2270 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -44,6 +44,7 @@ import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type {PolicyReportField, PolicyReportFields} from './PolicyReportField'; import type {PolicyTag, PolicyTags} from './PolicyTag'; +import type {PolicyTaxRates, TaxRate, TaxRates} from './PolicyTaxRates'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -121,6 +122,9 @@ export type { PolicyMembers, PolicyTag, PolicyTags, + TaxRate, + TaxRates, + PolicyTaxRates, PrivatePersonalDetails, RecentWaypoint, RecentlyUsedCategories, From 3e19f56714ba0927c1d0b2cbbb4de72587b926a5 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 5 Feb 2024 09:52:14 +0100 Subject: [PATCH 075/699] add PolicyTaxRates to ONYX KEYS --- src/ONYXKEYS.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 7328fb2543ad..bf1cdacea8d9 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -460,6 +460,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTags; + [ONYXKEYS.COLLECTION.POLICY_TAX_RATE]: OnyxTypes.PolicyTaxRates; [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; From ec6cf4f93decac331add63ba0f631f76c384d533 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 5 Feb 2024 09:52:35 +0100 Subject: [PATCH 076/699] fix typescript --- .../ReportActionItem/MoneyRequestView.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index e92821f8e94c..9c1db3d07a7a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -56,7 +56,7 @@ type MoneyRequestViewOnyxPropsWithoutTransaction = { /** Collection of tags attached to a policy */ policyTags: OnyxEntry; - /** Collection of tax rates attached to a policy */ + /** Collection of tax rates attached to a policy */ policyTaxRates: OnyxEntry; /** The expense report or iou report (only will have a value if this is a transaction thread) */ @@ -124,7 +124,8 @@ function MoneyRequestView({ const formattedTaxAmount = transactionTaxAmount ? CurrencyUtils.convertToDisplayString(transactionTaxAmount, transactionCurrency) : ''; - const taxRateTitle = TransactionUtils.getTaxName(policyTaxRates.taxes, transactionTaxCode); + const policyTaxRatesDescription = (policyTaxRates && policyTaxRates.name) ?? ''; + const taxRateTitle = (transactionTaxCode && policyTaxRates && TransactionUtils.getTaxName(policyTaxRates.taxes, transactionTaxCode)) ?? ''; // Flags for allowing or disallowing editing a money request const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); @@ -155,8 +156,8 @@ function MoneyRequestView({ const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(Object.values(policyTagsList))); const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true)); - // A flag for showing tax rate - const shouldShowTax = isTaxPolicyEnabled(isPolicyExpenseChat, policy) && transactionTaxCode && transactionTaxAmount; + // A flag for showing tax rate + const shouldShowTax = isTaxPolicyEnabled(isPolicyExpenseChat, policy) && transactionTaxCode && transactionTaxAmount; const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); @@ -370,7 +371,7 @@ function MoneyRequestView({ Date: Mon, 5 Feb 2024 10:07:04 +0100 Subject: [PATCH 077/699] use optional chaining --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 9fdcd7b5125a..2163332b507e 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -124,7 +124,7 @@ function MoneyRequestView({ const formattedTaxAmount = transactionTaxAmount ? CurrencyUtils.convertToDisplayString(transactionTaxAmount, transactionCurrency) : ''; - const policyTaxRatesDescription = (policyTaxRates && policyTaxRates.name) ?? ''; + const policyTaxRatesDescription = policyTaxRates?.name; const taxRateTitle = (transactionTaxCode && policyTaxRates && TransactionUtils.getTaxName(policyTaxRates.taxes, transactionTaxCode)) ?? ''; // Flags for allowing or disallowing editing a money request From 6364ee97a9b52a3edd970134a2d9a786e5477459 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 5 Feb 2024 16:58:36 +0700 Subject: [PATCH 078/699] remove NewRequestAmountPage --- src/ROUTES.ts | 20 ++++++------ src/SCREENS.ts | 1 - .../MoneyRequestConfirmationList.js | 10 +++++- ...oraryForRefactorRequestConfirmationList.js | 7 ++--- .../ReportActionItem/MoneyRequestView.tsx | 4 ++- src/libs/IOUUtils.ts | 2 +- .../AppNavigator/ModalStackNavigators.tsx | 1 - src/libs/Navigation/linkingConfig/config.ts | 1 - src/libs/Navigation/types.ts | 1 - .../AttachmentPickerWithMenuItems.js | 6 ++-- .../FloatingActionButtonAndPopover.js | 7 ++++- .../request/IOURequestRedirectToStartPage.js | 2 +- .../iou/request/step/IOURequestStepAmount.js | 31 ++++++++++++++++--- .../step/withFullTransactionOrNotFound.js | 11 ++++++- 14 files changed, 71 insertions(+), 33 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a84dc9c8f9ae..7156ecdfd2fb 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -265,10 +265,6 @@ const ROUTES = { route: ':iouType/new/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}` as const, }, - MONEY_REQUEST_AMOUNT: { - route: ':iouType/new/amount/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/amount/${reportID}` as const, - }, MONEY_REQUEST_PARTICIPANTS: { route: ':iouType/new/participants/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/participants/${reportID}` as const, @@ -317,17 +313,18 @@ const ROUTES = { MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', MONEY_REQUEST_CREATE: { - route: 'create/:iouType/start/:transactionID/:reportID', - getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}` as const, + route: ':action/:iouType/start/:transactionID/:reportID', + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => + `${action}/${iouType}/start/${transactionID}/${reportID}` as const, }, MONEY_REQUEST_STEP_CONFIRMATION: { route: 'create/:iouType/confirmation/:transactionID/:reportID', getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/confirmation/${transactionID}/${reportID}` as const, }, MONEY_REQUEST_STEP_AMOUNT: { - route: 'create/:iouType/amount/:transactionID/:reportID', - getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`create/${iouType}/amount/${transactionID}/${reportID}`, backTo), + route: ':action/:iouType/amount/:transactionID/:reportID', + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action}/${iouType}/amount/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAX_RATE: { route: 'create/:iouType/taxRate/:transactionID/:reportID?', @@ -400,8 +397,9 @@ const ROUTES = { getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/distance` as const, }, MONEY_REQUEST_CREATE_TAB_MANUAL: { - route: 'create/:iouType/start/:transactionID/:reportID/manual', - getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/manual` as const, + route: ':action/:iouType/start/:transactionID/:reportID/manual', + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => + `${action}/${iouType}/start/${transactionID}/${reportID}/manual` as const, }, MONEY_REQUEST_CREATE_TAB_SCAN: { route: 'create/:iouType/start/:transactionID/:reportID/scan', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 96b284dbea2f..8fc25571c9d9 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -145,7 +145,6 @@ const SCREENS = { STEP_TAX_AMOUNT: 'Money_Request_Step_Tax_Amount', STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate', ROOT: 'Money_Request', - AMOUNT: 'Money_Request_Amount', PARTICIPANTS: 'Money_Request_Participants', CONFIRMATION: 'Money_Request_Confirmation', CURRENCY: 'Money_Request_Currency', diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index afabb40fd9f4..4f4861bdc2e5 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -645,7 +645,15 @@ function MoneyRequestConfirmationList(props) { return; } if (props.isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.AMOUNT)); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute( + CONST.IOU.ACTION.EDIT, + CONST.IOU.TYPE.SPLIT, + props.transaction.transactionID, + props.reportID, + Navigation.getActiveRouteWithoutParams(), + ), + ); return; } Navigation.navigate(ROUTES.MONEY_REQUEST_AMOUNT.getRoute(props.iouType, props.reportID)); diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 2aff0444a59e..34212facfdc4 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -672,11 +672,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ if (isDistanceRequest) { return; } - if (isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID, CONST.EDIT_REQUEST_FIELD.AMOUNT)); - return; - } - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + const action = isEditingSplitBill ? CONST.IOU.ACTION.EDIT : CONST.IOU.ACTION.CREATE; + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(action, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())); }} style={[styles.moneyRequestMenuItem, styles.mt2]} titleStyle={styles.moneyRequestConfirmationAmount} diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 3533506797bb..1d9267f09a91 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -254,7 +254,9 @@ function MoneyRequestView({ titleStyle={styles.newKansasLarge} interactive={canEditAmount} shouldShowRightIcon={canEditAmount} - onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} + onPress={() => + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, transaction?.transactionID ?? '', report.reportID)) + } brickRoadIndicator={hasViolations('amount') || (hasErrors && transactionAmount === 0) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} error={hasErrors && transactionAmount === 0 ? translate('common.error.enterAmount') : ''} /> diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index c0fb4c6195b1..fcaafd961950 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -17,7 +17,7 @@ function navigateToStartMoneyRequestStep(requestType: ValueOf require('../../../pages/iou/request/step/IOURequestStepTag').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: () => require('../../../pages/iou/request/step/IOURequestStepWaypoint').default as React.ComponentType, [SCREENS.MONEY_REQUEST.ROOT]: () => require('../../../pages/iou/MoneyRequestSelectorPage').default as React.ComponentType, - [SCREENS.MONEY_REQUEST.AMOUNT]: () => require('../../../pages/iou/steps/NewRequestAmountPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.PARTICIPANTS]: () => require('../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.CONFIRMATION]: () => require('../../../pages/iou/steps/MoneyRequestConfirmPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.CURRENCY]: () => require('../../../pages/iou/IOUCurrencySelection').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index f1c9c316fe93..7cdf9c3679fa 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -397,7 +397,6 @@ const config: LinkingOptions['config'] = { }, }, }, - [SCREENS.MONEY_REQUEST.AMOUNT]: ROUTES.MONEY_REQUEST_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_RATE]: ROUTES.MONEY_REQUEST_STEP_TAX_RATE.route, [SCREENS.MONEY_REQUEST.PARTICIPANTS]: ROUTES.MONEY_REQUEST_PARTICIPANTS.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3c4cf17853f1..077ebd4caaac 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -194,7 +194,6 @@ type RoomInviteNavigatorParamList = { type MoneyRequestNavigatorParamList = { [SCREENS.MONEY_REQUEST.ROOT]: undefined; - [SCREENS.MONEY_REQUEST.AMOUNT]: undefined; [SCREENS.MONEY_REQUEST.PARTICIPANTS]: { iouType: string; reportID: string; diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index df5645ae61ad..7f10e7fa96bd 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -145,12 +145,14 @@ function AttachmentPickerWithMenuItems({ [CONST.IOU.TYPE.SPLIT]: { icon: Expensicons.Receipt, text: translate('iou.splitBill'), - onSelected: () => Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.SPLIT, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report.reportID)), + onSelected: () => + Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.SPLIT, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report.reportID)), }, [CONST.IOU.TYPE.REQUEST]: { icon: Expensicons.MoneyCircle, text: translate('iou.requestMoney'), - onSelected: () => Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.REQUEST, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report.reportID)), + onSelected: () => + Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.REQUEST, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report.reportID)), }, [CONST.IOU.TYPE.SEND]: { icon: Expensicons.Send, diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 0df490fa4466..0dc290f53f2a 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -169,7 +169,12 @@ function FloatingActionButtonAndPopover(props) { Navigation.navigate( // When starting to create a money request from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used // for all of the routes in the creation flow. - ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.REQUEST, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, ReportUtils.generateReportID()), + ROUTES.MONEY_REQUEST_CREATE.getRoute( + CONST.IOU.ACTION.CREATE, + CONST.IOU.TYPE.REQUEST, + CONST.IOU.OPTIMISTIC_TRANSACTION_ID, + ReportUtils.generateReportID(), + ), ), ), }, diff --git a/src/pages/iou/request/IOURequestRedirectToStartPage.js b/src/pages/iou/request/IOURequestRedirectToStartPage.js index ee98c8006cdb..e4091c48f1b8 100644 --- a/src/pages/iou/request/IOURequestRedirectToStartPage.js +++ b/src/pages/iou/request/IOURequestRedirectToStartPage.js @@ -43,7 +43,7 @@ function IOURequestRedirectToStartPage({ if (iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE) { Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE_TAB_DISTANCE.getRoute(iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, optimisticReportID)); } else if (iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL) { - Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE_TAB_MANUAL.getRoute(iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, optimisticReportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE_TAB_MANUAL.getRoute(CONST.IOU.ACTION.CREATE, iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, optimisticReportID)); } else if (iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN) { Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, optimisticReportID)); } diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index 84e0ac8533c5..4c2b24c87be2 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -59,7 +59,7 @@ const getTaxAmount = (transaction, defaultTaxValue, amount) => { function IOURequestStepAmount({ report, route: { - params: {iouType, reportID, transactionID, backTo, currency: selectedCurrency}, + params: {iouType, reportID, transactionID, backTo, currency: selectedCurrency, action}, }, transaction, transaction: {currency: originalCurrency}, @@ -71,6 +71,8 @@ function IOURequestStepAmount({ const focusTimeoutRef = useRef(null); const iouRequestType = getRequestType(transaction); const currency = selectedCurrency || originalCurrency; + const isEditing = action === CONST.IOU.ACTION.EDIT; + const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)); const isTaxTrackingEnabled = isPolicyExpenseChat && policy.isTaxTrackingEnabled; @@ -128,21 +130,40 @@ function IOURequestStepAmount({ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(iouType, transactionID, reportID)); }; + const saveAmountAndCurrency = ({amount}) => { + const newAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); + + // If the value hasn't changed, don't request to save changes on the server and just close the modal + if (newAmount === TransactionUtils.getAmount(transaction) && currency === TransactionUtils.getCurrency(transaction)) { + Navigation.dismissModal(); + return; + } + + if (isSplitBill) { + IOU.setDraftSplitTransaction(transactionID, {amount: newAmount, currency}); + Navigation.goBack(backTo); + return; + } + + IOU.updateMoneyRequestAmountAndCurrency(transactionID, reportID, currency, newAmount); + Navigation.dismissModal(); + }; + return ( (textInput.current = e)} onCurrencyButtonPress={navigateToCurrencySelectionPage} - onSubmitButtonPress={navigateToNextPage} + onSubmitButtonPress={isEditing ? saveAmountAndCurrency : navigateToNextPage} selectedTab={iouRequestType} /> diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.js b/src/pages/iou/request/step/withFullTransactionOrNotFound.js index 7cdbb3484999..801ad403af2a 100644 --- a/src/pages/iou/request/step/withFullTransactionOrNotFound.js +++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.js @@ -71,7 +71,16 @@ export default function (WrappedComponent) { key: ({route}) => { const transactionID = lodashGet(route, 'params.transactionID', 0); const userAction = lodashGet(route, 'params.action', CONST.IOU.ACTION.CREATE); - return `${userAction === CONST.IOU.ACTION.CREATE ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; + const isSplitBill = lodashGet(route, 'params.iouType', CONST.IOU.TYPE.REQUEST) === CONST.IOU.TYPE.SPLIT; + let transactionCollectionKey = ONYXKEYS.COLLECTION.TRANSACTION_DRAFT; + if (userAction === CONST.IOU.ACTION.EDIT) { + if (isSplitBill) { + transactionCollectionKey = ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT; + } else { + transactionCollectionKey = ONYXKEYS.COLLECTION.TRANSACTION; + } + } + return `${transactionCollectionKey}${transactionID}`; }, }, })(WithFullTransactionOrNotFoundWithRef); From 02a8a1897982b3d356a8541246c2545be55a5c6d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 5 Feb 2024 17:20:20 +0700 Subject: [PATCH 079/699] fix negative amount case --- src/libs/TransactionUtils.ts | 4 ++-- src/pages/iou/request/step/IOURequestStepAmount.js | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 8e48eddea0ac..6f55eb961b14 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -281,12 +281,12 @@ function getAmount(transaction: OnyxEntry, isFromExpenseReport?: bo // we need to return an opposite sign than is saved in the transaction object let amount = transaction?.modifiedAmount ?? 0; if (amount) { - return -amount; + return Math.abs(amount); } // To avoid -0 being shown, lets only change the sign if the value is other than 0. amount = transaction?.amount ?? 0; - return amount ? -amount : 0; + return amount ? Math.abs(amount) : 0; } /** diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index 4c2b24c87be2..fdc07f38eed6 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -71,9 +71,12 @@ function IOURequestStepAmount({ const focusTimeoutRef = useRef(null); const iouRequestType = getRequestType(transaction); const currency = selectedCurrency || originalCurrency; + const {amount: transactionAmount} = ReportUtils.getTransactionDetails(transaction); const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; + console.log(transaction); + const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)); const isTaxTrackingEnabled = isPolicyExpenseChat && policy.isTaxTrackingEnabled; @@ -160,7 +163,7 @@ function IOURequestStepAmount({ (textInput.current = e)} onCurrencyButtonPress={navigateToCurrencySelectionPage} onSubmitButtonPress={isEditing ? saveAmountAndCurrency : navigateToNextPage} From a719511f5f925628405cb595599cf2fcb2f444d2 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 5 Feb 2024 22:57:09 +0700 Subject: [PATCH 080/699] fix the case edit split --- .../iou/request/step/IOURequestStepAmount.js | 10 ++++--- .../step/withFullTransactionOrNotFound.js | 26 +++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index fdc07f38eed6..63cf19299a80 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -2,6 +2,7 @@ import {useFocusEffect} from '@react-navigation/native'; import PropTypes from 'prop-types'; import React, {useCallback, useRef} from 'react'; import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; import taxPropTypes from '@components/taxPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; @@ -33,6 +34,9 @@ const propTypes = { /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, + /** The draft transaction of scan split bill */ + splitTransactionDraft: transactionPropTypes, + /* Onyx Props */ /** Collection of tax rates attached to a policy */ policyTaxRates: taxPropTypes, @@ -49,6 +53,7 @@ const defaultProps = { transaction: {}, policyTaxRates: {}, policy: {}, + splitTransactionDraft: {}, }; const getTaxAmount = (transaction, defaultTaxValue, amount) => { @@ -62,6 +67,7 @@ function IOURequestStepAmount({ params: {iouType, reportID, transactionID, backTo, currency: selectedCurrency, action}, }, transaction, + splitTransactionDraft, transaction: {currency: originalCurrency}, policyTaxRates, policy, @@ -71,12 +77,10 @@ function IOURequestStepAmount({ const focusTimeoutRef = useRef(null); const iouRequestType = getRequestType(transaction); const currency = selectedCurrency || originalCurrency; - const {amount: transactionAmount} = ReportUtils.getTransactionDetails(transaction); + const {amount: transactionAmount} = ReportUtils.getTransactionDetails(_.isEmpty(splitTransactionDraft) ? transaction : splitTransactionDraft); const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; - console.log(transaction); - const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)); const isTaxTrackingEnabled = isPolicyExpenseChat && policy.isTaxTrackingEnabled; diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.js b/src/pages/iou/request/step/withFullTransactionOrNotFound.js index 801ad403af2a..1999b3a90574 100644 --- a/src/pages/iou/request/step/withFullTransactionOrNotFound.js +++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.js @@ -18,12 +18,16 @@ const propTypes = { /** The report corresponding to the reportID in the route params */ transaction: transactionPropTypes, + /** The draft transaction of scan split bill */ + splitTransactionDraft: transactionPropTypes, + route: IOURequestStepRoutePropTypes.isRequired, }; const defaultProps = { forwardedRef: () => {}, transaction: {}, + splitTransactionDraft: {}, }; export default function (WrappedComponent) { @@ -31,6 +35,7 @@ export default function (WrappedComponent) { function WithFullTransactionOrNotFound({forwardedRef, ...props}) { const { transaction: {transactionID}, + splitTransactionDraft: {transactionID: splitTransactionDraftID}, } = props; const isFocused = useIsFocused(); @@ -38,7 +43,7 @@ export default function (WrappedComponent) { // If the transaction does not have a transactionID, then the transaction no longer exists in Onyx as a full transaction and the not-found page should be shown. // In addition, the not-found page should be shown only if the component screen's route is active (i.e. is focused). // This is to prevent it from showing when the modal is being dismissed while navigating to a different route (e.g. on requesting money). - if (!transactionID) { + if (!transactionID && !splitTransactionDraftID) { return ; } @@ -71,16 +76,15 @@ export default function (WrappedComponent) { key: ({route}) => { const transactionID = lodashGet(route, 'params.transactionID', 0); const userAction = lodashGet(route, 'params.action', CONST.IOU.ACTION.CREATE); - const isSplitBill = lodashGet(route, 'params.iouType', CONST.IOU.TYPE.REQUEST) === CONST.IOU.TYPE.SPLIT; - let transactionCollectionKey = ONYXKEYS.COLLECTION.TRANSACTION_DRAFT; - if (userAction === CONST.IOU.ACTION.EDIT) { - if (isSplitBill) { - transactionCollectionKey = ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT; - } else { - transactionCollectionKey = ONYXKEYS.COLLECTION.TRANSACTION; - } - } - return `${transactionCollectionKey}${transactionID}`; + return `${userAction === CONST.IOU.ACTION.CREATE ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; + }, + }, + splitTransactionDraft: { + key: ({route}) => { + const transactionID = lodashGet(route, 'params.transactionID', 0); + const userAction = lodashGet(route, 'params.action', CONST.IOU.ACTION.CREATE); + const isEditingSplitBill = userAction === CONST.IOU.ACTION.EDIT && lodashGet(route, 'params.iouType', CONST.IOU.TYPE.REQUEST) === CONST.IOU.TYPE.SPLIT; + return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${isEditingSplitBill ? transactionID : '0'}`; }, }, })(WithFullTransactionOrNotFoundWithRef); From ae15fd56fb9fbfb41f4ca201e0e1fb3ed1f232fa Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 5 Feb 2024 23:03:08 +0700 Subject: [PATCH 081/699] delete old page --- src/pages/iou/MoneyRequestSelectorPage.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 0a0efc38313a..b8b81a904499 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -25,7 +25,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import NewDistanceRequestPage from './NewDistanceRequestPage'; import IOURequestStepScan from './request/step/IOURequestStepScan'; -import NewRequestAmountPage from './steps/NewRequestAmountPage'; const propTypes = { /** React Navigation route */ @@ -127,11 +126,6 @@ function MoneyRequestSelectorPage(props) { /> )} > - {() => } {shouldDisplayDistanceRequest && ( ) : ( - + null )} From 53ada16d87aa815b3901029b36b6953741b7450a Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 6 Feb 2024 20:40:36 -0800 Subject: [PATCH 082/699] report action utils functions --- src/libs/ReportActionsUtils.ts | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 1aeb6e6e7343..6f355137265d 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -492,6 +492,22 @@ function getSortedReportActionsForDisplay(reportActions: ReportActions | null, s return getSortedReportActions(baseURLAdjustedReportActions, true, shouldMarkTheFirstItemAsNewest); } +/** + * This method returns a combined array of report actions from a parent report and child transaction thread report that + * are ready for display in the ReportActionView. + */ +function getCombinedReportActionsForDisplay(reportActions: ReportAction[], transactionThreadReportActions: ReportAction[]): ReportAction[] { + + // Filter out the created action from the transaction thread report actions, since we already have the parent report's created action + const filteredTransactionThreadReportActions = transactionThreadReportActions?.filter(action => action.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED); + + // Sort the combined list of parent report actions and transaction thread report actions + const sortedReportActions = getSortedReportActions([...reportActions, ...filteredTransactionThreadReportActions], true); + + // Filter out IOU report actions because we don't want to show any preview actions for one transaction reports + return sortedReportActions.filter(action => action.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU); +} + /** * In some cases, there can be multiple closed report actions in a chat report. * This method returns the last closed report action so we can always show the correct archived report reason. @@ -660,6 +676,24 @@ function getAllReportActions(reportID: string): ReportActions { return allReportActions?.[reportID] ?? {}; } +/** + * Gets an array of IOU report actions + */ +function getIOUReportActions(reportID: string): ReportAction[] | null { + const reportActions = Object.values(getAllReportActions(reportID)); + if (!reportActions.length) { + return null; + } + + const iouRequestTypes: Array> = [CONST.IOU.REPORT_ACTION_TYPE.CREATE, CONST.IOU.REPORT_ACTION_TYPE.SPLIT]; + const iouRequestActions = reportActions?.filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && iouRequestTypes.includes(action.originalMessage.type)) ?? []; + + if (!iouRequestActions.length) { + return null; + } + return iouRequestActions; +} + /** * Check whether a report action is an attachment (a file, such as an image or a zip). * @@ -829,6 +863,7 @@ function isCurrentActionUnread(report: Report | EmptyObject, reportAction: Repor export { extractLinksFromMessageHtml, getAllReportActions, + getIOUReportActions, getIOUReportIDFromReportActionPreview, getLastClosedReportAction, getLastVisibleAction, @@ -843,6 +878,7 @@ export { getReportPreviewAction, getSortedReportActions, getSortedReportActionsForDisplay, + getCombinedReportActionsForDisplay, isConsecutiveActionMadeByPreviousActor, isCreatedAction, isCreatedTaskReportAction, From df1b56c47b65defc8c439c9c72c9fe0962a5263e Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 6 Feb 2024 20:41:01 -0800 Subject: [PATCH 083/699] report utils functions for one transaction report --- src/libs/ReportUtils.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5c6672d14bd7..9107322f0c67 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1166,6 +1166,38 @@ function isMoneyRequestReport(reportOrID: OnyxEntry | string): boolean { return isIOUReport(report) || isExpenseReport(report); } +/** + * Checks if a report has only one transaction associated with it + */ +function isOneTransactionReport(reportOrID: OnyxEntry | string): boolean { + const report = typeof reportOrID === 'object' ? reportOrID : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null; + + // Check the parent report (which would be the IOU or expense report if the passed report is an IOU or expense request) + // to see how many IOU report actions it contains + const iouReportActions = ReportActionsUtils.getIOUReportActions(report?.reportID ?? ''); + return (iouReportActions?.length ?? 0) === 1; +} + +/** + * Returns the reportID of the first transaction thread associated with a report + */ +function getOneTransactionThreadReportID(reportOrID: OnyxEntry | string): string | undefined { + const report = typeof reportOrID === 'object' ? reportOrID : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null; + + // Get all IOU report actions for the report. + const iouReportAction = ReportActionsUtils.getIOUReportActions(report?.reportID ?? '')?.find(reportAction => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.childReportID); + return String(iouReportAction?.childReportID) ?? '0'; +} + +/** + * Checks if a report is a transaction thread associated with a report that has only one transaction + */ +function isOneTransactionThread(reportOrID: OnyxEntry | string): boolean { + const report = typeof reportOrID === 'object' ? reportOrID : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null; + const parentReport = getParentReport(report); + return isOneTransactionReport(parentReport?.reportID ?? ''); +} + /** * Should return true only for personal 1:1 report * @@ -4907,6 +4939,9 @@ export { hasSingleParticipant, getReportRecipientAccountIDs, isOneOnOneChat, + isOneTransactionReport, + getOneTransactionThreadReportID, + isOneTransactionThread, goBackToDetailsPage, getTransactionReportName, getTransactionDetails, From b841b6aebd771e963e80d9707c3f8d9e77bef45a Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 6 Feb 2024 20:41:54 -0800 Subject: [PATCH 084/699] hide background for money requests on a single transaction report --- src/components/ReportActionItem/MoneyRequestView.tsx | 10 +++++++--- src/pages/home/report/ReportActionItem.js | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 3a3aef6cabcd..c0d15fb4dc02 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -68,6 +68,9 @@ type MoneyRequestViewPropsWithoutTransaction = MoneyRequestViewOnyxPropsWithoutT /** Whether we should display the horizontal rule below the component */ shouldShowHorizontalRule: boolean; + + /** Whether we should display the animated above the component */ + shouldShowAnimatedBackground: boolean; }; type MoneyRequestViewProps = MoneyRequestViewTransactionOnyxProps & MoneyRequestViewPropsWithoutTransaction; @@ -82,6 +85,7 @@ function MoneyRequestView({ policyTags, policy, transactionViolations, + shouldShowAnimatedBackground, }: MoneyRequestViewProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -199,9 +203,9 @@ function MoneyRequestView({ const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => transaction?.pendingFields?.[fieldPath] ?? pendingAction; return ( - - - + + {shouldShowAnimatedBackground && } + {hasReceipt && ( ); From 7ec2e5c1100314685ed899c9f3b67c36bb2e2d4e Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 6 Feb 2024 20:43:07 -0800 Subject: [PATCH 085/699] update css style for money request combine report actions for report view --- src/libs/ReportUtils.ts | 5 +++++ src/pages/home/ReportScreen.js | 15 +++++++++++++-- src/styles/utils/index.ts | 19 ++++++++----------- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9107322f0c67..b838eb5b51b6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3738,6 +3738,11 @@ function shouldReportBeInOptionList({ return false; } + // If this is a transaction thread associated with a report that only has one transaction, omit it + if (isOneTransactionThread(report)) { + return false; + } + // Include the currently viewed report. If we excluded the currently viewed report, then there // would be no way to highlight it in the options list and it would be confusing to users because they lose // a sense of context. diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index bfe27910c943..10cad0e84c0a 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -68,6 +68,9 @@ const propTypes = { /** All the report actions for this report */ reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + /** The report actions for the first transaction thread associated with the report */ + transactionThreadReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + /** The report's parentReportAction */ parentReportAction: PropTypes.shape(reportActionPropTypes), @@ -103,7 +106,8 @@ const propTypes = { const defaultProps = { isSidebarLoaded: false, - reportActions: {}, + reportActions: [], + transactionThreadReportActions: [], parentReportAction: {}, report: {}, reportMetadata: { @@ -143,6 +147,7 @@ function ReportScreen({ report: reportProp, reportMetadata, reportActions, + transactionThreadReportActions, parentReportAction, accountManagerReportID, markReadyForHydration, @@ -278,6 +283,7 @@ function ReportScreen({ const policy = policies[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`] || {}; const isTopMostReportId = currentReportID === getReportID(route); const didSubscribeToReportLeavingEvents = useRef(false); + const isOneTransactionReport = ReportUtils.isOneTransactionReport(report); useEffect(() => { if (!report || !report.reportID || shouldHideReport) { @@ -554,7 +560,7 @@ function ReportScreen({ > {isReportReadyForDisplay && !isLoadingInitialReportActions && !isLoading && ( ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), }, + transactionThreadReportActions: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${ReportUtils.getOneTransactionThreadReportID(getReportID(route))}`, + canEvict: false, + selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), + }, report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${getReportID(route)}`, allowStaleData: true, diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index a45b7cdbcb34..dc8fdac226f2 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -754,21 +754,18 @@ function getLineHeightStyle(lineHeight: number): TextStyle { /** * Gets the correct size for the empty state container based on screen dimensions */ -function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean, isMoneyOrTaskReport = false): ViewStyle { +function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean, isMoneyOrTaskReport = false, shouldShowAnimatedBackground = true): ViewStyle { const emptyStateBackground = isMoneyOrTaskReport ? CONST.EMPTY_STATE_BACKGROUND.MONEY_OR_TASK_REPORT : CONST.EMPTY_STATE_BACKGROUND; - if (isSmallScreenWidth) { - return { - minHeight: emptyStateBackground.SMALL_SCREEN.CONTAINER_MINHEIGHT, - display: 'flex', - justifyContent: 'space-between', - }; - } - - return { - minHeight: emptyStateBackground.WIDE_SCREEN.CONTAINER_MINHEIGHT, + let baseStyles: ViewStyle = { display: 'flex', justifyContent: 'space-between', }; + + if (shouldShowAnimatedBackground) { + baseStyles.minHeight = isSmallScreenWidth ? emptyStateBackground.SMALL_SCREEN.CONTAINER_MINHEIGHT : emptyStateBackground.WIDE_SCREEN.CONTAINER_MINHEIGHT; + } + + return baseStyles; } type GetBaseAutoCompleteSuggestionContainerStyleParams = { From b3dbe2607ec04fb9b8519fc3f1ed828e0857fa96 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 7 Feb 2024 09:28:57 +0100 Subject: [PATCH 086/699] add tax amount API types --- src/libs/API/types.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a4ab3db9a7cd..f6ec3b8732e3 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -121,6 +121,8 @@ const WRITE_COMMANDS = { UPDATE_MONEY_REQUEST_BILLABLE: 'UpdateMoneyRequestBillable', UPDATE_MONEY_REQUEST_MERCHANT: 'UpdateMoneyRequestMerchant', UPDATE_MONEY_REQUEST_TAG: 'UpdateMoneyRequestTag', + UPDATE_MONEY_REQUEST_TAX_AMOUNT: 'UpdateMoneyRequestTaxAmount', + UPDATE_MONEY_REQUEST_TAX_RATE: 'UpdateMoneyRequestTaxRate', UPDATE_MONEY_REQUEST_DISTANCE: 'UpdateMoneyRequestDistance', UPDATE_MONEY_REQUEST_CATEGORY: 'UpdateMoneyRequestCategory', UPDATE_MONEY_REQUEST_DESCRIPTION: 'UpdateMoneyRequestDescription', @@ -259,6 +261,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_MERCHANT]: Parameters.UpdateMoneyRequestParams; [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_BILLABLE]: Parameters.UpdateMoneyRequestParams; [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAG]: Parameters.UpdateMoneyRequestParams; + [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAX_AMOUNT]: Parameters.UpdateMoneyRequestParams; + [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAX_RATE]: Parameters.UpdateMoneyRequestParams; [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DISTANCE]: Parameters.UpdateMoneyRequestParams; [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_CATEGORY]: Parameters.UpdateMoneyRequestParams; [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DESCRIPTION]: Parameters.UpdateMoneyRequestParams; From 0bdef874da54f3a6e8a0630f640e95d08d00928b Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 7 Feb 2024 09:29:48 +0100 Subject: [PATCH 087/699] update tax request types --- src/libs/actions/IOU.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index bc1a8b9dd4cf..319889f54496 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1217,18 +1217,18 @@ function updateMoneyRequestTag(transactionID: string, transactionThreadReportID: } /** Updates the created tax amount of a money request */ -function updateMoneyRequestTaxAmount(transactionID: string, optimisticReportActionID: string, val: number) { +function updateMoneyRequestTaxAmount(transactionID: string, optimisticReportActionID: string, taxAmount: number) { const transactionChanges = { - taxAmount: val, + taxAmount, }; const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, true); API.write('UpdateMoneyRequestTaxAmount', params, onyxData); } /** Updates the created tax rate of a money request */ -function updateMoneyRequestTaxRate(transactionID: string, optimisticReportActionID: string, val: string) { +function updateMoneyRequestTaxRate(transactionID: string, optimisticReportActionID: string, taxCode: string) { const transactionChanges = { - taxCode: val, + taxCode, }; const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, true); API.write('UpdateMoneyRequestTaxRate', params, onyxData); From 3293311d92215d039c17f6654d8718ee94e5d46f Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 7 Feb 2024 11:37:57 +0100 Subject: [PATCH 088/699] add taxCode and taxAmount to getUpdatedTransaction --- src/libs/TransactionUtils.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index bce784819769..7964e8c25b78 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -204,6 +204,16 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra shouldStopSmartscan = true; } + if (Object.hasOwn(transactionChanges, 'taxAmount') && typeof transactionChanges.taxAmount === 'number') { + updatedTransaction.taxAmount = isFromExpenseReport ? -transactionChanges.taxAmount : transactionChanges.taxAmount; + shouldStopSmartscan = true; + } + + if (Object.hasOwn(transactionChanges, 'taxCode') && typeof transactionChanges.taxCode === 'string') { + updatedTransaction.taxCode = transactionChanges.taxCode; + shouldStopSmartscan = true; + } + if (Object.hasOwn(transactionChanges, 'billable') && typeof transactionChanges.billable === 'boolean') { updatedTransaction.billable = transactionChanges.billable; } @@ -237,6 +247,8 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra ...(Object.hasOwn(transactionChanges, 'billable') && {billable: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(Object.hasOwn(transactionChanges, 'category') && {category: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(Object.hasOwn(transactionChanges, 'tag') && {tag: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(Object.hasOwn(transactionChanges, 'taxAmount') && {taxAmount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(Object.hasOwn(transactionChanges, 'taxCode') && {taxCode: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }; return updatedTransaction; From 9abeb11ad5ee99e91a05a44862d006e2093777ab Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 7 Feb 2024 17:31:32 +0100 Subject: [PATCH 089/699] update Tax Amount Menu Item Description in ConfirmationList --- src/components/MoneyRequestConfirmationList.js | 2 +- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 596811086ee4..824652d481cf 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -810,7 +810,7 @@ function MoneyRequestConfirmationList(props) { diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index cd1874197063..902139610489 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -865,7 +865,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ From 59768825ac3e1a1f07253269c180b5417ed9ef1b Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 7 Feb 2024 17:32:02 +0100 Subject: [PATCH 090/699] update Tax Amount Menu Item Description in MoneyRequestView --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index a545bd99c378..7e04b3224119 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -390,7 +390,7 @@ function MoneyRequestView({ Date: Wed, 7 Feb 2024 17:34:15 +0100 Subject: [PATCH 091/699] add pending action for taxCode --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 7e04b3224119..927dff901158 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -374,7 +374,7 @@ function MoneyRequestView({ )} {shouldShowTax && ( - + Date: Wed, 7 Feb 2024 17:35:02 +0100 Subject: [PATCH 092/699] add pending action for taxAmount --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 927dff901158..e6a20977ae15 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -387,7 +387,7 @@ function MoneyRequestView({ )} {shouldShowTax && ( - + Date: Wed, 7 Feb 2024 20:42:37 +0000 Subject: [PATCH 093/699] chore(typescript): migrate IOU related components --- src/libs/OptionsListUtils.ts | 2 +- src/libs/ReportUtils.ts | 2 +- src/libs/TransactionUtils.ts | 18 +- src/libs/actions/IOU.ts | 26 ++- src/pages/iou/NewDistanceRequestPage.tsx | 2 +- src/pages/iou/SplitBillDetailsPage.js | 196 ------------------- src/pages/iou/SplitBillDetailsPage.tsx | 25 ++- src/pages/iou/steps/NewRequestAmountPage.tsx | 2 +- 8 files changed, 41 insertions(+), 232 deletions(-) delete mode 100644 src/pages/iou/SplitBillDetailsPage.js diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index b6518b361381..33e5bbf25cbc 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -725,7 +725,7 @@ function createOption( /** * Get the option for a policy expense report. */ -function getPolicyExpenseReportOption(report: Report): ReportUtils.OptionData { +function getPolicyExpenseReportOption(report: Partial): ReportUtils.OptionData { const expenseReport = policyExpenseReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]; const option = createOption( diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 64d79a3cd812..c1751c7ff164 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1984,7 +1984,7 @@ function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry< * into a flat object. Used for displaying transactions and sending them in API commands */ -function getTransactionDetails(transaction: OnyxEntry, createdDateFormat: string = CONST.DATE.FNS_FORMAT_STRING): TransactionDetails | undefined { +function getTransactionDetails(transaction: OnyxEntry | undefined, createdDateFormat: string = CONST.DATE.FNS_FORMAT_STRING): TransactionDetails | undefined { if (!transaction) { return; } diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 8a814f311481..47f52a534605 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -135,11 +135,11 @@ function hasReceipt(transaction: Transaction | undefined | null): boolean { return !!transaction?.receipt?.state || hasEReceipt(transaction); } -function isMerchantMissing(transaction: Transaction) { - if (transaction.modifiedMerchant && transaction.modifiedMerchant !== '') { - return transaction.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; +function isMerchantMissing(transaction: Transaction | undefined) { + if (transaction?.modifiedMerchant && transaction.modifiedMerchant !== '') { + return transaction?.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; } - const isMerchantEmpty = transaction.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction.merchant === ''; + const isMerchantEmpty = transaction?.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction?.merchant === ''; return isMerchantEmpty; } @@ -151,15 +151,15 @@ function isPartialMerchant(merchant: string): boolean { return merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; } -function isAmountMissing(transaction: Transaction) { - return transaction.amount === 0 && (!transaction.modifiedAmount || transaction.modifiedAmount === 0); +function isAmountMissing(transaction: Transaction | undefined) { + return transaction?.amount === 0 && (!transaction.modifiedAmount || transaction.modifiedAmount === 0); } -function isCreatedMissing(transaction: Transaction) { - return transaction.created === '' && (!transaction.created || transaction.modifiedCreated === ''); +function isCreatedMissing(transaction: Transaction | undefined) { + return transaction?.created === '' && (!transaction.created || transaction.modifiedCreated === ''); } -function areRequiredFieldsEmpty(transaction: Transaction): boolean { +function areRequiredFieldsEmpty(transaction: Transaction | undefined): boolean { const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`] ?? null; const isFromExpenseReport = parentReport?.type === CONST.REPORT.TYPE.EXPENSE; return (isFromExpenseReport && isMerchantMissing(transaction)) || isAmountMissing(transaction) || isCreatedMissing(transaction); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0ce540bea456..e9bbffb076f8 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2086,9 +2086,15 @@ function startSplitBill( * @param sessionAccountID - accountID of the current user * @param sessionEmail - email of the current user */ -function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportAction, updatedTransaction: OnyxTypes.Transaction, sessionAccountID: number, sessionEmail: string) { +function completeSplitBill( + chatReportID: string, + reportAction: OnyxTypes.ReportAction, + updatedTransaction: OnyxTypes.Transaction | undefined, + sessionAccountID: number, + sessionEmail: string, +) { const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(sessionEmail); - const {transactionID} = updatedTransaction; + const {transactionID} = updatedTransaction ?? {transactionID: ''}; const unmodifiedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; // Save optimistic updated transaction and action @@ -2149,8 +2155,8 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA }, ]; - const splitParticipants: Split[] = updatedTransaction.comment.splits ?? []; - const {modifiedAmount: amount, modifiedCurrency: currency} = updatedTransaction; + const splitParticipants: Split[] = updatedTransaction?.comment.splits ?? []; + const {modifiedAmount: amount, modifiedCurrency: currency} = updatedTransaction ?? {}; // Exclude the current user when calculating the split amount, `calculateAmount` takes it into account const splitAmount = IOUUtils.calculateAmount(splitParticipants.length - 1, amount ?? 0, currency ?? '', false); @@ -2208,13 +2214,13 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA isPolicyExpenseChat ? -splitAmount : splitAmount, currency ?? '', oneOnOneIOUReport?.reportID ?? '', - updatedTransaction.comment.comment, - updatedTransaction.modifiedCreated, + updatedTransaction?.comment.comment, + updatedTransaction?.modifiedCreated, CONST.IOU.TYPE.SPLIT, transactionID, - updatedTransaction.modifiedMerchant, - {...updatedTransaction.receipt, state: CONST.IOU.RECEIPT_STATE.OPEN}, - updatedTransaction.filename, + updatedTransaction?.modifiedMerchant, + {...updatedTransaction?.receipt, state: CONST.IOU.RECEIPT_STATE.OPEN}, + updatedTransaction?.filename, ); const oneOnOneCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); @@ -2223,7 +2229,7 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA CONST.IOU.REPORT_ACTION_TYPE.CREATE, splitAmount, currency ?? '', - updatedTransaction.comment.comment ?? '', + updatedTransaction?.comment.comment ?? '', [participant], oneOnOneTransaction.transactionID, undefined, diff --git a/src/pages/iou/NewDistanceRequestPage.tsx b/src/pages/iou/NewDistanceRequestPage.tsx index f40eb5d4fbe1..260d6d6144d2 100644 --- a/src/pages/iou/NewDistanceRequestPage.tsx +++ b/src/pages/iou/NewDistanceRequestPage.tsx @@ -36,7 +36,7 @@ function NewDistanceRequestPage({iou, report, route}: NewDistanceRequestPageProp Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report?.reportID)); return; } - IOU.navigateToNextPage(iou ?? {}, iouType, report ?? {}); + IOU.navigateToNextPage(iou, iouType, report ?? undefined); }, [iou, iouType, isEditingNewRequest, report]); return ( diff --git a/src/pages/iou/SplitBillDetailsPage.js b/src/pages/iou/SplitBillDetailsPage.js deleted file mode 100644 index be3afb822723..000000000000 --- a/src/pages/iou/SplitBillDetailsPage.js +++ /dev/null @@ -1,196 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import MoneyRequestConfirmationList from '@components/MoneyRequestConfirmationList'; -import MoneyRequestHeaderStatusBar from '@components/MoneyRequestHeaderStatusBar'; -import ScreenWrapper from '@components/ScreenWrapper'; -import transactionPropTypes from '@components/transactionPropTypes'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; -import withReportAndReportActionOrNotFound from '@pages/home/report/withReportAndReportActionOrNotFound'; -import personalDetailsPropType from '@pages/personalDetailsPropType'; -import reportPropTypes from '@pages/reportPropTypes'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; - -const propTypes = { - /* Onyx Props */ - - /** The personal details of the person who is logged in */ - personalDetails: personalDetailsPropType, - - /** The active report */ - report: reportPropTypes.isRequired, - - /** Array of report actions for this report */ - reportActions: PropTypes.shape(reportActionPropTypes), - - /** The current transaction */ - transaction: transactionPropTypes.isRequired, - - /** The draft transaction that holds data to be persisited on the current transaction */ - draftTransaction: transactionPropTypes, - - /** Route params */ - route: PropTypes.shape({ - params: PropTypes.shape({ - /** Report ID passed via route r/:reportID/split/details */ - reportID: PropTypes.string, - - /** ReportActionID passed via route r/split/:reportActionID */ - reportActionID: PropTypes.string, - }), - }).isRequired, - - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user accountID */ - accountID: PropTypes.number, - - /** Currently logged in user email */ - email: PropTypes.string, - }).isRequired, -}; - -const defaultProps = { - personalDetails: {}, - reportActions: {}, - draftTransaction: undefined, -}; - -function SplitBillDetailsPage(props) { - const styles = useThemeStyles(); - const {reportID} = props.report; - const {translate} = useLocalize(); - const reportAction = props.reportActions[`${props.route.params.reportActionID.toString()}`]; - const participantAccountIDs = reportAction.originalMessage.participantAccountIDs; - - // In case this is workspace split bill, we manually add the workspace as the second participant of the split bill - // because we don't save any accountID in the report action's originalMessage other than the payee's accountID - let participants; - if (ReportUtils.isPolicyExpenseChat(props.report)) { - participants = [ - OptionsListUtils.getParticipantsOption({accountID: participantAccountIDs[0], selected: true}, props.personalDetails), - OptionsListUtils.getPolicyExpenseReportOption({...props.report, selected: true}), - ]; - } else { - participants = _.map(participantAccountIDs, (accountID) => OptionsListUtils.getParticipantsOption({accountID, selected: true}, props.personalDetails)); - } - const payeePersonalDetails = props.personalDetails[reportAction.actorAccountID]; - const participantsExcludingPayee = _.filter(participants, (participant) => participant.accountID !== reportAction.actorAccountID); - - const isScanning = TransactionUtils.hasReceipt(props.transaction) && TransactionUtils.isReceiptBeingScanned(props.transaction); - const hasSmartScanFailed = TransactionUtils.hasReceipt(props.transaction) && props.transaction.receipt.state === CONST.IOU.RECEIPT_STATE.SCANFAILED; - const isEditingSplitBill = props.session.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(props.transaction); - - const { - amount: splitAmount, - currency: splitCurrency, - comment: splitComment, - merchant: splitMerchant, - created: splitCreated, - category: splitCategory, - tag: splitTag, - billable: splitBillable, - } = isEditingSplitBill && props.draftTransaction ? ReportUtils.getTransactionDetails(props.draftTransaction) : ReportUtils.getTransactionDetails(props.transaction); - - const onConfirm = useCallback( - () => IOU.completeSplitBill(reportID, reportAction, props.draftTransaction, props.session.accountID, props.session.email), - [reportID, reportAction, props.draftTransaction, props.session.accountID, props.session.email], - ); - - return ( - - - - - {isScanning && ( - - )} - {Boolean(participants.length) && ( - - )} - - - - ); -} - -SplitBillDetailsPage.propTypes = propTypes; -SplitBillDetailsPage.defaultProps = defaultProps; -SplitBillDetailsPage.displayName = 'SplitBillDetailsPage'; - -export default compose( - withReportAndReportActionOrNotFound, - withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, - }, - reportActions: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${route.params.reportID}`, - canEvict: false, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - session: { - key: ONYXKEYS.SESSION, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - transaction: { - key: ({route, reportActions}) => { - const reportAction = reportActions[`${route.params.reportActionID.toString()}`]; - return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(reportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, - }, - draftTransaction: { - key: ({route, reportActions}) => { - const reportAction = reportActions[`${route.params.reportActionID.toString()}`]; - return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${lodashGet(reportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, - }, - }), -)(SplitBillDetailsPage); diff --git a/src/pages/iou/SplitBillDetailsPage.tsx b/src/pages/iou/SplitBillDetailsPage.tsx index 742f6360e45b..ce55d4dea7e9 100644 --- a/src/pages/iou/SplitBillDetailsPage.tsx +++ b/src/pages/iou/SplitBillDetailsPage.tsx @@ -49,9 +49,9 @@ type SplitBillDetailsPageProps = WithReportAndReportActionOrNotFound & SplitBill function SplitBillDetailsPage({personalDetails, report, route, reportActions, transaction, draftTransaction, session}: SplitBillDetailsPageProps) { const styles = useThemeStyles(); - const {reportID} = report ?? {}; + const {reportID} = report ?? {reportID: ''}; const {translate} = useLocalize(); - const reportAction = reportActions?.[route.params.reportActionID] as (ReportActionBase & OriginalMessageIOU) | undefined; + const reportAction = reportActions?.[route.params.reportActionID] as ReportActionBase & OriginalMessageIOU; const participantAccountIDs = reportAction?.originalMessage.participantAccountIDs ?? []; // In case this is workspace split bill, we manually add the workspace as the second participant of the split bill @@ -59,21 +59,18 @@ function SplitBillDetailsPage({personalDetails, report, route, reportActions, tr let participants; if (ReportUtils.isPolicyExpenseChat(report)) { participants = [ - // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. - OptionsListUtils.getParticipantsOption({accountID: participantAccountIDs[0], selected: true}, personalDetails), + OptionsListUtils.getParticipantsOption({accountID: participantAccountIDs[0], selected: true, reportID: ''}, personalDetails), OptionsListUtils.getPolicyExpenseReportOption({...report, selected: true}), ]; } else { - // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. - participants = participantAccountIDs.map((accountID) => OptionsListUtils.getParticipantsOption({accountID, selected: true}, personalDetails)); + participants = participantAccountIDs.map((accountID) => OptionsListUtils.getParticipantsOption({accountID, selected: true, reportID: ''}, personalDetails)); } const payeePersonalDetails = personalDetails?.[reportAction?.actorAccountID ?? 0]; - // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. const participantsExcludingPayee = participants.filter((participant) => participant.accountID !== reportAction?.actorAccountID); const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); const hasSmartScanFailed = TransactionUtils.hasReceipt(transaction) && transaction?.receipt?.state === CONST.IOU.RECEIPT_STATE.SCANFAILED; - const isEditingSplitBill = session?.accountID === reportAction?.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction ?? ({} as Transaction)); + const isEditingSplitBill = session?.accountID === reportAction?.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction ?? undefined); const { amount: splitAmount, @@ -83,16 +80,17 @@ function SplitBillDetailsPage({personalDetails, report, route, reportActions, tr created: splitCreated, category: splitCategory, tag: splitTag, - } = ReportUtils.getTransactionDetails(isEditingSplitBill && draftTransaction ? draftTransaction : transaction) ?? {}; + billable: splitBillable, + } = ReportUtils.getTransactionDetails((isEditingSplitBill && draftTransaction) || transaction) ?? {}; const onConfirm = useCallback( - () => IOU.completeSplitBill(Number(reportID), reportAction ?? {}, draftTransaction ?? {}, session?.accountID ?? -1, session?.email ?? ''), + () => IOU.completeSplitBill(reportID, reportAction, draftTransaction ?? undefined, session?.accountID ?? 0, session?.email ?? ''), [reportID, reportAction, draftTransaction, session?.accountID, session?.email], ); return ( - + {isScanning && ( @@ -114,6 +112,7 @@ function SplitBillDetailsPage({personalDetails, report, route, reportActions, tr iouMerchant={splitMerchant} iouCategory={splitCategory} iouTag={splitTag} + iouIsBillable={splitBillable} iouType={CONST.IOU.TYPE.SPLIT} isReadOnly={!isEditingSplitBill} shouldShowSmartScanFields @@ -124,10 +123,10 @@ function SplitBillDetailsPage({personalDetails, report, route, reportActions, tr hasSmartScanFailed={hasSmartScanFailed} reportID={reportID} reportActionID={reportAction?.reportActionID} - transaction={isEditingSplitBill ? draftTransaction ?? transaction : transaction} + transaction={isEditingSplitBill ? draftTransaction || transaction : transaction} onConfirm={onConfirm} isPolicyExpenseChat={ReportUtils.isPolicyExpenseChat(report)} - policyID={ReportUtils.isPolicyExpenseChat(report) && report?.policyID} + policyID={ReportUtils.isPolicyExpenseChat(report) ? report?.policyID : null} /> )} diff --git a/src/pages/iou/steps/NewRequestAmountPage.tsx b/src/pages/iou/steps/NewRequestAmountPage.tsx index 4d530c050c22..0224c912e443 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.tsx +++ b/src/pages/iou/steps/NewRequestAmountPage.tsx @@ -118,7 +118,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}: NewRequestAmoun return; } - IOU.navigateToNextPage(iou ?? {}, iouType, report ?? {}); + IOU.navigateToNextPage(iou, iouType, report ?? undefined); }; const content = ( From b25f22f7e3c3e64299715c19b8ea43438f60bf5a Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 9 Feb 2024 17:32:22 +0000 Subject: [PATCH 094/699] chore: apply pull request suggestions --- src/pages/iou/NewDistanceRequestPage.tsx | 2 +- src/pages/iou/steps/NewRequestAmountPage.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/NewDistanceRequestPage.tsx b/src/pages/iou/NewDistanceRequestPage.tsx index 49b918dd3b30..1b35d9646daf 100644 --- a/src/pages/iou/NewDistanceRequestPage.tsx +++ b/src/pages/iou/NewDistanceRequestPage.tsx @@ -24,7 +24,7 @@ type NewDistanceRequestPageProps = NewDistanceRequestPageOnyxProps & StackScreen // This component is responsible for getting the transactionID from the IOU key, or creating the transaction if it doesn't exist yet, and then passing the transactionID. // You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that DistanceRequest can subscribe to the transaction. function NewDistanceRequestPage({iou, report, route}: NewDistanceRequestPageProps) { - const iouType = route.params.iouType ?? 'request'; + const iouType = route.params.iouType; const isEditingNewRequest = Navigation.getActiveRoute().includes('address'); useEffect(() => { diff --git a/src/pages/iou/steps/NewRequestAmountPage.tsx b/src/pages/iou/steps/NewRequestAmountPage.tsx index e1e406263825..8b63b67b0808 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.tsx +++ b/src/pages/iou/steps/NewRequestAmountPage.tsx @@ -24,6 +24,8 @@ import type SCREENS from '@src/SCREENS'; import type {IOU as IOUType, Report} from '@src/types/onyx'; import MoneyRequestAmountForm from './MoneyRequestAmountForm'; +type NavigateToNextPageOptions = {amount: string}; + type NewRequestAmountPageOnyxProps = { /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: OnyxEntry; @@ -113,7 +115,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}: NewRequestAmoun Navigation.navigate(ROUTES.MONEY_REQUEST_CURRENCY.getRoute(iouType, reportID, currency, activeRoute)); }; - const navigateToNextPage = ({amount}: {amount: string}) => { + const navigateToNextPage = ({amount}: NavigateToNextPageOptions) => { const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); IOU.setMoneyRequestAmount(amountInSmallestCurrencyUnits); IOU.setMoneyRequestCurrency(currency); From 9eebd1bed31a1395be1cf6f5f6d10e1877e463db Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 9 Feb 2024 17:45:47 +0000 Subject: [PATCH 095/699] fix: typing issues --- src/libs/Navigation/OnyxTabNavigator.tsx | 10 ++++++---- src/libs/actions/Tab.ts | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libs/Navigation/OnyxTabNavigator.tsx b/src/libs/Navigation/OnyxTabNavigator.tsx index 2ae3414956a8..c0ac1b20920b 100644 --- a/src/libs/Navigation/OnyxTabNavigator.tsx +++ b/src/libs/Navigation/OnyxTabNavigator.tsx @@ -4,13 +4,15 @@ import type {EventMapCore, NavigationState, ScreenListeners} from '@react-naviga import React from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import Tab from '@userActions/Tab'; +import type CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import {defaultScreenOptions} from './OnyxTabNavigatorConfig'; type OnyxTabNavigatorOnyxProps = { - selectedTab: OnyxEntry; + selectedTab: OnyxEntry>; }; type OnyxTabNavigatorProps = OnyxTabNavigatorOnyxProps & @@ -19,7 +21,7 @@ type OnyxTabNavigatorProps = OnyxTabNavigatorOnyxProps & id: string; /** Name of the selected tab */ - selectedTab?: string; + selectedTab?: ValueOf; /** A function triggered when a tab has been selected */ onTabSelected?: (newIouType: string) => void; @@ -32,7 +34,7 @@ export const TopTab = createMaterialTopTabNavigator(); // This takes all the same props as MaterialTopTabsNavigator: https://reactnavigation.org/docs/material-top-tab-navigator/#props, // except ID is now required, and it gets a `selectedTab` from Onyx -function OnyxTabNavigator({id, selectedTab = '', children, onTabSelected = () => {}, screenListeners, ...rest}: OnyxTabNavigatorProps) { +function OnyxTabNavigator({id, selectedTab, children, onTabSelected = () => {}, screenListeners, ...rest}: OnyxTabNavigatorProps) { return ( const state = event.data.state; const index = state.index; const routeNames = state.routeNames; - Tab.setSelectedTab(id, routeNames[index]); + Tab.setSelectedTab(id, routeNames[index] as ValueOf); onTabSelected(routeNames[index]); }, ...(screenListeners ?? {}), diff --git a/src/libs/actions/Tab.ts b/src/libs/actions/Tab.ts index a210cef36c73..8f1f647bd982 100644 --- a/src/libs/actions/Tab.ts +++ b/src/libs/actions/Tab.ts @@ -1,10 +1,12 @@ import Onyx from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; /** * Sets the selected tab for a given tab ID */ -function setSelectedTab(id: string, index: string) { +function setSelectedTab(id: string, index: ValueOf) { Onyx.merge(`${ONYXKEYS.COLLECTION.SELECTED_TAB}${id}`, index); } From 353b74099d6162013136351775bd00a9f155c55a Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Fri, 9 Feb 2024 12:23:18 -0800 Subject: [PATCH 096/699] update report actions items to retrieve transaction thread report details --- src/pages/home/report/ReportActionItem.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 209e0c00bbbe..0f1a729794fb 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -77,6 +77,7 @@ import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import reportActionPropTypes from './reportActionPropTypes'; import ReportAttachmentsContext from './ReportAttachmentsContext'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; const propTypes = { ...windowDimensionsPropTypes, @@ -113,6 +114,9 @@ const propTypes = { /** IOU report for this action, if any */ iouReport: reportPropTypes, + /** Single transaction thread associated with the report, if any */ + transactionThreadReport: reportPropTypes, + /** Flag to show, hide the thread divider line */ shouldHideThreadDividerLine: PropTypes.bool, @@ -132,6 +136,7 @@ const defaultProps = { emojiReactions: {}, shouldShowSubscriptAvatar: false, iouReport: undefined, + transactionThreadReport: {}, shouldHideThreadDividerLine: false, userWallet: {}, parentReportActions: {}, @@ -664,6 +669,15 @@ function ReportActionItem(props) { policyReportFields={_.values(props.policyReportFields)} shouldShowHorizontalRule={!props.shouldHideThreadDividerLine} /> + {!isEmptyObject(props.transactionThreadReport) && ( + + + + )} ); } @@ -813,6 +827,13 @@ export default compose( }, initialValue: {}, }, + transactionThreadReport: { + key: ({report}) => { + const transactionThreadReportID = ReportUtils.isOneTransactionReport(report) ? ReportUtils.getOneTransactionThreadReportID(report) : ''; + return transactionThreadReportID ? `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}` : {}; + }, + initialValue: {}, + }, policyReportFields: { key: ({report}) => (report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID}` : undefined), initialValue: [], @@ -847,6 +868,7 @@ export default compose( _.isEqual(prevProps.report.pendingFields, nextProps.report.pendingFields) && _.isEqual(prevProps.report.isDeletedParentAction, nextProps.report.isDeletedParentAction) && _.isEqual(prevProps.report.errorFields, nextProps.report.errorFields) && + _.isEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && lodashGet(prevProps.report, 'statusNum') === lodashGet(nextProps.report, 'statusNum') && lodashGet(prevProps.report, 'stateNum') === lodashGet(nextProps.report, 'stateNum') && lodashGet(prevProps.report, 'parentReportID') === lodashGet(nextProps.report, 'parentReportID') && From 66f498b67d2eff3ff47442ee64068518ef531c28 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Fri, 9 Feb 2024 12:40:43 -0800 Subject: [PATCH 097/699] use undefined instead of {} --- src/pages/home/report/ReportActionItem.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 0f1a729794fb..47b0650c9869 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -136,7 +136,7 @@ const defaultProps = { emojiReactions: {}, shouldShowSubscriptAvatar: false, iouReport: undefined, - transactionThreadReport: {}, + transactionThreadReport: undefined, shouldHideThreadDividerLine: false, userWallet: {}, parentReportActions: {}, @@ -669,7 +669,7 @@ function ReportActionItem(props) { policyReportFields={_.values(props.policyReportFields)} shouldShowHorizontalRule={!props.shouldHideThreadDividerLine} /> - {!isEmptyObject(props.transactionThreadReport) && ( + {props.transactionThreadReport && !isEmptyObject(props.transactionThreadReport) && ( { const transactionThreadReportID = ReportUtils.isOneTransactionReport(report) ? ReportUtils.getOneTransactionThreadReportID(report) : ''; - return transactionThreadReportID ? `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}` : {}; + return transactionThreadReportID ? `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}` : undefined; }, initialValue: {}, }, From de66987b70c4e67823775ecc39dc936ba1685320 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Fri, 9 Feb 2024 14:57:50 -0800 Subject: [PATCH 098/699] check prevProps --- src/pages/home/ReportScreen.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index d37be839d8cc..b7e05af217a2 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -670,6 +670,7 @@ export default compose( (prevProps, nextProps) => prevProps.isSidebarLoaded === nextProps.isSidebarLoaded && _.isEqual(prevProps.reportActions, nextProps.reportActions) && + _.isEqual(prevProps.transactionThreadReportActions, nextProps.transactionThreadReportActions) && _.isEqual(prevProps.reportMetadata, nextProps.reportMetadata) && prevProps.isComposerFullSize === nextProps.isComposerFullSize && _.isEqual(prevProps.betas, nextProps.betas) && From 264dbbcfb26a46f49dad8924ebe1d0d48da1dead Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 16 Feb 2024 00:18:09 +0000 Subject: [PATCH 099/699] chore: apply pull request feedback --- src/pages/iou/steps/NewRequestAmountPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/steps/NewRequestAmountPage.tsx b/src/pages/iou/steps/NewRequestAmountPage.tsx index 8b63b67b0808..8f0085230233 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.tsx +++ b/src/pages/iou/steps/NewRequestAmountPage.tsx @@ -139,7 +139,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}: NewRequestAmoun }} onCurrencyButtonPress={navigateToCurrencySelectionPage} onSubmitButtonPress={navigateToNextPage} - selectedTab={selectedTab} + selectedTab={selectedTab ?? CONST.TAB_REQUEST.MANUAL} /> ); From 8b9ff58d8d120f31934862eaa5a39420472b5245 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 19 Feb 2024 15:13:58 +0700 Subject: [PATCH 100/699] fix ts check --- src/libs/Navigation/Navigation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 8009c963ade7..4e56586bf1f5 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -94,7 +94,7 @@ function getActiveRouteIndex(stateOrRoute: StateOrRoute, index?: number): number function parseHybridAppUrl(url: HybridAppRoute | Route): Route { switch (url) { case HYBRID_APP_ROUTES.MONEY_REQUEST_CREATE: - return ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.REQUEST, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, ReportUtils.generateReportID()); + return ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.REQUEST, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, ReportUtils.generateReportID()); default: return url; } From 68ef4f53cc8e9420bada67903b07ca9fab37cd14 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 19 Feb 2024 09:26:07 +0100 Subject: [PATCH 101/699] fix bad merge for IOURequestStepAmount --- src/pages/iou/request/step/IOURequestStepAmount.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index 7861cbc041e5..c360f837a0c3 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -1,5 +1,4 @@ import {useFocusEffect} from '@react-navigation/native'; -import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef} from 'react'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -29,10 +28,6 @@ const propTypes = { /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, - /* Onyx Props */ - /** Collection of tax rates attached to a policy */ - policyTaxRates: taxPropTypes, - /** The policy of the report */ policy: PropTypes.shape({ /** @@ -69,9 +64,6 @@ function IOURequestStepAmount({ const originalCurrency = useRef(null); const iouRequestType = getRequestType(transaction); - const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)); - const isTaxTrackingEnabled = isPolicyExpenseChat && lodashGet(policy, 'tax.trackingEnabled', policy.isTaxTrackingEnabled); - useFocusEffect( useCallback(() => { focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION); From a105896eee3873b173f38583a5ab64495dd9bdc1 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 19 Feb 2024 09:36:32 +0100 Subject: [PATCH 102/699] fix bad merge for EditRequestPage --- src/pages/EditRequestPage.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index ba07afae7388..77d4f8369403 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -89,10 +89,22 @@ const defaultProps = { policyTaxRates: {}, }; -function EditRequestPage({report, route, policy, policyCategories, policyTags, parentReportActions, transaction}) { +const getTaxAmount = (transactionAmount, transactionTaxCode, policyTaxRates) => { + const percentage = (transactionTaxCode ? policyTaxRates.taxes[transactionTaxCode].value : policyTaxRates.defaultValue) || ''; + return CurrencyUtils.convertToBackendAmount(Number.parseFloat(TransactionUtils.calculateTaxAmount(percentage, transactionAmount))); +}; + +function EditRequestPage({report, route, policy, policyTaxRates, policyCategories, policyTags, parentReportActions, transaction}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); - const {amount: transactionAmount, currency: transactionCurrency, category: transactionCategory, tag: transactionTag} = ReportUtils.getTransactionDetails(transaction); + const { + amount: transactionAmount, + taxAmount: transactionTaxAmount, + taxCode: transactionTaxCode, + currency: transactionCurrency, + category: transactionCategory, + tag: transactionTag, + } = ReportUtils.getTransactionDetails(transaction); const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; const fieldToEdit = lodashGet(route, ['params', 'field'], ''); @@ -102,6 +114,8 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex); const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); + const taxRateTitle = TransactionUtils.getTaxName(policyTaxRates.taxes, transactionTaxCode); + // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); From 58af4ad224cde512a00715f21ae5732ba0aff4fd Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 19 Feb 2024 09:54:25 +0100 Subject: [PATCH 103/699] use trackingEnabled for chrcking if tax tracking is enabled --- src/libs/PolicyUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 6b1c63785ea4..12b3d6952183 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -215,7 +215,7 @@ function isPaidGroupPolicy(policy: OnyxEntry | EmptyObject): boolean { } function isTaxPolicyEnabled(isPolicyExpenseChat: boolean, policy: OnyxEntry): boolean { - return (isPolicyExpenseChat && policy?.isTaxTrackingEnabled) ?? false; + return (isPolicyExpenseChat && (policy?.tax?.trackingEnabled || policy?.isTaxTrackingEnabled)) ?? false; } /** From 81fb82ce80bbb46da9508ce546b827918730ccb7 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 19 Feb 2024 10:08:18 +0100 Subject: [PATCH 104/699] fix prettier --- src/types/onyx/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 28b26ed77c3e..59cabbe020ee 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -34,8 +34,8 @@ import type {PolicyCategories, PolicyCategory} from './PolicyCategory'; import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type {PolicyReportField, PolicyReportFields} from './PolicyReportField'; -import type {PolicyTaxRates, TaxRate, TaxRates} from './PolicyTaxRates'; import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; +import type {PolicyTaxRates, TaxRate, TaxRates} from './PolicyTaxRates'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; From 88c091434482a37637c015ebdbae13994b8a5452 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 19 Feb 2024 10:18:01 +0100 Subject: [PATCH 105/699] fix lint --- src/libs/PolicyUtils.ts | 2 +- src/pages/EditRequestPage.js | 10 ---------- .../iou/request/step/IOURequestStepAmount.js | 16 ---------------- 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 12b3d6952183..0a6cc9e7a35a 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -215,7 +215,7 @@ function isPaidGroupPolicy(policy: OnyxEntry | EmptyObject): boolean { } function isTaxPolicyEnabled(isPolicyExpenseChat: boolean, policy: OnyxEntry): boolean { - return (isPolicyExpenseChat && (policy?.tax?.trackingEnabled || policy?.isTaxTrackingEnabled)) ?? false; + return (isPolicyExpenseChat && (policy?.tax?.trackingEnabled ?? policy?.isTaxTrackingEnabled)) ?? false; } /** diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 77d4f8369403..3653e37a6555 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -67,12 +67,6 @@ const propTypes = { /** Transaction that stores the request data */ transaction: transactionPropTypes, - /* Onyx Props */ - /** The policy of the report */ - policy: PropTypes.shape({ - /** Is Tax tracking Enabled */ - isTaxTrackingEnabled: PropTypes.bool, - }), /** Collection of tax rates attached to a policy */ policyTaxRates: taxPropTypes, @@ -85,7 +79,6 @@ const defaultProps = { policyTags: {}, parentReportActions: {}, transaction: {}, - policy: {}, policyTaxRates: {}, }; @@ -308,9 +301,6 @@ export default compose( policyCategories: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`, }, - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, - }, policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index c360f837a0c3..0bc51a517466 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -1,5 +1,4 @@ import {useFocusEffect} from '@react-navigation/native'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef} from 'react'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; @@ -27,21 +26,6 @@ const propTypes = { /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, - - /** The policy of the report */ - policy: PropTypes.shape({ - /** - * Whether or not the policy has tax tracking enabled - * - * @deprecated - use tax.trackingEnabled instead - */ - isTaxTrackingEnabled: PropTypes.bool, - - /** Whether or not the policy has tax tracking enabled */ - tax: PropTypes.shape({ - trackingEnabled: PropTypes.bool, - }), - }), }; const defaultProps = { From 0381cb36ce2533bd8772e9cd3b955e327e43d438 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 19 Feb 2024 12:17:58 +0100 Subject: [PATCH 106/699] fix typecheck --- src/libs/actions/IOU.ts | 22 ++++++++++++++++++---- src/pages/EditRequestPage.js | 4 ++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 2decc65456f8..7fe33ec54881 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1349,20 +1349,34 @@ function updateMoneyRequestTag( } /** Updates the created tax amount of a money request */ -function updateMoneyRequestTaxAmount(transactionID: string, optimisticReportActionID: string, taxAmount: number) { +function updateMoneyRequestTaxAmount( + transactionID: string, + optimisticReportActionID: string, + taxAmount: number, + policy: OnyxEntry, + policyTagList: OnyxEntry, + policyCategories: OnyxEntry, +) { const transactionChanges = { taxAmount, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, true); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, policy, policyTagList, policyCategories, true); API.write('UpdateMoneyRequestTaxAmount', params, onyxData); } /** Updates the created tax rate of a money request */ -function updateMoneyRequestTaxRate(transactionID: string, optimisticReportActionID: string, taxCode: string) { +function updateMoneyRequestTaxRate( + transactionID: string, + optimisticReportActionID: string, + taxCode: string, + policy: OnyxEntry, + policyTagList: OnyxEntry, + policyCategories: OnyxEntry, +) { const transactionChanges = { taxCode, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, true); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, policy, policyTagList, policyCategories, true); API.write('UpdateMoneyRequestTaxRate', params, onyxData); } diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 3653e37a6555..c6e239d8c766 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -136,13 +136,13 @@ function EditRequestPage({report, route, policy, policyTaxRates, policyCategorie const updateTaxAmount = (transactionChanges) => { const newTaxAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(transactionChanges.amount)); - IOU.updateMoneyRequestTaxAmount(transaction.transactionID, report.reportID, newTaxAmount); + IOU.updateMoneyRequestTaxAmount(transaction.transactionID, report.reportID, newTaxAmount, policy, policyTags, policyCategories); Navigation.dismissModal(report.reportID); }; const updateTaxRate = (transactionChanges) => { const newTaxCode = transactionChanges.data.code; - IOU.updateMoneyRequestTaxRate(transaction.transactionID, report.reportID, newTaxCode); + IOU.updateMoneyRequestTaxRate(transaction.transactionID, report.reportID, newTaxCode, policy, policyTags, policyCategories); Navigation.dismissModal(report.reportID); }; From ec0eb6f7ed0d3a0cd0884cd7999fb91472702797 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Tue, 20 Feb 2024 03:20:43 +0530 Subject: [PATCH 107/699] Comment typo fix src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 7c0320244620..d5221c98b046 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -68,7 +68,7 @@ function StateSelectionPage() { Navigation.goBack(appendParam(backTo, 'state', option.value)); } else { // Otherwise, navigate to the specific route defined in "backTo" with a country parameter - // @ts-expect-error Navigation.navigate does take a paraml + // @ts-expect-error Navigation.navigate does take a param Navigation.navigate(appendParam(backTo, 'state', option.value)); } }, From 07f6f2160d2670a8155708a88e38a291f6cb6ca7 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Tue, 20 Feb 2024 03:21:40 +0530 Subject: [PATCH 108/699] Comment formatting src/pages/settings/Profile/PersonalDetails/AddressPage.js Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/pages/settings/Profile/PersonalDetails/AddressPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index 95b006828c39..87960d1f6687 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -65,9 +65,9 @@ function AddressPage({privatePersonalDetails, route}) { usePrivatePersonalDetails(); const {translate} = useLocalize(); const address = useMemo(() => lodashGet(privatePersonalDetails, 'address') || {}, [privatePersonalDetails]); - const countryFromUrlTemp = lodashGet(route, 'params.country'); - // check if country is valid + + // Check if country is valid const countryFromUrl = lodashGet(CONST.ALL_COUNTRIES, countryFromUrlTemp) ? countryFromUrlTemp : ''; const stateFromUrl = useGeographicalStateFromRoute(); From 1bf4c04caf038e6cbb1957bb23d0f13c3b88ca71 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Mon, 19 Feb 2024 20:08:01 -0800 Subject: [PATCH 109/699] simplify transactionThreadReportActions display --- src/libs/ReportUtils.ts | 2 +- src/pages/home/ReportScreen.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6d094087b2fa..0042f7da11d5 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1218,7 +1218,7 @@ function getOneTransactionThreadReportID(reportOrID: OnyxEntry | string) // Get all IOU report actions for the report. const iouReportAction = ReportActionsUtils.getIOUReportActions(report?.reportID ?? '')?.find(reportAction => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.childReportID); - return String(iouReportAction?.childReportID) ?? '0'; + return iouReportAction ? String(iouReportAction.childReportID) : '0' } /** diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 663cd971f71e..f281843a7052 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -268,7 +268,6 @@ function ReportScreen({ const {reportPendingAction, reportErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report); const screenWrapperStyle = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}]; const isEmptyChat = useMemo(() => _.isEmpty(reportActions), [reportActions]); - // There are no reportActions at all to display and we are still in the process of loading the next set of actions. const isLoadingInitialReportActions = _.isEmpty(reportActions) && reportMetadata.isLoadingInitialReportActions; const isOptimisticDelete = lodashGet(report, 'statusNum') === CONST.REPORT.STATUS_NUM.CLOSED; const shouldHideReport = !ReportUtils.canAccessReport(report, policies, betas); @@ -285,7 +284,6 @@ function ReportScreen({ const policy = policies[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`] || {}; const isTopMostReportId = currentReportID === getReportID(route); const didSubscribeToReportLeavingEvents = useRef(false); - const isOneTransactionReport = ReportUtils.isOneTransactionReport(report); useEffect(() => { if (!report.reportID || shouldHideReport) { @@ -565,7 +563,7 @@ function ReportScreen({ > {isReportReadyForDisplay && !isLoadingInitialReportActions && !isLoading && ( ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), }, transactionThreadReportActions: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${ReportUtils.getOneTransactionThreadReportID(getReportID(route))}`, + key: ({route}) => { + const reportID = getReportID(route); + const transactionThreadReportID = reportID && ReportUtils.isOneTransactionReport(reportID) ? ReportUtils.getOneTransactionThreadReportID(reportID) : '0'; + return `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}` + }, canEvict: false, selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), }, From 877740b9160d380890798ac2a6e42e52f2bc7bea Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 20 Feb 2024 17:01:43 +0530 Subject: [PATCH 110/699] Fix TS errors --- src/ROUTES.ts | 17 +++++++---------- src/components/StateSelector.tsx | 1 - .../PersonalDetails/StateSelectionPage.tsx | 11 ++++++----- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 3a4b92e9e529..1cf8eef0f4ab 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -133,16 +133,13 @@ const ROUTES = { }, SETTINGS_ADDRESS_STATE: { route: 'settings/profile/address/state', - getRoute: (state: string, backTo?: string, label?: string) => { - let route = `settings/profile/address/state?state=${state}`; - if (backTo) { - route += `&backTo=${encodeURIComponent(backTo)}`; - } - if (label) { - route += `&label=${encodeURIComponent(label)}`; - } - return route; - }, + + getRoute: (state?: string, backTo?: string, label?: string) => + `${getUrlWithBackToParam(`settings/profile/address/state${state ? `?state=${encodeURIComponent(state)}` : ''}`, backTo)}${ + // Nullish operator ?? doesnt seem to be a replacement for || here + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + label ? `${backTo || state ? '&' : '?'}label=${encodeURIComponent(label)}` : '' + }` as const, }, SETTINGS_CONTACT_METHODS: { route: 'settings/profile/contact-methods', diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 79c086057f88..9ddf2573f857 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -83,7 +83,6 @@ function StateSelector({errorText, shouldUseStateFromUrl = true, value: stateCod description={label || translate('common.state')} onPress={() => { const activeRoute = Navigation.getActiveRoute(); - // @ts-expect-error Navigation.navigate does take a param Navigation.navigate(ROUTES.SETTINGS_ADDRESS_STATE.getRoute(stateCode, activeRoute, label)); }} wrapperStyle={wrapperStyle} diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index d5221c98b046..532f4d3f97e4 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -12,6 +12,7 @@ import searchCountryOptions from '@libs/searchCountryOptions'; import type {CountryData} from '@libs/searchCountryOptions'; import StringUtils from '@libs/StringUtils'; import {appendParam} from '@libs/Url'; +import type {Route} from '@src/ROUTES'; type State = keyof typeof COMMON_CONST.STATES; @@ -64,12 +65,12 @@ function StateSelectionPage() { Navigation.goBack(); } else if (!_.isEmpty(backTo) && navigation.getState().routes.length === 1) { // If "backTo" is not empty and there is only one route, go back to the specific route defined in "backTo" with a country parameter - // @ts-expect-error Navigation.goBack does take a param - Navigation.goBack(appendParam(backTo, 'state', option.value)); - } else { + Navigation.goBack(appendParam(backTo, 'state', option.value) as Route); + } else if (!_.isEmpty(backTo)) { // Otherwise, navigate to the specific route defined in "backTo" with a country parameter - // @ts-expect-error Navigation.navigate does take a param - Navigation.navigate(appendParam(backTo, 'state', option.value)); + Navigation.navigate(appendParam(backTo, 'state', option.value) as Route); + } else { + Navigation.goBack(); } }, [navigation, params?.backTo], From b479e69163b26ba6cb8b5f9e339e49d45f51352a Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 14:41:36 +0100 Subject: [PATCH 111/699] use taxRates from policy key for MoneyTemporaryForRefactorRequestConfirmationList --- ...TemporaryForRefactorRequestConfirmationList.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 0be77a201f1f..5f4abfec7794 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -259,6 +259,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const {unit, rate, currency} = mileageRate; const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; + const taxRates = lodashGet(policy, 'taxRates', {}); // A flag for showing the categories field const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(_.values(policyCategories))); @@ -293,7 +294,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode); - const taxRateTitle = TransactionUtils.getDefaultTaxName(policyTaxRates, transaction); + const taxRateTitle = TransactionUtils.getDefaultTaxName(taxRates, transaction); const previousTransactionTaxAmount = usePrevious(transaction.taxAmount); @@ -359,13 +360,13 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // Calculate and set tax amount in transaction draft useEffect(() => { - const taxAmount = getTaxAmount(transaction, policyTaxRates.defaultValue); + const taxAmount = getTaxAmount(transaction, taxRates.defaultValue); const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); if (previousTransactionTaxAmount !== transaction.taxAmount && amountInSmallestCurrencyUnits !== transaction.taxAmount) { return; } IOU.setMoneyRequestTaxAmount(transaction.transactionID, amountInSmallestCurrencyUnits); - }, [policyTaxRates.defaultValue, transaction, previousTransactionTaxAmount]); + }, [taxRates.defaultValue, transaction, previousTransactionTaxAmount]); /** * Returns the participants with amount @@ -800,10 +801,10 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ { item: ( Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} @@ -817,10 +818,10 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ { item: ( Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} From c9150473c7c9e77e0a3ec936d652eadbebf2257c Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 14:45:13 +0100 Subject: [PATCH 112/699] use taxRates from policy key for MoneyTemporaryForRefactorRequestConfirmationList --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 5f4abfec7794..ee1975220a24 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -162,10 +162,6 @@ const propTypes = { /** Collection of tags attached to a policy */ policyTags: tagPropTypes, - /* Onyx Props */ - /** Collection of tax rates attached to a policy */ - policyTaxRates: taxPropTypes, - /** Transaction that represents the money request */ transaction: transactionPropTypes, }; @@ -199,7 +195,6 @@ const defaultProps = { isDistanceRequest: false, shouldShowSmartScanFields: true, isPolicyExpenseChat: false, - policyTaxRates: {}, }; const getTaxAmount = (transaction, defaultTaxValue) => { @@ -245,7 +240,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, - policyTaxRates, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -950,8 +944,5 @@ export default compose( policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, - policyTaxRates: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, - }, }), )(MoneyTemporaryForRefactorRequestConfirmationList); From 4e57d4ae08c18fb59b55fc4501b4c7ad6cfb57eb Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 14:49:49 +0100 Subject: [PATCH 113/699] add taxRates propTypes to policy for MoneyTemporaryForRefactorRequestConfirmationList --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index ee1975220a24..c9afa52be850 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -162,6 +162,10 @@ const propTypes = { /** Collection of tags attached to a policy */ policyTags: tagPropTypes, + /* Onyx Props */ + /** Collection of tax rates attached to a policy */ + policyTaxRates: taxPropTypes, + /** Transaction that represents the money request */ transaction: transactionPropTypes, }; From 17281f6829da70f9dc828d2b0def2c5f3105868a Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 14:50:44 +0100 Subject: [PATCH 114/699] remove unused Fragment import for MoneyTemporaryForRefactorRequestConfirmation --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index c9afa52be850..1224c12255c6 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -2,7 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import {format} from 'date-fns'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; From 9b3ac1296a0ce0c01eae5b9e6b2dc65546e8fd8b Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 14:52:22 +0100 Subject: [PATCH 115/699] update taxAmount Description for MoneyTemporaryForRefactorRequestConfirmation --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 1224c12255c6..094e2d58066e 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -819,7 +819,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ key={`${taxRates.name}${formattedTaxAmount}`} shouldShowRightIcon={!isReadOnly} title={formattedTaxAmount} - description={taxRates.name} + description={translate('iou.taxAmount')} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} From af4783aa91236784e84eb133c691f40b6bfef9e8 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 15:01:55 +0100 Subject: [PATCH 116/699] use taxRates from policy key for MoneyRequestRequestConfirmationList --- src/components/MoneyRequestConfirmationList.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index c5fea084674d..13a118d416a4 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -229,6 +229,7 @@ function MoneyRequestConfirmationList(props) { const {unit, rate, currency} = props.mileageRate; const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = props.isDistanceRequest && props.iouAmount === 0; + const taxRates = lodashGet(props.policy, 'taxRates', {}); // A flag for showing the categories field const shouldShowCategories = props.isPolicyExpenseChat && (props.iouCategory || OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories))); @@ -262,7 +263,7 @@ function MoneyRequestConfirmationList(props) { props.isDistanceRequest ? currency : props.iouCurrencyCode, ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transaction.taxAmount, props.iouCurrencyCode); - const taxRateTitle = TransactionUtils.getDefaultTaxName(props.policyTaxRates, transaction); + const taxRateTitle = TransactionUtils.getDefaultTaxName(taxRates, transaction); const isFocused = useIsFocused(); const [formError, setFormError] = useState(''); @@ -812,7 +813,7 @@ function MoneyRequestConfirmationList(props) { From dbd68de9d793cd328281efa0a357d9f6535ca315 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 15:03:46 +0100 Subject: [PATCH 117/699] remove policyTaxRates props and ONYX key for MoneyRequestConfirmationList --- src/components/MoneyRequestConfirmationList.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 13a118d416a4..a71c8bc96b2c 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -167,10 +167,6 @@ const propTypes = { /** Collection of tags attached to a policy */ policyTags: tagPropTypes, - /* Onyx Props */ - /** Collection of tax rates attached to a policy */ - policyTaxRates: taxPropTypes, - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: iouPropTypes, }; @@ -207,7 +203,6 @@ const defaultProps = { shouldShowSmartScanFields: true, isPolicyExpenseChat: false, iou: iouDefaultProps, - policyTaxRates: {}, }; function MoneyRequestConfirmationList(props) { @@ -882,9 +877,6 @@ export default compose( policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, - policyTaxRates: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, - }, iou: { key: ONYXKEYS.IOU, }, From dfb486a9685f3d9ef279df6a51799062ef4204b4 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 15:24:43 +0100 Subject: [PATCH 118/699] update taxRates propTypes to policy for MoneyTemporaryForRefactorRequestConfirmationList --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 094e2d58066e..3e4435d6c4fc 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -25,6 +25,7 @@ import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; +import {policyPropTypes} from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -43,7 +44,6 @@ import ReceiptEmptyState from './ReceiptEmptyState'; import SettlementButton from './SettlementButton'; import Switch from './Switch'; import tagPropTypes from './tagPropTypes'; -import taxPropTypes from './taxPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; @@ -163,8 +163,8 @@ const propTypes = { policyTags: tagPropTypes, /* Onyx Props */ - /** Collection of tax rates attached to a policy */ - policyTaxRates: taxPropTypes, + /** The policy of the report */ + policy: policyPropTypes.policy, /** Transaction that represents the money request */ transaction: transactionPropTypes, @@ -193,6 +193,7 @@ const defaultProps = { listStyles: [], policyCategories: {}, policyTags: {}, + policy: {}, transactionID: '', transaction: {}, mileageRate: {unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: 0, currency: 'USD'}, From e252453fad3d53e756cf3afba491dc57747b72ba Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 15:26:37 +0100 Subject: [PATCH 119/699] update taxRates propTypes to policy for MoneyRequestConfirmationList --- src/components/MoneyRequestConfirmationList.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index a71c8bc96b2c..1587a4ace6f0 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -25,6 +25,7 @@ import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; +import {policyPropTypes} from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -42,7 +43,6 @@ import SettlementButton from './SettlementButton'; import ShowMoreButton from './ShowMoreButton'; import Switch from './Switch'; import tagPropTypes from './tagPropTypes'; -import taxPropTypes from './taxPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; @@ -167,6 +167,10 @@ const propTypes = { /** Collection of tags attached to a policy */ policyTags: tagPropTypes, + /* Onyx Props */ + /** The policy of the report */ + policy: policyPropTypes.policy, + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: iouPropTypes, }; From a811a81bf818fcee01d3ba5255cddfd225597e88 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 15:46:36 +0100 Subject: [PATCH 120/699] add taxRates types to policy --- src/types/onyx/Policy.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 7d4c08374b81..37ce28f93abc 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1,5 +1,6 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +import type {PolicyTaxRates} from '@src/types/onyx/PolicyTaxRates'; import type * as OnyxCommon from './OnyxCommon'; type Unit = 'mi' | 'km'; @@ -171,6 +172,9 @@ type Policy = { trackingEnabled: boolean; }; + /** Collection of tax rates attached to a policy */ + taxRates?: PolicyTaxRates; + /** ReportID of the admins room for this workspace */ chatReportIDAdmins?: number; From b4e61f4cfc3bb004cd8a48080d50bc13bcabf70a Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 15:46:48 +0100 Subject: [PATCH 121/699] use taxRates from policy key for MoneyRequestView --- src/components/ReportActionItem/MoneyRequestView.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 2125610a3ea8..147d8e6ea31a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -120,11 +120,11 @@ function MoneyRequestView({ const isCardTransaction = TransactionUtils.isCardTransaction(transaction); const cardProgramName = isCardTransaction && transactionCardID !== undefined ? CardUtils.getCardDescription(transactionCardID) : ''; const isApproved = ReportUtils.isReportApproved(moneyRequestReport); - + const taxRates = policy?.taxRates; const formattedTaxAmount = transactionTaxAmount ? CurrencyUtils.convertToDisplayString(transactionTaxAmount, transactionCurrency) : ''; - const policyTaxRatesDescription = policyTaxRates?.name; - const taxRateTitle = (transactionTaxCode && policyTaxRates && TransactionUtils.getTaxName(policyTaxRates.taxes, transactionTaxCode)) ?? ''; + const policyTaxRatesDescription = taxRates?.name; + const taxRateTitle = (transactionTaxCode && taxRates && TransactionUtils.getTaxName(taxRates?.taxes, transactionTaxCode)) ?? ''; // Flags for allowing or disallowing editing a money request const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); From 0cd0f457bc6cdaa641dd155b39a869ea5d79cf44 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 15:50:14 +0100 Subject: [PATCH 122/699] remove policyTaxRates props and ONYX key for MoneyRequestView --- src/components/ReportActionItem/MoneyRequestView.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 147d8e6ea31a..0242c83069fd 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -58,9 +58,6 @@ type MoneyRequestViewOnyxPropsWithoutTransaction = { /** Collection of tags attached to a policy */ policyTagList: OnyxEntry; - /** Collection of tax rates attached to a policy */ - policyTaxRates: OnyxEntry; - /** The expense report or iou report (only will have a value if this is a transaction thread) */ parentReport: OnyxEntry; @@ -87,7 +84,6 @@ function MoneyRequestView({ transaction, policyTagList, policy, - policyTaxRates, transactionViolations, }: MoneyRequestViewProps) { const theme = useTheme(); @@ -491,9 +487,6 @@ export default withOnyx `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`, }, - policyTaxRates: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${report.policyID}`, - }, parentReport: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, }, From a175dac5612296883cd32c49eae9ceca45a4ef25 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 16:04:40 +0100 Subject: [PATCH 123/699] fix lint warning for MoneyRequestView --- src/types/onyx/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 37ce28f93abc..1ba392c6ad3c 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1,6 +1,6 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; -import type {PolicyTaxRates} from '@src/types/onyx/PolicyTaxRates'; +import type {PolicyTaxRates} from './PolicyTaxRates'; import type * as OnyxCommon from './OnyxCommon'; type Unit = 'mi' | 'km'; From 8480869271cb762b2f0d0a8bd1b613d2728df13e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 16:21:19 +0100 Subject: [PATCH 124/699] use taxRates from policy key for taxPicker --- src/components/TaxPicker/index.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/TaxPicker/index.js b/src/components/TaxPicker/index.js index 287805692bcf..b83d75f314aa 100644 --- a/src/components/TaxPicker/index.js +++ b/src/components/TaxPicker/index.js @@ -12,14 +12,15 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, propTypes} from './taxPickerPropTypes'; -function TaxPicker({selectedTaxRate, policyTaxRates, insets, onSubmit}) { +function TaxPicker({selectedTaxRate, policy, insets, onSubmit}) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); - const policyTaxRatesCount = TransactionUtils.getEnabledTaxRateCount(policyTaxRates.taxes); - const isTaxRatesCountBelowThreshold = policyTaxRatesCount < CONST.TAX_RATES_LIST_THRESHOLD; + const taxRates = policy.taxRates; + const taxRatesCount = TransactionUtils.getEnabledTaxRateCount(taxRates.taxes); + const isTaxRatesCountBelowThreshold = taxRatesCount < CONST.TAX_RATES_LIST_THRESHOLD; const shouldShowTextInput = !isTaxRatesCountBelowThreshold; @@ -56,10 +57,10 @@ function TaxPicker({selectedTaxRate, policyTaxRates, insets, onSubmit}) { false, false, true, - policyTaxRates, + taxRates, ); return policyTaxRatesOptions; - }, [policyTaxRates, searchValue, selectedOptions]); + }, [taxRates, searchValue, selectedOptions]); const selectedOptionKey = lodashGet(_.filter(lodashGet(sections, '[0].data', []), (taxRate) => taxRate.searchText === selectedTaxRate)[0], 'keyForList'); @@ -90,7 +91,7 @@ TaxPicker.propTypes = propTypes; TaxPicker.defaultProps = defaultProps; export default withOnyx({ - policyTaxRates: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, + policy: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, })(TaxPicker); From dc0ec64db55aa607208df89f1f71a13e01a9f4f1 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 16:45:47 +0100 Subject: [PATCH 125/699] update TaxPicker PropTypes --- src/components/TaxPicker/taxPickerPropTypes.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/TaxPicker/taxPickerPropTypes.js b/src/components/TaxPicker/taxPickerPropTypes.js index 289b4e19aaa4..35d1866ff5d1 100644 --- a/src/components/TaxPicker/taxPickerPropTypes.js +++ b/src/components/TaxPicker/taxPickerPropTypes.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import taxPropTypes from '@components/taxPropTypes'; +import safeAreaInsetPropTypes from '@pages/safeAreaInsetPropTypes'; const propTypes = { /** The selected tax rate of an expense */ @@ -8,9 +9,16 @@ const propTypes = { /** Callback to fire when a tax is pressed */ onSubmit: PropTypes.func.isRequired, - /* Onyx Props */ - /** Collection of tax rates attached to a policy */ - policyTaxRates: taxPropTypes, + policy: PropTypes.shape({ + /** Collection of tax rates attached to a policy */ + taxRates: taxPropTypes, + }), + + /** + * Safe area insets required for reflecting the portion of the view, + * that is not covered by navigation bars, tab bars, toolbars, and other ancestor views. + */ + insets: safeAreaInsetPropTypes.isRequired, }; const defaultProps = { From 94a2e3fb6e0927a49a98bad626cacb3dfc83746e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 20 Feb 2024 16:47:03 +0100 Subject: [PATCH 126/699] fix prettier --- src/types/onyx/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 1ba392c6ad3c..90b6c0da7e8d 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1,7 +1,7 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; -import type {PolicyTaxRates} from './PolicyTaxRates'; import type * as OnyxCommon from './OnyxCommon'; +import type {PolicyTaxRates} from './PolicyTaxRates'; type Unit = 'mi' | 'km'; From 6153babfa4e8a5c5b4be981b171ba601655800cd Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 20 Feb 2024 19:36:14 -0800 Subject: [PATCH 127/699] minor style and lint updates --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/pages/home/report/ReportActionItem.js | 4 ++-- src/styles/utils/index.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index ae519b04ac92..89e91cd91d52 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -71,7 +71,7 @@ type MoneyRequestViewPropsWithoutTransaction = MoneyRequestViewOnyxPropsWithoutT /** Whether we should display the horizontal rule below the component */ shouldShowHorizontalRule: boolean; - /** Whether we should display the animated above the component */ + /** Whether we should display the animated banner above the component */ shouldShowAnimatedBackground: boolean; }; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 9253457c95f1..c607a9a09a84 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -62,6 +62,7 @@ import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; @@ -77,7 +78,6 @@ import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import reportActionPropTypes from './reportActionPropTypes'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; const propTypes = { ...windowDimensionsPropTypes, @@ -637,7 +637,7 @@ function ReportActionItem(props) { ); diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 5d92ab6ff719..c926a8e83f0e 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -757,7 +757,7 @@ function getLineHeightStyle(lineHeight: number): TextStyle { */ function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean, isMoneyOrTaskReport = false, shouldShowAnimatedBackground = true): ViewStyle { const emptyStateBackground = isMoneyOrTaskReport ? CONST.EMPTY_STATE_BACKGROUND.MONEY_OR_TASK_REPORT : CONST.EMPTY_STATE_BACKGROUND; - let baseStyles: ViewStyle = { + const baseStyles: ViewStyle = { display: 'flex', justifyContent: 'space-between', }; From d436ffe6c5ba59673ced29ffc171a0241b13275b Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 20 Feb 2024 20:26:42 -0800 Subject: [PATCH 128/699] use simplified report icons when applicable --- src/libs/ReportUtils.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0042f7da11d5..e96f624ad8c7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1633,6 +1633,11 @@ function getIcons( }; const isPayer = currentUserAccountID === report?.managerID; + // For one transaction IOUs, display a simplified report icon + if (isOneTransactionReport(report)) { + return [ownerIcon]; + } + return isPayer ? [managerIcon, ownerIcon] : [ownerIcon, managerIcon]; } @@ -4417,6 +4422,10 @@ function shouldReportShowSubscript(report: OnyxEntry): boolean { return true; } + if (isExpenseReport(report) && isOneTransactionReport(report)) { + return true; + } + if (isWorkspaceTaskReport(report)) { return true; } From 3d5af3b73c518c4fc92db662a5187a51366cfbcc Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 11:05:46 +0100 Subject: [PATCH 129/699] use taxRates from policy key for EditRequestPage --- src/pages/EditRequestPage.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index c6e239d8c766..b69dfcb32577 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -7,7 +7,6 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView import categoryPropTypes from '@components/categoryPropTypes'; import ScreenWrapper from '@components/ScreenWrapper'; import tagPropTypes from '@components/tagPropTypes'; -import taxPropTypes from '@components/taxPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; @@ -67,9 +66,6 @@ const propTypes = { /** Transaction that stores the request data */ transaction: transactionPropTypes, - - /** Collection of tax rates attached to a policy */ - policyTaxRates: taxPropTypes, }; const defaultProps = { @@ -79,7 +75,6 @@ const defaultProps = { policyTags: {}, parentReportActions: {}, transaction: {}, - policyTaxRates: {}, }; const getTaxAmount = (transactionAmount, transactionTaxCode, policyTaxRates) => { @@ -87,7 +82,7 @@ const getTaxAmount = (transactionAmount, transactionTaxCode, policyTaxRates) => return CurrencyUtils.convertToBackendAmount(Number.parseFloat(TransactionUtils.calculateTaxAmount(percentage, transactionAmount))); }; -function EditRequestPage({report, route, policy, policyTaxRates, policyCategories, policyTags, parentReportActions, transaction}) { +function EditRequestPage({report, route, policy, policyCategories, policyTags, parentReportActions, transaction}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); const { @@ -107,6 +102,8 @@ function EditRequestPage({report, route, policy, policyTaxRates, policyCategorie const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex); const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); + const policyTaxRates = lodashGet(props.policy, 'taxRates', {}); + const taxRateTitle = TransactionUtils.getTaxName(policyTaxRates.taxes, transactionTaxCode); // A flag for verifying that the current report is a sub-report of a workspace chat @@ -304,9 +301,6 @@ export default compose( policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, - policyTaxRates: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${report.policyID}`, - }, parentReportActions: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, canEvict: false, From 446c7c8427ad90306460ce799186386cd9118725 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 11:08:04 +0100 Subject: [PATCH 130/699] remove undefined props usage for EditRequestPage --- src/pages/EditRequestPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index b69dfcb32577..ce52e4db3052 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -102,7 +102,7 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex); const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); - const policyTaxRates = lodashGet(props.policy, 'taxRates', {}); + const policyTaxRates = lodashGet(policy, 'taxRates', {}); const taxRateTitle = TransactionUtils.getTaxName(policyTaxRates.taxes, transactionTaxCode); From 3e4c6c61ed756484379442ca9eae3398a9a03f9e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 12:45:38 +0100 Subject: [PATCH 131/699] use taxRates from policy key for IOURequestStepTaxAmountPage --- .../iou/request/step/IOURequestStepTaxAmountPage.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js index 491d9801c2dc..6a8fff080de7 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js @@ -29,16 +29,12 @@ const propTypes = { /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, - - /* Onyx Props */ - /** Collection of tax rates attached to a policy */ - policyTaxRates: taxPropTypes, }; const defaultProps = { report: {}, + policy: {}, transaction: {}, - policyTaxRates: {}, }; const getTaxAmount = (transaction, defaultTaxValue) => { @@ -53,7 +49,7 @@ function IOURequestStepTaxAmountPage({ transaction, transaction: {currency}, report, - policyTaxRates, + policy, }) { const {translate} = useLocalize(); const textInput = useRef(null); @@ -63,6 +59,7 @@ function IOURequestStepTaxAmountPage({ const isSaveButtonPressed = useRef(false); const originalCurrency = useRef(null); + const policyTaxRates = lodashGet(policy, 'taxRates', {}); useEffect(() => { if (transaction.originalCurrency) { @@ -158,8 +155,8 @@ export default compose( withWritableReportOrNotFound, withFullTransactionOrNotFound, withOnyx({ - policyTaxRates: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${report ? report.policyID : '0'}`, + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, }, }), )(IOURequestStepTaxAmountPage); From 7a1cb79c716dcd91a50576223f56780e31d35d59 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 12:47:31 +0100 Subject: [PATCH 132/699] add taxRates propTypes to policy for IOURequestStepTaxAmountPage --- src/pages/iou/request/step/IOURequestStepTaxAmountPage.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js index 6a8fff080de7..5135f5f319b4 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js @@ -29,6 +29,12 @@ const propTypes = { /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, + + /** The policy of the report */ + policy: PropTypes.shape({ + /** Collection of tax rates attached to a policy */ + taxRates: taxPropTypes, + }), }; const defaultProps = { From 7376d2e006ecf260545c41a1f73ce0fefa4bcfee Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 12:50:08 +0100 Subject: [PATCH 133/699] add missing imports for IOURequestStepTaxAmountPage --- src/pages/iou/request/step/IOURequestStepTaxAmountPage.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js index 5135f5f319b4..1d5ea686e975 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js @@ -1,4 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; +import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef} from 'react'; import {withOnyx} from 'react-native-onyx'; import taxPropTypes from '@components/taxPropTypes'; From cd2883c47a696e5bdd3fbba36b343055c72adb58 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 12:54:36 +0100 Subject: [PATCH 134/699] add policy onyx props for IOURequestStepTaxRatePage --- src/pages/iou/request/step/IOURequestStepTaxRatePage.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js index f930e33f129f..79429d57cfe1 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js @@ -96,5 +96,8 @@ export default compose( policyTaxRates: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${report ? report.policyID : '0'}`, }, + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, + }, }), )(IOURequestStepTaxRatePage); From 7c88b5d6a958325e4db2722cd7e664cbd22f23de Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 12:56:19 +0100 Subject: [PATCH 135/699] use taxRates from policy key for IOURequestStepTaxRatePage --- src/pages/iou/request/step/IOURequestStepTaxRatePage.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js index 79429d57cfe1..c9f4b0cab940 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js @@ -35,7 +35,7 @@ const propTypes = { const defaultProps = { report: {}, - policyTaxRates: {}, + policy: {}, transaction: {}, }; @@ -48,12 +48,14 @@ function IOURequestStepTaxRatePage({ route: { params: {backTo}, }, - policyTaxRates, + policy, transaction, report, }) { const {translate} = useLocalize(); + const policyTaxRates = lodashGet(policy, 'taxRates', {}); + const navigateBack = () => { Navigation.goBack(backTo); }; From 3d18ec40de77f13cb49b88c437755c453b1cafb2 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 12:57:30 +0100 Subject: [PATCH 136/699] remove taxRates props for IOURequestStepTaxRatePage --- src/pages/iou/request/step/IOURequestStepTaxRatePage.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js index c9f4b0cab940..502b9e223059 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js @@ -2,7 +2,6 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import TaxPicker from '@components/TaxPicker'; -import taxPropTypes from '@components/taxPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; @@ -22,10 +21,6 @@ const propTypes = { /** Navigation route context info provided by react navigation */ route: IOURequestStepRoutePropTypes.isRequired, - /* Onyx Props */ - /** Collection of tax rates attached to a policy */ - policyTaxRates: taxPropTypes, - /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, From 3c8c714bb470721a4688a383f56bcdfdf0c74dc2 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 12:58:53 +0100 Subject: [PATCH 137/699] add policy props types for IOURequestStepTaxRatePage --- src/pages/iou/request/step/IOURequestStepTaxRatePage.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js index 502b9e223059..c56d0e860f33 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js @@ -1,3 +1,4 @@ +import lodashGet from 'lodash/get'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -26,6 +27,12 @@ const propTypes = { /** The report attached to the transaction */ report: reportPropTypes, + + /** The policy of the report */ + policy: PropTypes.shape({ + /** Collection of tax rates attached to a policy */ + taxRates: taxPropTypes, + }), }; const defaultProps = { From 1df6ec5ccfdee83b0b98a7b95beb9304d6d27a2c Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 13:00:17 +0100 Subject: [PATCH 138/699] remove policyTaxRates onyx keys for IOURequestStepTaxRatePage --- src/pages/iou/request/step/IOURequestStepTaxRatePage.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js index c56d0e860f33..e8bf5915e648 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js @@ -97,9 +97,6 @@ export default compose( withWritableReportOrNotFound, withFullTransactionOrNotFound, withOnyx({ - policyTaxRates: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${report ? report.policyID : '0'}`, - }, policy: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, }, From 30ddf52f4df7212723c8a4bea5e03f6496688331 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 13:21:42 +0100 Subject: [PATCH 139/699] add missing proptypes for IOURequestStepTaxRatePage --- src/pages/iou/request/step/IOURequestStepTaxRatePage.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js index e8bf5915e648..694c76e87d99 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js @@ -1,8 +1,10 @@ import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import TaxPicker from '@components/TaxPicker'; +import taxPropTypes from '@components/taxPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; From aa0832653f4355e113fe6b28df2f95f6f1e0a0a3 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 14:04:30 +0100 Subject: [PATCH 140/699] remove tax rates types and keys --- src/ONYXKEYS.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 2e0ea7a6c9f9..d39d9a6020be 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -279,7 +279,6 @@ const ONYXKEYS = { POLICY_CATEGORIES: 'policyCategories_', POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', - POLICY_TAX_RATE: 'policyTaxRates_', POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', POLICY_REPORT_FIELDS: 'policyReportFields_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', @@ -469,7 +468,6 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; [ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string; [ONYXKEYS.COLLECTION.NEXT_STEP]: OnyxTypes.ReportNextStep; - [ONYXKEYS.COLLECTION.POLICY_TAX_RATE]: OnyxTypes.PolicyTaxRates; }; type OnyxValuesMapping = { From be1c293edaac9be71e5066c6bf193971ee11f234 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 21 Feb 2024 14:14:07 +0100 Subject: [PATCH 141/699] use BaseTextInputRef for text input ref --- src/pages/EditRequestTaxAmountPage.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/EditRequestTaxAmountPage.tsx b/src/pages/EditRequestTaxAmountPage.tsx index 437e84df652c..22ce8a4de2df 100644 --- a/src/pages/EditRequestTaxAmountPage.tsx +++ b/src/pages/EditRequestTaxAmountPage.tsx @@ -1,10 +1,10 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef} from 'react'; -import type {TextInput} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import type {BaseTextInputRef} from '@src/components/TextInput/BaseTextInput/types'; import CONST from '@src/CONST'; import MoneyRequestAmountForm from './iou/steps/MoneyRequestAmountForm'; @@ -27,7 +27,7 @@ type EditRequestTaxAmountPageProps = { function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurrency, onNavigateToCurrency, onSubmit}: EditRequestTaxAmountPageProps) { const {translate} = useLocalize(); - const textInput = useRef(null); + const textInput = useRef(null); const focusTimeoutRef = useRef(null); @@ -52,7 +52,6 @@ function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurre > Date: Thu, 22 Feb 2024 12:43:38 +0100 Subject: [PATCH 142/699] update Tax picker prop Types --- src/components/TaxPicker/taxPickerPropTypes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/TaxPicker/taxPickerPropTypes.js b/src/components/TaxPicker/taxPickerPropTypes.js index 35d1866ff5d1..476bd9f8eaf4 100644 --- a/src/components/TaxPicker/taxPickerPropTypes.js +++ b/src/components/TaxPicker/taxPickerPropTypes.js @@ -18,12 +18,12 @@ const propTypes = { * Safe area insets required for reflecting the portion of the view, * that is not covered by navigation bars, tab bars, toolbars, and other ancestor views. */ - insets: safeAreaInsetPropTypes.isRequired, + insets?: safeAreaInsetPropTypes.isRequired, }; const defaultProps = { selectedTaxRate: '', - policyTaxRates: {}, + policy: {}, }; export {propTypes, defaultProps}; From 7abbcb9eac173c1328a3dd36fc020baee38258f9 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 12:48:45 +0100 Subject: [PATCH 143/699] remove unexoected token --- src/components/TaxPicker/taxPickerPropTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TaxPicker/taxPickerPropTypes.js b/src/components/TaxPicker/taxPickerPropTypes.js index 476bd9f8eaf4..e0c15a058c0f 100644 --- a/src/components/TaxPicker/taxPickerPropTypes.js +++ b/src/components/TaxPicker/taxPickerPropTypes.js @@ -18,7 +18,7 @@ const propTypes = { * Safe area insets required for reflecting the portion of the view, * that is not covered by navigation bars, tab bars, toolbars, and other ancestor views. */ - insets?: safeAreaInsetPropTypes.isRequired, + insets: safeAreaInsetPropTypes, }; const defaultProps = { From 78f82327a02f35552706769b24761431ddd74fea Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 12:54:29 +0100 Subject: [PATCH 144/699] update tax rates var --- src/pages/EditRequestPage.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index ce52e4db3052..4ac9de3cb2f6 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -77,8 +77,8 @@ const defaultProps = { transaction: {}, }; -const getTaxAmount = (transactionAmount, transactionTaxCode, policyTaxRates) => { - const percentage = (transactionTaxCode ? policyTaxRates.taxes[transactionTaxCode].value : policyTaxRates.defaultValue) || ''; +const getTaxAmount = (transactionAmount, transactionTaxCode, taxRates) => { + const percentage = (transactionTaxCode ? taxRates.taxes[transactionTaxCode].value : taxRates.defaultValue) || ''; return CurrencyUtils.convertToBackendAmount(Number.parseFloat(TransactionUtils.calculateTaxAmount(percentage, transactionAmount))); }; @@ -102,9 +102,9 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex); const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); - const policyTaxRates = lodashGet(policy, 'taxRates', {}); + const taxRates = lodashGet(policy, 'taxRates', {}); - const taxRateTitle = TransactionUtils.getTaxName(policyTaxRates.taxes, transactionTaxCode); + const taxRateTitle = TransactionUtils.getTaxName(taxRates.taxes, transactionTaxCode); // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); @@ -230,7 +230,7 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p return ( { const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); From 60ab450b8716499fa3b18133ebdaec143e108b5e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 12:59:10 +0100 Subject: [PATCH 145/699] update tax rate name --- src/components/ReportActionItem/MoneyRequestView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index dfae8702abef..2cf016556429 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -122,7 +122,7 @@ function MoneyRequestView({ const taxRates = policy?.taxRates; const formattedTaxAmount = transactionTaxAmount ? CurrencyUtils.convertToDisplayString(transactionTaxAmount, transactionCurrency) : ''; - const policyTaxRatesDescription = taxRates?.name; + const taxRatesDescription = taxRates?.name; const taxRateTitle = (transactionTaxCode && taxRates && TransactionUtils.getTaxName(taxRates?.taxes, transactionTaxCode)) ?? ''; // Flags for allowing or disallowing editing a money request @@ -433,7 +433,7 @@ function MoneyRequestView({ Date: Thu, 22 Feb 2024 13:01:02 +0100 Subject: [PATCH 146/699] update tax rate name --- src/pages/iou/request/step/IOURequestStepTaxAmountPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js index 1d5ea686e975..907b79f6968f 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js @@ -67,7 +67,7 @@ function IOURequestStepTaxAmountPage({ const isSaveButtonPressed = useRef(false); const originalCurrency = useRef(null); - const policyTaxRates = lodashGet(policy, 'taxRates', {}); + const taxRates = lodashGet(policy, 'taxRates', {}); useEffect(() => { if (transaction.originalCurrency) { @@ -146,7 +146,7 @@ function IOURequestStepTaxAmountPage({ isEditing={isEditing} currency={currency} amount={transaction.taxAmount} - taxAmount={getTaxAmount(transaction, policyTaxRates.defaultValue)} + taxAmount={getTaxAmount(transaction, taxRates.defaultValue)} ref={(e) => (textInput.current = e)} onCurrencyButtonPress={navigateToCurrencySelectionPage} onSubmitButtonPress={updateTaxAmount} From 1127e7c58684ce9c6d0320610b673263ef4f7111 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 13:01:52 +0100 Subject: [PATCH 147/699] update tax rate name --- src/pages/iou/request/step/IOURequestStepTaxRatePage.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js index 694c76e87d99..241f5cccdc17 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js @@ -58,16 +58,16 @@ function IOURequestStepTaxRatePage({ }) { const {translate} = useLocalize(); - const policyTaxRates = lodashGet(policy, 'taxRates', {}); + const taxRates = lodashGet(policy, 'taxRates', {}); const navigateBack = () => { Navigation.goBack(backTo); }; - const selectedTaxRate = TransactionUtils.getDefaultTaxName(policyTaxRates, transaction); + const selectedTaxRate = TransactionUtils.getDefaultTaxName(taxRates, transaction); const updateTaxRates = (taxes) => { - const taxAmount = getTaxAmount(policyTaxRates, taxes.text, transaction.amount); + const taxAmount = getTaxAmount(taxRates, taxes.text, transaction.amount); const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); IOU.setMoneyRequestTaxRate(transaction.transactionID, taxes); IOU.setMoneyRequestTaxAmount(transaction.transactionID, amountInSmallestCurrencyUnits); From 79f02bc6e6b80c799649d3d83bb085f4c4eeb89b Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 22 Feb 2024 13:20:33 +0100 Subject: [PATCH 148/699] Fix bug with Item in report list is not highlighted and list cannot be navigated and refactore code --- .../CalendarPicker/YearPickerModal.tsx | 2 +- .../MoneyRequestConfirmationList.js | 3 -- ...oraryForRefactorRequestConfirmationList.js | 3 -- .../OptionsList/BaseOptionsList.tsx | 7 +++- src/components/OptionsList/types.ts | 3 -- .../optionsSelectorPropTypes.js | 3 -- .../SelectionList/BaseSelectionList.tsx | 7 +++- .../SelectionList/selectionListPropTypes.js | 3 -- .../StatePicker/StateSelectorModal.tsx | 2 +- src/libs/OptionsListUtils.ts | 39 ------------------- src/pages/EditReportFieldDropdownPage.tsx | 1 + src/pages/NewChatPage.tsx | 10 +---- .../BusinessTypeSelectorModal.tsx | 2 +- src/pages/ReportParticipantsPage.tsx | 1 - src/pages/RoomInvitePage.tsx | 6 --- src/pages/RoomMembersPage.tsx | 2 +- src/pages/SearchPage/index.js | 6 --- src/pages/WorkspaceSwitcherPage.tsx | 1 - src/pages/iou/IOUCurrencySelection.js | 1 - ...yForRefactorRequestParticipantsSelector.js | 8 ---- .../request/step/IOURequestStepCurrency.js | 1 - .../MoneyRequestParticipantsSelector.js | 8 ---- .../ShareLogList/BaseShareLogList.tsx | 6 --- .../PersonalDetails/CountrySelectionPage.js | 2 +- src/pages/settings/Profile/PronounsPage.js | 2 +- .../settings/Profile/TimezoneSelectPage.js | 2 +- src/pages/tasks/TaskAssigneeSelectorModal.js | 8 ---- .../TaskShareDestinationSelectorModal.js | 3 -- src/pages/workspace/WorkspaceInvitePage.tsx | 6 --- src/pages/workspace/WorkspaceMembersPage.js | 2 +- .../workspace/WorkspaceProfileCurrencyPage.js | 2 +- 31 files changed, 23 insertions(+), 129 deletions(-) diff --git a/src/components/DatePicker/CalendarPicker/YearPickerModal.tsx b/src/components/DatePicker/CalendarPicker/YearPickerModal.tsx index f8c4a12ec188..29beedb92714 100644 --- a/src/components/DatePicker/CalendarPicker/YearPickerModal.tsx +++ b/src/components/DatePicker/CalendarPicker/YearPickerModal.tsx @@ -34,7 +34,7 @@ function YearPickerModal({isVisible, years, currentYear = new Date().getFullYear const yearsList = searchText === '' ? years : years.filter((year) => year.text.includes(searchText)); return { headerMessage: !yearsList.length ? translate('common.noResultsFound') : '', - sections: [{data: yearsList.sort((a, b) => b.value - a.value), indexOffset: 0}], + sections: [{data: yearsList.sort((a, b) => b.value - a.value)}], }; }, [years, searchText, translate]); diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 0de601bc9f61..5db353a01830 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -377,14 +377,12 @@ function MoneyRequestConfirmationList(props) { title: translate('moneyRequestConfirmationList.paidBy'), data: [formattedPayeeOption], shouldShow: true, - indexOffset: 0, isDisabled: shouldDisablePaidBySection, }, { title: translate('moneyRequestConfirmationList.splitWith'), data: formattedParticipantsList, shouldShow: true, - indexOffset: 1, }, ); } else { @@ -396,7 +394,6 @@ function MoneyRequestConfirmationList(props) { title: translate('common.to'), data: formattedSelectedParticipants, shouldShow: true, - indexOffset: 0, }); } return sections; diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 3939e847707d..a2efb01a5e76 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -422,14 +422,12 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ title: translate('moneyRequestConfirmationList.paidBy'), data: [formattedPayeeOption], shouldShow: true, - indexOffset: 0, isDisabled: shouldDisablePaidBySection, }, { title: translate('moneyRequestConfirmationList.splitWith'), data: formattedParticipantsList, shouldShow: true, - indexOffset: 1, }, ); } else { @@ -441,7 +439,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ title: translate('common.to'), data: formattedSelectedParticipants, shouldShow: true, - indexOffset: 0, }); } return sections; diff --git a/src/components/OptionsList/BaseOptionsList.tsx b/src/components/OptionsList/BaseOptionsList.tsx index 575df128894a..928308c73bfe 100644 --- a/src/components/OptionsList/BaseOptionsList.tsx +++ b/src/components/OptionsList/BaseOptionsList.tsx @@ -223,6 +223,11 @@ function BaseOptionsList( return ; }; + const sectionsWithIndexOffset = sections.map((section, index) => { + const indexOffset = [...sections].splice(0, index).reduce((acc, curr) => acc + curr.data.length, 0); + return {...section, indexOffset}; + }); + return ( {isLoading ? ( @@ -248,7 +253,7 @@ function BaseOptionsList( onScroll={onScroll} contentContainerStyle={contentContainerStyles} showsVerticalScrollIndicator={showScrollIndicator} - sections={sections} + sections={sectionsWithIndexOffset} keyExtractor={extractKey} stickySectionHeadersEnabled={false} renderItem={renderItem} diff --git a/src/components/OptionsList/types.ts b/src/components/OptionsList/types.ts index fa3ef8df56f6..dc455a53690d 100644 --- a/src/components/OptionsList/types.ts +++ b/src/components/OptionsList/types.ts @@ -9,9 +9,6 @@ type Section = { /** Title of the section */ title: string; - /** The initial index of this section given the total number of options in each section's data array */ - indexOffset: number; - /** Array of options */ data: OptionData[]; diff --git a/src/components/OptionsSelector/optionsSelectorPropTypes.js b/src/components/OptionsSelector/optionsSelectorPropTypes.js index 8e58a7ffdb86..b430ce8a4933 100644 --- a/src/components/OptionsSelector/optionsSelectorPropTypes.js +++ b/src/components/OptionsSelector/optionsSelectorPropTypes.js @@ -14,9 +14,6 @@ const propTypes = { /** Title of the section */ title: PropTypes.string, - /** The initial index of this section given the total number of options in each section's data array */ - indexOffset: PropTypes.number, - /** Array of options */ data: PropTypes.arrayOf(optionPropTypes), diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index b0996a08895a..9c2998a0f868 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -375,6 +375,11 @@ function BaseSelectionList( isActive: !disableKeyboardShortcuts && isFocused, }); + const sectionsWithIndexOffset = sections.map((section, index) => { + const indexOffset = [...sections].splice(0, index).reduce((acc, curr) => acc + curr.data.length, 0); + return {...section, indexOffset}; + }); + return ( ( )} {}, onStat // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing textInputLabel={label || translate('common.state')} textInputValue={searchValue} - sections={[{data: searchResults, indexOffset: 0}]} + sections={[{data: searchResults}]} onSelectRow={onStateSelected} onChangeText={setSearchValue} initiallyFocusedOptionKey={currentState} diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 97b4fc0144c8..e655740eca95 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -71,7 +71,6 @@ type PayeePersonalDetails = { type CategorySection = { title: string | undefined; shouldShow: boolean; - indexOffset: number; data: Option[]; }; @@ -131,7 +130,6 @@ type MemberForList = { type SectionForSearchTerm = { section: CategorySection; - newIndexOffset: number; }; type GetOptions = { @@ -946,14 +944,11 @@ function getCategoryListSections( const categorySections: CategorySection[] = []; const numberOfCategories = enabledCategories.length; - let indexOffset = 0; - if (numberOfCategories === 0 && selectedOptions.length > 0) { categorySections.push({ // "Selected" section title: '', shouldShow: false, - indexOffset, data: getCategoryOptionTree(selectedOptions, true), }); @@ -967,7 +962,6 @@ function getCategoryListSections( // "Search" section title: '', shouldShow: true, - indexOffset, data: getCategoryOptionTree(searchCategories, true), }); @@ -983,7 +977,6 @@ function getCategoryListSections( // "All" section when items amount less than the threshold title: '', shouldShow: false, - indexOffset, data: getCategoryOptionTree(enabledAndSelectedCategories), }); @@ -995,11 +988,8 @@ function getCategoryListSections( // "Selected" section title: '', shouldShow: false, - indexOffset, data: getCategoryOptionTree(selectedOptions, true), }); - - indexOffset += selectedOptions.length; } const filteredRecentlyUsedCategories = recentlyUsedCategories @@ -1016,11 +1006,8 @@ function getCategoryListSections( // "Recent" section title: Localize.translateLocal('common.recent'), shouldShow: true, - indexOffset, data: getCategoryOptionTree(cutRecentlyUsedCategories, true), }); - - indexOffset += filteredRecentlyUsedCategories.length; } const filteredCategories = enabledCategories.filter((category) => !selectedOptionNames.includes(category.name)); @@ -1029,7 +1016,6 @@ function getCategoryListSections( // "All" section when items amount more than the threshold title: Localize.translateLocal('common.all'), shouldShow: true, - indexOffset, data: getCategoryOptionTree(filteredCategories), }); @@ -1063,7 +1049,6 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt const sortedTags = sortTags(tags); const enabledTags = sortedTags.filter((tag) => tag.enabled); const numberOfTags = enabledTags.length; - let indexOffset = 0; // If all tags are disabled but there's a previously selected tag, show only the selected tag if (numberOfTags === 0 && selectedOptions.length > 0) { @@ -1076,7 +1061,6 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt // "Selected" section title: '', shouldShow: false, - indexOffset, data: getTagsOptions(selectedTagOptions), }); @@ -1090,7 +1074,6 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt // "Search" section title: '', shouldShow: true, - indexOffset, data: getTagsOptions(searchTags), }); @@ -1102,7 +1085,6 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt // "All" section when items amount less than the threshold title: '', shouldShow: false, - indexOffset, data: getTagsOptions(enabledTags), }); @@ -1131,11 +1113,8 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt // "Selected" section title: '', shouldShow: true, - indexOffset, data: getTagsOptions(selectedTagOptions), }); - - indexOffset += selectedOptions.length; } if (filteredRecentlyUsedTags.length > 0) { @@ -1145,18 +1124,14 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt // "Recent" section title: Localize.translateLocal('common.recent'), shouldShow: true, - indexOffset, data: getTagsOptions(cutRecentlyUsedTags), }); - - indexOffset += filteredRecentlyUsedTags.length; } tagSections.push({ // "All" section when items amount more than the threshold title: Localize.translateLocal('common.all'), shouldShow: true, - indexOffset, data: getTagsOptions(filteredTags), }); @@ -1227,8 +1202,6 @@ function getTaxRatesSection(policyTaxRates: PolicyTaxRateWithDefault | undefined const enabledTaxRates = sortedTaxRates.filter((taxRate) => !taxRate.isDisabled); const numberOfTaxRates = enabledTaxRates.length; - let indexOffset = 0; - // If all tax are disabled but there's a previously selected tag, show only the selected tag if (numberOfTaxRates === 0 && selectedOptions.length > 0) { const selectedTaxRateOptions = selectedOptions.map((option) => ({ @@ -1240,7 +1213,6 @@ function getTaxRatesSection(policyTaxRates: PolicyTaxRateWithDefault | undefined // "Selected" sectiong title: '', shouldShow: false, - indexOffset, data: getTaxRatesOptions(selectedTaxRateOptions), }); @@ -1254,7 +1226,6 @@ function getTaxRatesSection(policyTaxRates: PolicyTaxRateWithDefault | undefined // "Search" section title: '', shouldShow: true, - indexOffset, data: getTaxRatesOptions(searchTaxRates), }); @@ -1266,7 +1237,6 @@ function getTaxRatesSection(policyTaxRates: PolicyTaxRateWithDefault | undefined // "All" section when items amount less than the threshold title: '', shouldShow: false, - indexOffset, data: getTaxRatesOptions(enabledTaxRates), }); @@ -1290,18 +1260,14 @@ function getTaxRatesSection(policyTaxRates: PolicyTaxRateWithDefault | undefined // "Selected" section title: '', shouldShow: true, - indexOffset, data: getTaxRatesOptions(selectedTaxRatesOptions), }); - - indexOffset += selectedOptions.length; } policyRatesSections.push({ // "All" section when number of items are more than the threshold title: '', shouldShow: true, - indexOffset, data: getTaxRatesOptions(filteredTaxRates), }); @@ -1965,7 +1931,6 @@ function formatSectionsFromSearchTerm( filteredRecentReports: ReportUtils.OptionData[], filteredPersonalDetails: ReportUtils.OptionData[], maxOptionsSelected: boolean, - indexOffset = 0, personalDetails: OnyxEntry = {}, shouldGetOptionDetails = false, ): SectionForSearchTerm { @@ -1983,9 +1948,7 @@ function formatSectionsFromSearchTerm( }) : selectedOptions, shouldShow: selectedOptions.length > 0, - indexOffset, }, - newIndexOffset: indexOffset + selectedOptions.length, }; } @@ -2009,9 +1972,7 @@ function formatSectionsFromSearchTerm( }) : selectedParticipantsWithoutDetails, shouldShow: selectedParticipantsWithoutDetails.length > 0, - indexOffset, }, - newIndexOffset: indexOffset + selectedParticipantsWithoutDetails.length, }; } diff --git a/src/pages/EditReportFieldDropdownPage.tsx b/src/pages/EditReportFieldDropdownPage.tsx index 1ad3c766221b..1ad63bb2bf2f 100644 --- a/src/pages/EditReportFieldDropdownPage.tsx +++ b/src/pages/EditReportFieldDropdownPage.tsx @@ -92,6 +92,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldID, fieldValue, textInputLabel={translate('common.search')} boldStyle sections={sections} + focusedIndex={0} value={searchValue} onSelectRow={(option: Record) => onSubmit({[fieldID]: option.text})} onChangeText={setSearchValue} diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 72393e89ae1a..b4f40eb0691b 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -73,13 +73,10 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF const sections = useMemo((): OptionsListUtils.CategorySection[] => { const sectionsList: OptionsListUtils.CategorySection[] = []; - let indexOffset = 0; - const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(searchTerm, selectedOptions, filteredRecentReports, filteredPersonalDetails, maxParticipantsReached, indexOffset); + const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(searchTerm, selectedOptions, filteredRecentReports, filteredPersonalDetails, maxParticipantsReached); sectionsList.push(formatResults.section); - indexOffset = formatResults.newIndexOffset; - if (maxParticipantsReached) { return sectionsList; } @@ -88,24 +85,19 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF title: translate('common.recents'), data: filteredRecentReports, shouldShow: filteredRecentReports.length > 0, - indexOffset, }); - indexOffset += filteredRecentReports.length; sectionsList.push({ title: translate('common.contacts'), data: filteredPersonalDetails, shouldShow: filteredPersonalDetails.length > 0, - indexOffset, }); - indexOffset += filteredPersonalDetails.length; if (filteredUserToInvite) { sectionsList.push({ title: undefined, data: [filteredUserToInvite], shouldShow: true, - indexOffset, }); } diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/TypeBusiness/BusinessTypePicker/BusinessTypeSelectorModal.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/TypeBusiness/BusinessTypePicker/BusinessTypeSelectorModal.tsx index 2db3a4fdf7ad..0f85b58bf10a 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/TypeBusiness/BusinessTypePicker/BusinessTypeSelectorModal.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/TypeBusiness/BusinessTypePicker/BusinessTypeSelectorModal.tsx @@ -62,7 +62,7 @@ function BusinessTypeSelectorModal({isVisible, currentBusinessType, onBusinessTy onBackButtonPress={onClose} /> { diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 40a1b009b38d..464a63f723f9 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -97,7 +97,6 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa const sections = useMemo(() => { const sectionsArr: Sections = []; - let indexOffset = 0; if (!didScreenTransitionEnd) { return []; @@ -120,9 +119,7 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa sectionsArr.push({ title: undefined, data: filterSelectedOptionsFormatted, - indexOffset, }); - indexOffset += filterSelectedOptions.length; // Filtering out selected users from the search results const selectedLogins = selectedOptions.map(({login}) => login); @@ -133,15 +130,12 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa sectionsArr.push({ title: translate('common.contacts'), data: personalDetailsFormatted, - indexOffset, }); - indexOffset += personalDetailsFormatted.length; if (hasUnselectedUserToInvite) { sectionsArr.push({ title: undefined, data: [OptionsListUtils.formatMemberForList(userToInvite)], - indexOffset, }); } diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 7593857536a6..3b33ced25d5f 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -265,7 +265,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { { const newSections = []; - let indexOffset = 0; if (recentReports.length > 0) { newSections.push({ data: recentReports, shouldShow: true, - indexOffset, }); - indexOffset += recentReports.length; } if (localPersonalDetails.length > 0) { newSections.push({ data: localPersonalDetails, shouldShow: true, - indexOffset, }); - indexOffset += recentReports.length; } if (userToInvite) { newSections.push({ data: [userToInvite], shouldShow: true, - indexOffset, }); } diff --git a/src/pages/WorkspaceSwitcherPage.tsx b/src/pages/WorkspaceSwitcherPage.tsx index d361ba5137b6..7ef1c8d1fb53 100644 --- a/src/pages/WorkspaceSwitcherPage.tsx +++ b/src/pages/WorkspaceSwitcherPage.tsx @@ -155,7 +155,6 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { () => ({ data: filteredAndSortedUserWorkspaces, shouldShow: true, - indexOffset: 0, }), [filteredAndSortedUserWorkspaces], ); diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 7495efb43171..da662b066176 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -145,7 +145,6 @@ function IOUCurrencySelection(props) { : [ { data: filteredCurrencies, - indexOffset: 0, }, ], headerMessage: isEmpty ? translate('common.noResultsFound') : '', diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 238b66c0e727..c514dd9663df 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -106,7 +106,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ if (!didScreenTransitionEnd) { return [newSections, {}]; } - let indexOffset = 0; const chatOptions = OptionsListUtils.getFilteredOptions( reports, @@ -141,12 +140,10 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ chatOptions.recentReports, chatOptions.personalDetails, maxParticipantsReached, - indexOffset, personalDetails, true, ); newSections.push(formatResults.section); - indexOffset = formatResults.newIndexOffset; if (maxParticipantsReached) { return [newSections, {}]; @@ -156,17 +153,13 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ title: translate('common.recents'), data: chatOptions.recentReports, shouldShow: !_.isEmpty(chatOptions.recentReports), - indexOffset, }); - indexOffset += chatOptions.recentReports.length; newSections.push({ title: translate('common.contacts'), data: chatOptions.personalDetails, shouldShow: !_.isEmpty(chatOptions.personalDetails), - indexOffset, }); - indexOffset += chatOptions.personalDetails.length; if (chatOptions.userToInvite && !OptionsListUtils.isCurrentUser(chatOptions.userToInvite)) { newSections.push({ @@ -176,7 +169,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails); }), shouldShow: true, - indexOffset, }); } diff --git a/src/pages/iou/request/step/IOURequestStepCurrency.js b/src/pages/iou/request/step/IOURequestStepCurrency.js index 43e4e9bf0eaa..ba1354b4a2e6 100644 --- a/src/pages/iou/request/step/IOURequestStepCurrency.js +++ b/src/pages/iou/request/step/IOURequestStepCurrency.js @@ -109,7 +109,6 @@ function IOURequestStepCurrency({ : [ { data: filteredCurrencies, - indexOffset: 0, }, ], headerMessage: isEmpty ? translate('common.noResultsFound') : '', diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 3fde970327d7..f75bf3f7ddd2 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -140,7 +140,6 @@ function MoneyRequestParticipantsSelector({ */ const sections = useMemo(() => { const newSections = []; - let indexOffset = 0; const formatResults = OptionsListUtils.formatSectionsFromSearchTerm( searchTerm, @@ -148,12 +147,10 @@ function MoneyRequestParticipantsSelector({ newChatOptions.recentReports, newChatOptions.personalDetails, maxParticipantsReached, - indexOffset, personalDetails, true, ); newSections.push(formatResults.section); - indexOffset = formatResults.newIndexOffset; if (maxParticipantsReached) { return newSections; @@ -163,17 +160,13 @@ function MoneyRequestParticipantsSelector({ title: translate('common.recents'), data: newChatOptions.recentReports, shouldShow: !_.isEmpty(newChatOptions.recentReports), - indexOffset, }); - indexOffset += newChatOptions.recentReports.length; newSections.push({ title: translate('common.contacts'), data: newChatOptions.personalDetails, shouldShow: !_.isEmpty(newChatOptions.personalDetails), - indexOffset, }); - indexOffset += newChatOptions.personalDetails.length; if (newChatOptions.userToInvite && !OptionsListUtils.isCurrentUser(newChatOptions.userToInvite)) { newSections.push({ @@ -183,7 +176,6 @@ function MoneyRequestParticipantsSelector({ return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails); }), shouldShow: true, - indexOffset, }); } diff --git a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx index 18e936f3045e..70c2d301b9ac 100644 --- a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx +++ b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx @@ -64,29 +64,23 @@ function BaseShareLogList({betas, reports, onAttachLogToReport}: BaseShareLogLis const sections = useMemo(() => { const sectionsList = []; - let indexOffset = 0; sectionsList.push({ title: translate('common.recents'), data: searchOptions.recentReports, shouldShow: searchOptions.recentReports?.length > 0, - indexOffset, }); - indexOffset += searchOptions.recentReports?.length; sectionsList.push({ title: translate('common.contacts'), data: searchOptions.personalDetails, shouldShow: searchOptions.personalDetails?.length > 0, - indexOffset, }); - indexOffset += searchOptions.personalDetails?.length; if (searchOptions.userToInvite) { sectionsList.push({ data: [searchOptions.userToInvite], shouldShow: true, - indexOffset, }); } diff --git a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.js b/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.js index d8327041538d..4adee2cc0dd4 100644 --- a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.js +++ b/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.js @@ -93,7 +93,7 @@ function CountrySelectionPage({route, navigation}) { headerMessage={headerMessage} textInputLabel={translate('common.country')} textInputValue={searchValue} - sections={[{data: searchResults, indexOffset: 0}]} + sections={[{data: searchResults}]} ListItem={RadioListItem} onSelectRow={selectCountry} onChangeText={setSearchValue} diff --git a/src/pages/settings/Profile/PronounsPage.js b/src/pages/settings/Profile/PronounsPage.js index 1d4675a42b8a..b8c0a4ebffde 100644 --- a/src/pages/settings/Profile/PronounsPage.js +++ b/src/pages/settings/Profile/PronounsPage.js @@ -100,7 +100,7 @@ function PronounsPage({currentUserPersonalDetails, isLoadingApp}) { textInputLabel={translate('pronounsPage.pronouns')} textInputPlaceholder={translate('pronounsPage.placeholderText')} textInputValue={searchValue} - sections={[{data: filteredPronounsList, indexOffset: 0}]} + sections={[{data: filteredPronounsList}]} ListItem={RadioListItem} onSelectRow={updatePronouns} onChangeText={setSearchValue} diff --git a/src/pages/settings/Profile/TimezoneSelectPage.js b/src/pages/settings/Profile/TimezoneSelectPage.js index b6c8a5967abc..9e8dafcc5205 100644 --- a/src/pages/settings/Profile/TimezoneSelectPage.js +++ b/src/pages/settings/Profile/TimezoneSelectPage.js @@ -94,7 +94,7 @@ function TimezoneSelectPage(props) { textInputValue={timezoneInputText} onChangeText={filterShownTimezones} onSelectRow={saveSelectedTimezone} - sections={[{data: timezoneOptions, indexOffset: 0, isDisabled: timezone.automatic}]} + sections={[{data: timezoneOptions, isDisabled: timezone.automatic}]} initiallyFocusedOptionKey={_.get(_.filter(timezoneOptions, (tz) => tz.text === timezone.selected)[0], 'keyForList')} showScrollIndicator shouldShowTooltips={false} diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index 0e1e64dfa415..67a6e5f57d01 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -121,39 +121,31 @@ function TaskAssigneeSelectorModal({reports, task, rootParentReportPolicy}) { const sections = useMemo(() => { const sectionsList = []; - let indexOffset = 0; if (currentUserOption) { sectionsList.push({ title: translate('newTaskPage.assignMe'), data: [currentUserOption], shouldShow: true, - indexOffset, }); - indexOffset += 1; } sectionsList.push({ title: translate('common.recents'), data: recentReports, shouldShow: recentReports?.length > 0, - indexOffset, }); - indexOffset += recentReports?.length; sectionsList.push({ title: translate('common.contacts'), data: personalDetails, shouldShow: personalDetails?.length > 0, - indexOffset, }); - indexOffset += personalDetails?.length; if (userToInvite) { sectionsList.push({ data: [userToInvite], shouldShow: true, - indexOffset, }); } diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index b8d9229e6158..e4144f8b348e 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -90,15 +90,12 @@ function TaskShareDestinationSelectorModal(props) { const getSections = () => { const sections = []; - let indexOffset = 0; if (filteredRecentReports?.length > 0) { sections.push({ data: filteredRecentReports, shouldShow: true, - indexOffset, }); - indexOffset += filteredRecentReports?.length; } return sections; diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 8efc7d7c6a1e..b65f168fd7b2 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -164,7 +164,6 @@ function WorkspaceInvitePage({ const sections: MembersSection[] = useMemo(() => { const sectionsArr: MembersSection[] = []; - let indexOffset = 0; if (!didScreenTransitionEnd) { return []; @@ -188,9 +187,7 @@ function WorkspaceInvitePage({ title: undefined, data: filterSelectedOptions, shouldShow: true, - indexOffset, }); - indexOffset += filterSelectedOptions.length; // Filtering out selected users from the search results const selectedLogins = selectedOptions.map(({login}) => login); @@ -201,9 +198,7 @@ function WorkspaceInvitePage({ title: translate('common.contacts'), data: personalDetailsFormatted, shouldShow: !isEmptyObject(personalDetailsFormatted), - indexOffset, }); - indexOffset += personalDetailsFormatted.length; Object.values(usersToInvite).forEach((userToInvite) => { const hasUnselectedUserToInvite = !selectedLogins.some((selectedLogin) => selectedLogin === userToInvite.login); @@ -213,7 +208,6 @@ function WorkspaceInvitePage({ title: undefined, data: [OptionsListUtils.formatMemberForList(userToInvite)], shouldShow: true, - indexOffset: indexOffset++, }); } }); diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 62b96943453c..311be0481f84 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -488,7 +488,7 @@ function WorkspaceMembersPage(props) { Date: Thu, 22 Feb 2024 12:32:52 +0000 Subject: [PATCH 149/699] chore: remove unused typescript expect error --- src/pages/iou/steps/NewRequestAmountPage.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/steps/NewRequestAmountPage.tsx b/src/pages/iou/steps/NewRequestAmountPage.tsx index 8f0085230233..e3c6640c11e7 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.tsx +++ b/src/pages/iou/steps/NewRequestAmountPage.tsx @@ -2,13 +2,13 @@ import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; -import type {TextInput as RNTextInput} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; @@ -44,7 +44,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}: NewRequestAmoun const {translate} = useLocalize(); const prevMoneyRequestID = useRef(iou?.id); - const textInput = useRef(null); + const textInput = useRef(null); const iouType = route.params.iouType ?? ''; const reportID = route.params.reportID ?? ''; @@ -130,7 +130,6 @@ function NewRequestAmountPage({route, iou, report, selectedTab}: NewRequestAmoun const content = ( Date: Thu, 22 Feb 2024 13:33:50 +0100 Subject: [PATCH 150/699] add taxRates types to Policy --- src/types/onyx/Policy.ts | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index f85d4e0cba05..31b2d1ac4b53 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -35,6 +35,42 @@ type DisabledFields = { reimbursable?: boolean; }; +type TaxRate = { + /** Name of the a tax rate. */ + name: string; + + /** The value of the tax rate. */ + value: string; + + /** The code associated with the tax rate. */ + code: string; + + /** This contains the tax name and tax value as one name */ + modifiedName: string; + + /** Indicates if the tax rate is disabled. */ + isDisabled?: boolean; +}; + +type TaxRates = Record; + +type TaxRatesWithDefault = { + /** Name of the tax */ + name: string; + + /** Default policy tax ID */ + defaultExternalID: string; + + /** Default value of taxes */ + defaultValue: string; + + /** Default foreign policy tax ID */ + foreignTaxDefault: string; + + /** List of tax names and values */ + taxes: TaxRates; +}; + // These types are for the Integration connections for a policy (eg. Quickbooks, Xero, etc). // This data is not yet used in the codebase which is why it is given a very generic type, but the data is being put into Onyx for future use. // Once the data is being used, these types should be defined appropriately. @@ -190,4 +226,4 @@ type Policy = { export default Policy; -export type {Unit, CustomUnit, Attributes, Rate}; +export type {Unit, CustomUnit, Attributes, Rate, TaxRate, TaxRates, TaxRatesWithDefault}; From 76cd4d0787575cecfffbf0be39af9ddd41a52c7c Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 13:35:01 +0100 Subject: [PATCH 151/699] export policy tax rates types from onyx types --- src/types/onyx/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 59cabbe020ee..9c59a89e4040 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -30,12 +30,12 @@ import type {PersonalDetailsList} from './PersonalDetails'; import type PersonalDetails from './PersonalDetails'; import type PlaidData from './PlaidData'; import type Policy from './Policy'; +import type {TaxRate, TaxRates, TaxRatesWithDefault} from './Policy'; import type {PolicyCategories, PolicyCategory} from './PolicyCategory'; import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type {PolicyReportField, PolicyReportFields} from './PolicyReportField'; import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; -import type {PolicyTaxRates, TaxRate, TaxRates} from './PolicyTaxRates'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -108,9 +108,6 @@ export type { PolicyMembers, PolicyTag, PolicyTags, - TaxRate, - TaxRates, - PolicyTaxRates, PolicyTagList, PrivatePersonalDetails, RecentWaypoint, @@ -131,6 +128,9 @@ export type { SecurityGroup, Session, Task, + TaxRate, + TaxRates, + TaxRatesWithDefault, Transaction, TransactionViolation, TransactionViolations, From b8315690f541d4668247ca40e6c45b082d0e716b Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 13:37:48 +0100 Subject: [PATCH 152/699] @update getFiltered options for taxRates --- src/libs/OptionsListUtils.ts | 44 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index cdda08f8497c..051c9a4cbd0f 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -111,8 +111,8 @@ type GetOptionsConfig = { recentlyUsedTags?: string[]; canInviteUser?: boolean; includeSelectedOptions?: boolean; - includePolicyTaxRates?: boolean; - policyTaxRates?: PolicyTaxRates; + includeTaxRates?: boolean; + taxRates?: PolicyTaxRates; transactionViolations?: OnyxCollection; }; @@ -141,7 +141,7 @@ type GetOptions = { currentUserOption: ReportUtils.OptionData | null | undefined; categoryOptions: CategorySection[]; tagOptions: CategorySection[]; - policyTaxRatesOptions: CategorySection[]; + taxRatesOptions: CategorySection[]; }; type PreviewConfig = {showChatPreviewLine?: boolean; forcePolicyNamePreview?: boolean}; @@ -1175,13 +1175,13 @@ function hasEnabledTags(policyTagList: Array /** * Transforms tax rates to a new object format - to add codes and new name with concatenated name and value. * - * @param policyTaxRates - The original tax rates object. + * @param taxRates - The original tax rates object. * @returns The transformed tax rates object.g */ -function transformedTaxRates(policyTaxRates: PolicyTaxRates | undefined): Record { - const defaultTaxKey = policyTaxRates?.defaultExternalID; +function transformedTaxRates(taxRates: PolicyTaxRates | undefined): Record { + const defaultTaxKey = taxRates?.defaultExternalID; const getModifiedName = (data: TaxRate, code: string) => `${data.name} (${data.value})${defaultTaxKey === code ? ` • ${Localize.translateLocal('common.default')}` : ''}`; - const taxes = Object.fromEntries(Object.entries(policyTaxRates?.taxes ?? {}).map(([code, data]) => [code, {...data, code, modifiedName: getModifiedName(data, code), name: data.name}])); + const taxes = Object.fromEntries(Object.entries(taxRates?.taxes ?? {}).map(([code, data]) => [code, {...data, code, modifiedName: getModifiedName(data, code), name: data.name}])); return taxes; } @@ -1210,10 +1210,10 @@ function getTaxRatesOptions(taxRates: Array>): Option[] { /** * Builds the section list for tax rates */ -function getTaxRatesSection(policyTaxRates: PolicyTaxRates | undefined, selectedOptions: Category[], searchInputValue: string): CategorySection[] { +function getTaxRatesSection(taxRates: PolicyTaxRates | undefined, selectedOptions: Category[], searchInputValue: string): CategorySection[] { const policyRatesSections = []; - const taxes = transformedTaxRates(policyTaxRates); + const taxes = transformedTaxRates(taxRates); const sortedTaxRates = sortTaxRates(taxes); const enabledTaxRates = sortedTaxRates.filter((taxRate) => !taxRate.isDisabled); @@ -1352,8 +1352,8 @@ function getOptions( canInviteUser = true, includeSelectedOptions = false, transactionViolations = {}, - includePolicyTaxRates, - policyTaxRates, + includeTaxRates, + taxRates, }: GetOptionsConfig, ): GetOptions { if (includeCategories) { @@ -1366,7 +1366,7 @@ function getOptions( currentUserOption: null, categoryOptions, tagOptions: [], - policyTaxRatesOptions: [], + taxRatesOptions: [], }; } @@ -1380,12 +1380,12 @@ function getOptions( currentUserOption: null, categoryOptions: [], tagOptions, - policyTaxRatesOptions: [], + taxRatesOptions: [], }; } - if (includePolicyTaxRates) { - const policyTaxRatesOptions = getTaxRatesSection(policyTaxRates, selectedOptions as Category[], searchInputValue); + if (includeTaxRates) { + const taxRatesOptions = getTaxRatesSection(taxRates, selectedOptions as Category[], searchInputValue); return { recentReports: [], @@ -1394,7 +1394,7 @@ function getOptions( currentUserOption: null, categoryOptions: [], tagOptions: [], - policyTaxRatesOptions, + taxRatesOptions, }; } @@ -1406,7 +1406,7 @@ function getOptions( currentUserOption: null, categoryOptions: [], tagOptions: [], - policyTaxRatesOptions: [], + taxRatesOptions: [], }; } @@ -1695,7 +1695,7 @@ function getOptions( currentUserOption, categoryOptions: [], tagOptions: [], - policyTaxRatesOptions: [], + taxRatesOptions: [], }; } @@ -1792,8 +1792,8 @@ function getFilteredOptions( recentlyUsedTags: string[] = [], canInviteUser = true, includeSelectedOptions = false, - includePolicyTaxRates = false, - policyTaxRates: PolicyTaxRates = {} as PolicyTaxRates, + includeTaxRates = false, + taxRates: PolicyTaxRates = {} as PolicyTaxRates, ) { return getOptions(reports, personalDetails, { betas, @@ -1813,8 +1813,8 @@ function getFilteredOptions( recentlyUsedTags, canInviteUser, includeSelectedOptions, - includePolicyTaxRates, - policyTaxRates, + includeTaxRates, + taxRates, }); } From 172c8076c953148d81cf66762047c8c4dd741dd0 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 22 Feb 2024 13:38:57 +0100 Subject: [PATCH 153/699] Fix TS issue and update tests --- .../OptionsList/BaseOptionsList.tsx | 2 +- src/components/OptionsList/types.ts | 3 +++ tests/unit/OptionsListUtilsTest.js | 20 ------------------- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/src/components/OptionsList/BaseOptionsList.tsx b/src/components/OptionsList/BaseOptionsList.tsx index 928308c73bfe..6e3effc24e90 100644 --- a/src/components/OptionsList/BaseOptionsList.tsx +++ b/src/components/OptionsList/BaseOptionsList.tsx @@ -182,7 +182,7 @@ function BaseOptionsList( option={item} showTitleTooltip={showTitleTooltip} hoverStyle={optionHoveredStyle} - optionIsFocused={!disableFocusOptions && !isItemDisabled && focusedIndex === index + section.indexOffset} + optionIsFocused={!disableFocusOptions && !isItemDisabled && focusedIndex === index + (section.indexOffset ?? 0)} onSelectRow={onSelectRow} isSelected={isSelected} showSelectedState={canSelectMultipleOptions} diff --git a/src/components/OptionsList/types.ts b/src/components/OptionsList/types.ts index dc455a53690d..c8c117d800e4 100644 --- a/src/components/OptionsList/types.ts +++ b/src/components/OptionsList/types.ts @@ -9,6 +9,9 @@ type Section = { /** Title of the section */ title: string; + /** The initial index of this section given the total number of options in each section's data array */ + indexOffset?: number; + /** Array of options */ data: OptionData[]; diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 00f1307ab59f..cd17b5157059 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -713,7 +713,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: false, - indexOffset: 0, data: [ { text: 'Food', @@ -746,7 +745,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [ { text: 'Food', @@ -771,7 +769,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [], }, ]; @@ -837,7 +834,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: false, - indexOffset: 0, data: [ { text: 'Medical', @@ -852,7 +848,6 @@ describe('OptionsListUtils', () => { { title: 'Recent', shouldShow: true, - indexOffset: 1, data: [ { text: 'Restaurant', @@ -867,7 +862,6 @@ describe('OptionsListUtils', () => { { title: 'All', shouldShow: true, - indexOffset: 2, data: [ { text: 'Cars', @@ -964,7 +958,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [ { text: 'Food', @@ -997,7 +990,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [], }, ]; @@ -1006,7 +998,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: false, - indexOffset: 0, data: [ { text: 'Medical', @@ -1111,7 +1102,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: false, - indexOffset: 0, // data sorted alphabetically by name data: [ { @@ -1142,7 +1132,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [ { text: 'Accounting', @@ -1158,7 +1147,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [], }, ]; @@ -1212,7 +1200,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [ { text: 'Medical', @@ -1226,7 +1213,6 @@ describe('OptionsListUtils', () => { { title: 'Recent', shouldShow: true, - indexOffset: 1, data: [ { text: 'HR', @@ -1240,7 +1226,6 @@ describe('OptionsListUtils', () => { { title: 'All', shouldShow: true, - indexOffset: 2, // data sorted alphabetically by name data: [ { @@ -1299,7 +1284,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [ { text: 'Accounting', @@ -1322,7 +1306,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [], }, ]; @@ -2088,7 +2071,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: false, - indexOffset: 0, // data sorted alphabetically by name data: [ { @@ -2141,7 +2123,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, // data sorted alphabetically by name data: [ { @@ -2165,7 +2146,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [], }, ]; From 45ea252ef358198ebed4bdb5b16bb8f0b45a4e15 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 13:41:10 +0100 Subject: [PATCH 154/699] destructure taxRatesOptions from options utils --- src/components/TaxPicker/index.js | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/components/TaxPicker/index.js b/src/components/TaxPicker/index.js index b83d75f314aa..1e01b0adca93 100644 --- a/src/components/TaxPicker/index.js +++ b/src/components/TaxPicker/index.js @@ -39,27 +39,8 @@ function TaxPicker({selectedTaxRate, policy, insets, onSubmit}) { }, [selectedTaxRate]); const sections = useMemo(() => { - const {policyTaxRatesOptions} = OptionsListUtils.getFilteredOptions( - {}, - {}, - [], - searchValue, - selectedOptions, - [], - false, - false, - false, - {}, - [], - false, - {}, - [], - false, - false, - true, - taxRates, - ); - return policyTaxRatesOptions; + const {taxRatesOptions} = OptionsListUtils.getFilteredOptions({}, {}, [], searchValue, selectedOptions, [], false, false, false, {}, [], false, {}, [], false, false, true, taxRates); + return taxRatesOptions; }, [taxRates, searchValue, selectedOptions]); const selectedOptionKey = lodashGet(_.filter(lodashGet(sections, '[0].data', []), (taxRate) => taxRate.searchText === selectedTaxRate)[0], 'keyForList'); From b3f55a88608c213f90c921591dd358634444dded Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 13:50:16 +0100 Subject: [PATCH 155/699] update OptionsListUtils tax rate types --- src/libs/OptionsListUtils.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 051c9a4cbd0f..22e1176293fd 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -22,12 +22,14 @@ import type { Report, ReportAction, ReportActions, + TaxRate, + TaxRates, + TaxRatesWithDefault, Transaction, TransactionViolation, } from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; -import type {PolicyTaxRates, TaxRate, TaxRates} from '@src/types/onyx/PolicyTaxRates'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import times from '@src/utils/times'; @@ -112,7 +114,7 @@ type GetOptionsConfig = { canInviteUser?: boolean; includeSelectedOptions?: boolean; includeTaxRates?: boolean; - taxRates?: PolicyTaxRates; + taxRates?: TaxRatesWithDefault; transactionViolations?: OnyxCollection; }; @@ -1178,7 +1180,7 @@ function hasEnabledTags(policyTagList: Array * @param taxRates - The original tax rates object. * @returns The transformed tax rates object.g */ -function transformedTaxRates(taxRates: PolicyTaxRates | undefined): Record { +function transformedTaxRates(taxRates: TaxRatesWithDefault | undefined): Record { const defaultTaxKey = taxRates?.defaultExternalID; const getModifiedName = (data: TaxRate, code: string) => `${data.name} (${data.value})${defaultTaxKey === code ? ` • ${Localize.translateLocal('common.default')}` : ''}`; const taxes = Object.fromEntries(Object.entries(taxRates?.taxes ?? {}).map(([code, data]) => [code, {...data, code, modifiedName: getModifiedName(data, code), name: data.name}])); @@ -1210,7 +1212,7 @@ function getTaxRatesOptions(taxRates: Array>): Option[] { /** * Builds the section list for tax rates */ -function getTaxRatesSection(taxRates: PolicyTaxRates | undefined, selectedOptions: Category[], searchInputValue: string): CategorySection[] { +function getTaxRatesSection(taxRates: TaxRatesWithDefault | undefined, selectedOptions: Category[], searchInputValue: string): CategorySection[] { const policyRatesSections = []; const taxes = transformedTaxRates(taxRates); @@ -1793,7 +1795,7 @@ function getFilteredOptions( canInviteUser = true, includeSelectedOptions = false, includeTaxRates = false, - taxRates: PolicyTaxRates = {} as PolicyTaxRates, + taxRates: TaxRatesWithDefault = {} as TaxRatesWithDefault, ) { return getOptions(reports, personalDetails, { betas, From 09364ce2ad7c7cf7ad64063311b8458cc8e65218 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 13:52:44 +0100 Subject: [PATCH 156/699] update TransactionUtils tax rate types --- src/libs/TransactionUtils.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 0291d5e77a8d..484acf62cf0e 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -4,8 +4,7 @@ import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {RecentWaypoint, Report, ReportAction, Transaction, TransactionViolation} from '@src/types/onyx'; -import type {PolicyTaxRates, TaxRate, TaxRates} from '@src/types/onyx/PolicyTaxRates'; +import type {RecentWaypoint, Report, ReportAction, TaxRate, TaxRates, TaxRatesWithDefault, Transaction, TransactionViolation} from '@src/types/onyx'; import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -622,7 +621,7 @@ function getEnabledTaxRateCount(options: TaxRates) { /** * Gets the default tax name */ -function getDefaultTaxName(policyTaxRates: PolicyTaxRates, transaction: Transaction) { +function getDefaultTaxName(policyTaxRates: TaxRatesWithDefault, transaction: Transaction) { const defaultTaxKey = policyTaxRates.defaultExternalID; const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${Localize.translateLocal('common.default')}`) || ''; From 5f123ff141789f82584ba2bae66577a404e8d762 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 13:59:54 +0100 Subject: [PATCH 157/699] update getDefaultTaxName parameter name --- src/libs/TransactionUtils.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 484acf62cf0e..250c5650cd6d 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -621,10 +621,9 @@ function getEnabledTaxRateCount(options: TaxRates) { /** * Gets the default tax name */ -function getDefaultTaxName(policyTaxRates: TaxRatesWithDefault, transaction: Transaction) { - const defaultTaxKey = policyTaxRates.defaultExternalID; - const defaultTaxName = - (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${Localize.translateLocal('common.default')}`) || ''; +function getDefaultTaxName(taxRates: TaxRatesWithDefault, transaction: Transaction) { + const defaultTaxKey = taxRates.defaultExternalID; + const defaultTaxName = (defaultTaxKey && `${taxRates.taxes[defaultTaxKey].name} (${taxRates.taxes[defaultTaxKey].value}) • ${Localize.translateLocal('common.default')}`) || ''; return transaction?.taxRate?.text ?? defaultTaxName; } From 071ff8b5145465c5fbc114bc58c68c96200bfdee Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 14:01:43 +0100 Subject: [PATCH 158/699] update taxRates in policy types --- src/types/onyx/Policy.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 31b2d1ac4b53..7db309194452 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1,7 +1,6 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; -import type {PolicyTaxRates} from './PolicyTaxRates'; type Unit = 'mi' | 'km'; @@ -212,7 +211,7 @@ type Policy = { }; /** Collection of tax rates attached to a policy */ - taxRates?: PolicyTaxRates; + taxRates?: TaxRatesWithDefault; /** ReportID of the admins room for this workspace */ chatReportIDAdmins?: number; From 84c51b06a74820a9e41deb8d8ed3d1acae0ba26a Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 14:07:30 +0100 Subject: [PATCH 159/699] update taxRates name in OptionListUtilsTest --- tests/unit/OptionsListUtilsTest.js | 33 +++++++----------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 00f1307ab59f..7244b7830a29 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -2063,7 +2063,7 @@ describe('OptionsListUtils', () => { const emptySearch = ''; const wrongSearch = 'bla bla'; - const policyTaxRatesWithDefault = { + const taxRatesWithDefault = { name: 'Tax', defaultExternalID: 'CODE1', defaultValue: '0%', @@ -2170,34 +2170,15 @@ describe('OptionsListUtils', () => { }, ]; - const result = OptionsListUtils.getFilteredOptions({}, {}, [], emptySearch, [], [], false, false, false, {}, [], false, {}, [], false, false, true, policyTaxRatesWithDefault); + const result = OptionsListUtils.getFilteredOptions({}, {}, [], emptySearch, [], [], false, false, false, {}, [], false, {}, [], false, false, true, taxRatesWithDefault); - expect(result.policyTaxRatesOptions).toStrictEqual(resultList); + expect(result.taxRatesOptions).toStrictEqual(resultList); - const searchResult = OptionsListUtils.getFilteredOptions({}, {}, [], search, [], [], false, false, false, {}, [], false, {}, [], false, false, true, policyTaxRatesWithDefault); - expect(searchResult.policyTaxRatesOptions).toStrictEqual(searchResultList); + const searchResult = OptionsListUtils.getFilteredOptions({}, {}, [], search, [], [], false, false, false, {}, [], false, {}, [], false, false, true, taxRatesWithDefault); + expect(searchResult.taxRatesOptions).toStrictEqual(searchResultList); - const wrongSearchResult = OptionsListUtils.getFilteredOptions( - {}, - {}, - [], - wrongSearch, - [], - [], - false, - false, - false, - {}, - [], - false, - {}, - [], - false, - false, - true, - policyTaxRatesWithDefault, - ); - expect(wrongSearchResult.policyTaxRatesOptions).toStrictEqual(wrongSearchResultList); + const wrongSearchResult = OptionsListUtils.getFilteredOptions({}, {}, [], wrongSearch, [], [], false, false, false, {}, [], false, {}, [], false, false, true, taxRatesWithDefault); + expect(wrongSearchResult.taxRatesOptions).toStrictEqual(wrongSearchResultList); }); it('formatMemberForList()', () => { From f8b66da13c5fe911a46c53b17d932d83e9b4f106 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 15:19:14 +0100 Subject: [PATCH 160/699] add taxRate and oldTaxRate to ExpenseOriginalMessage --- src/libs/ReportUtils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a36fc095489b..db3568ef34f7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -87,6 +87,8 @@ type ExpenseOriginalMessage = { oldTag?: string; billable?: string; oldBillable?: string; + taxRate?: string; + oldTaxRate?: string; }; type SpendBreakdown = { From 049756b984e4918117b5bfa2ffbbd6b8ee04879f Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 22 Feb 2024 15:32:24 +0100 Subject: [PATCH 161/699] Update tests --- tests/perf-test/OptionsSelector.perf-test.js | 14 +++++--------- tests/perf-test/SelectionList.perf-test.js | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/tests/perf-test/OptionsSelector.perf-test.js b/tests/perf-test/OptionsSelector.perf-test.js index 6104ded05c6a..969015ae0fbc 100644 --- a/tests/perf-test/OptionsSelector.perf-test.js +++ b/tests/perf-test/OptionsSelector.perf-test.js @@ -36,21 +36,17 @@ jest.mock('../../src/components/withNavigationFocus', () => (Component) => { }); const generateSections = (sectionConfigs) => - _.map(sectionConfigs, ({numItems, indexOffset, shouldShow = true}) => ({ + _.map(sectionConfigs, ({numItems, shouldShow = true}, index) => ({ data: Array.from({length: numItems}, (_v, i) => ({ - text: `Item ${i + indexOffset}`, - keyForList: `item-${i + indexOffset}`, + text: `Item ${i + index}`, + keyForList: `item-${i + index}`, })), - indexOffset, shouldShow, })); -const singleSectionSConfig = [{numItems: 1000, indexOffset: 0}]; +const singleSectionSConfig = [{numItems: 1000}]; -const mutlipleSectionsConfig = [ - {numItems: 1000, indexOffset: 0}, - {numItems: 100, indexOffset: 70}, -]; +const mutlipleSectionsConfig = [{numItems: 1000}, {numItems: 100}]; function OptionsSelectorWrapper(args) { const sections = generateSections(singleSectionSConfig); diff --git a/tests/perf-test/SelectionList.perf-test.js b/tests/perf-test/SelectionList.perf-test.js index a109f92a1501..4afdca30313f 100644 --- a/tests/perf-test/SelectionList.perf-test.js +++ b/tests/perf-test/SelectionList.perf-test.js @@ -64,7 +64,6 @@ function SelectionListWrapper(args) { keyForList: `item-${i}`, isSelected: _.contains(selectedIds, `item-${i}`), })), - indexOffset: 0, isDisabled: false, }, ]; From ffbe73a35218775b153099c0544751217fdb2414 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 22 Feb 2024 14:39:27 +0000 Subject: [PATCH 162/699] [TS migration] Migrate awaitStagingDeploysTest to Typescript --- src/types/utils/AsMutable.ts | 5 +++ ...loysTest.js => awaitStagingDeploysTest.ts} | 39 ++++++++++++++----- 2 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 src/types/utils/AsMutable.ts rename tests/unit/{awaitStagingDeploysTest.js => awaitStagingDeploysTest.ts} (83%) diff --git a/src/types/utils/AsMutable.ts b/src/types/utils/AsMutable.ts new file mode 100644 index 000000000000..57c49058cd14 --- /dev/null +++ b/src/types/utils/AsMutable.ts @@ -0,0 +1,5 @@ +import type {Writable} from 'type-fest'; + +const asMutable = (value: T): Writable => value as Writable; + +export default asMutable; diff --git a/tests/unit/awaitStagingDeploysTest.js b/tests/unit/awaitStagingDeploysTest.ts similarity index 83% rename from tests/unit/awaitStagingDeploysTest.js rename to tests/unit/awaitStagingDeploysTest.ts index 8b8327e99047..7f1d6bac1eaa 100644 --- a/tests/unit/awaitStagingDeploysTest.js +++ b/tests/unit/awaitStagingDeploysTest.ts @@ -1,29 +1,46 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + /** * @jest-environment node */ import * as core from '@actions/core'; -import _ from 'underscore'; +import asMutable from '@src/types/utils/AsMutable'; import run from '../../.github/actions/javascript/awaitStagingDeploys/awaitStagingDeploys'; import GithubUtils from '../../.github/libs/GithubUtils'; +type Workflow = { + workflow_id: string; + branch: string; +}; + +type WorkflowStatus = {status: string}; + // Lower poll rate to speed up tests const TEST_POLL_RATE = 1; -const COMPLETED_WORKFLOW = {status: 'completed'}; -const INCOMPLETE_WORKFLOW = {status: 'in_progress'}; +const COMPLETED_WORKFLOW: WorkflowStatus = {status: 'completed'}; +const INCOMPLETE_WORKFLOW: WorkflowStatus = {status: 'in_progress'}; + +type MockListResponse = { + data: { + workflow_runs: WorkflowStatus[]; + }; +}; + +type MockedFunctionListResponse = jest.MockedFunction<() => Promise>; const consoleSpy = jest.spyOn(console, 'log'); const mockGetInput = jest.fn(); -const mockListPlatformDeploysForTag = jest.fn(); -const mockListPlatformDeploys = jest.fn(); -const mockListPreDeploys = jest.fn(); -const mockListWorkflowRuns = jest.fn().mockImplementation((args) => { +const mockListPlatformDeploysForTag: MockedFunctionListResponse = jest.fn(); +const mockListPlatformDeploys: MockedFunctionListResponse = jest.fn(); +const mockListPreDeploys: MockedFunctionListResponse = jest.fn(); +const mockListWorkflowRuns = jest.fn().mockImplementation((args: Workflow) => { const defaultReturn = Promise.resolve({data: {workflow_runs: []}}); - if (!_.has(args, 'workflow_id')) { + if (!args.workflow_id) { return defaultReturn; } - if (!_.isUndefined(args.branch)) { + if (args.branch !== undefined) { return mockListPlatformDeploysForTag(); } @@ -40,7 +57,7 @@ const mockListWorkflowRuns = jest.fn().mockImplementation((args) => { beforeAll(() => { // Mock core module - core.getInput = mockGetInput; + asMutable(core).getInput = mockGetInput; // Mock octokit module const moctokit = { @@ -50,6 +67,8 @@ beforeAll(() => { }, }, }; + + // @ts-expect-error TODO: Remove this once GithubUtils (https://github.com/Expensify/App/issues/25382) is migrated to TypeScript. GithubUtils.internalOctokit = moctokit; GithubUtils.POLL_RATE = TEST_POLL_RATE; }); From 2527914bfadfc75030084c691b337b1e1363eea0 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 22 Feb 2024 16:10:50 +0100 Subject: [PATCH 163/699] add taxRate system message --- src/libs/ModifiedExpenseMessage.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 13a58834860b..e2d0741eca43 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -115,6 +115,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr 'currency' in reportActionOriginalMessage; const hasModifiedMerchant = reportActionOriginalMessage && 'oldMerchant' in reportActionOriginalMessage && 'merchant' in reportActionOriginalMessage; + if (hasModifiedAmount) { const oldCurrency = reportActionOriginalMessage?.oldCurrency ?? ''; const oldAmountValue = reportActionOriginalMessage?.oldAmount ?? 0; @@ -214,6 +215,19 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr }); } + const hasModifiedTaxRate = reportActionOriginalMessage && 'oldTaxRate' in reportActionOriginalMessage && 'taxRate' in reportActionOriginalMessage; + if (hasModifiedTaxRate) { + buildMessageFragmentForValue( + reportActionOriginalMessage?.taxRate ?? '', + reportActionOriginalMessage?.oldTaxRate ?? '', + Localize.translateLocal('iou.taxRate'), + true, + setFragments, + removalFragments, + changeFragments, + ); + } + const hasModifiedBillable = reportActionOriginalMessage && 'oldBillable' in reportActionOriginalMessage && 'billable' in reportActionOriginalMessage; if (hasModifiedBillable) { buildMessageFragmentForValue( From a6faef416ad62d6e0c58df96dce26ce1ca1ac7b7 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 26 Feb 2024 18:17:58 +0530 Subject: [PATCH 164/699] Refactor StateSelectionPage with more explanatory comments --- .../PersonalDetails/StateSelectionPage.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 7fe667f5b9ea..804d3de6bf13 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -60,17 +60,22 @@ function StateSelectionPage() { (option: CountryData) => { const backTo = params?.backTo ?? ''; - // Check the navigation state and "backTo" parameter to decide navigation behavior - if (navigation.getState().routes.length === 1 && _.isEmpty(backTo)) { - // If there is only one route and "backTo" is empty, go back in navigation - Navigation.goBack(); - } else if (!_.isEmpty(backTo) && navigation.getState().routes.length === 1) { - // If "backTo" is not empty and there is only one route, go back to the specific route defined in "backTo" with a country parameter - Navigation.goBack(appendParam(backTo, 'state', option.value) as Route); + // Determine navigation action based on "backTo" presence and route stack length. + if (navigation.getState().routes.length === 1) { + // If this is the only page in the navigation stack (examples include direct navigation to this page via URL or page reload). + if (_.isEmpty(backTo)) { + // No "backTo": default back navigation. + Navigation.goBack(); + } else { + // "backTo" provided: navigate back to "backTo" with state parameter. + Navigation.goBack(appendParam(backTo, 'state', option.value) as Route); + } } else if (!_.isEmpty(backTo)) { - // Otherwise, navigate to the specific route defined in "backTo" with a country parameter + // Most common case: Navigation stack has multiple routes and "backTo" is defined: navigate to "backTo" with state parameter. Navigation.navigate(appendParam(backTo, 'state', option.value) as Route); } else { + // This is a fallback block and should never execute if StateSelector is correctly appending the "backTo" route. + // Navigation stack has multiple routes but no "backTo" defined: default back navigation. Navigation.goBack(); } }, From c0cc219b0a3c16b888822087f624dd50184f711d Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 26 Feb 2024 16:53:00 +0100 Subject: [PATCH 165/699] fix lint --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 1ecb22c99908..fa8d0d51990c 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -194,7 +194,6 @@ const defaultProps = { policy: {}, policyCategories: {}, policyTags: {}, - policy: {}, transactionID: '', transaction: {}, mileageRate: {unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: 0, currency: 'USD'}, From 452d012b89174e0e03ede3d6abd5448217c99d09 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Mon, 26 Feb 2024 22:47:47 -0800 Subject: [PATCH 166/699] add transactionThreadReportID to report structure for onyx and props --- src/pages/reportPropTypes.js | 3 +++ src/types/onyx/Report.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js index 3a056ee7c0a3..7422bad8061f 100644 --- a/src/pages/reportPropTypes.js +++ b/src/pages/reportPropTypes.js @@ -73,4 +73,7 @@ export default PropTypes.shape({ /** Custom fields attached to the report */ reportFields: PropTypes.objectOf(PropTypes.string), + + /** ID of the transaction thread associated with the report, if any */ + transactionThreadReportID: PropTypes.string, }); diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index bb86d2cf4ae4..31ee82d6d795 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -170,6 +170,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** If the report contains reportFields, save the field id and its value */ reportFields?: Record; + + /** The ID of the single transaction thread report associated with this report, if one exists */ + transactionThreadReportID?: string }, PolicyReportField['fieldID'] >; From cac6d81a379c9e5be9b55224e4279b2e959fde1c Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Mon, 26 Feb 2024 22:50:31 -0800 Subject: [PATCH 167/699] simplify transactionThreadReportID logic to use value returned in the report from auth --- src/libs/ReportUtils.ts | 35 +++++++---------------- src/pages/home/ReportScreen.js | 7 ++--- src/pages/home/report/ReportActionItem.js | 5 +--- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e622ebcbbc53..ccc8d4b5d5ec 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1201,33 +1201,20 @@ function isMoneyRequestReport(reportOrID: OnyxEntry | string): boolean { /** * Checks if a report has only one transaction associated with it */ -function isOneTransactionReport(reportOrID: OnyxEntry | string): boolean { - const report = typeof reportOrID === 'object' ? reportOrID : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null; - - // Check the parent report (which would be the IOU or expense report if the passed report is an IOU or expense request) - // to see how many IOU report actions it contains - const iouReportActions = ReportActionsUtils.getIOUReportActions(report?.reportID ?? ''); - return (iouReportActions?.length ?? 0) === 1; -} - -/** - * Returns the reportID of the first transaction thread associated with a report - */ -function getOneTransactionThreadReportID(reportOrID: OnyxEntry | string): string | undefined { - const report = typeof reportOrID === 'object' ? reportOrID : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null; - - // Get all IOU report actions for the report. - const iouReportAction = ReportActionsUtils.getIOUReportActions(report?.reportID ?? '')?.find(reportAction => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.childReportID); - return iouReportAction ? String(iouReportAction.childReportID) : '0' +function isOneTransactionReport(report: OnyxEntry): boolean { + return report?.transactionThreadReportID !== undefined; } /** * Checks if a report is a transaction thread associated with a report that has only one transaction */ -function isOneTransactionThread(reportOrID: OnyxEntry | string): boolean { - const report = typeof reportOrID === 'object' ? reportOrID : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null; - const parentReport = getParentReport(report); - return isOneTransactionReport(parentReport?.reportID ?? ''); +function isOneTransactionThread(reportID: string, parentReport: OnyxEntry | EmptyObject): boolean { + if (isEmptyObject(parentReport)) { + return false; + } + + const transactionThreadReportID = parentReport?.transactionThreadReportID ?? undefined; + return reportID === transactionThreadReportID; } /** @@ -3967,7 +3954,8 @@ function shouldReportBeInOptionList({ } // If this is a transaction thread associated with a report that only has one transaction, omit it - if (isOneTransactionThread(report)) { + const parentReport = getParentReport(report); + if (isOneTransactionThread(report.reportID, parentReport)) { return false; } @@ -5206,7 +5194,6 @@ export { getReportRecipientAccountIDs, isOneOnOneChat, isOneTransactionReport, - getOneTransactionThreadReportID, isOneTransactionThread, goBackToDetailsPage, getTransactionReportName, diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index f281843a7052..f3b12cd5c898 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -212,6 +212,7 @@ function ReportScreen({ policyName: reportProp.policyName, isOptimisticReport: reportProp.isOptimisticReport, lastMentionedTime: reportProp.lastMentionedTime, + transactionThreadReportID: reportProp.transactionThreadReportID }), [ reportProp.lastReadTime, @@ -617,11 +618,7 @@ export default compose( selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), }, transactionThreadReportActions: { - key: ({route}) => { - const reportID = getReportID(route); - const transactionThreadReportID = reportID && ReportUtils.isOneTransactionReport(reportID) ? ReportUtils.getOneTransactionThreadReportID(reportID) : '0'; - return `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}` - }, + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.transactionThreadReportID : 0}`, canEvict: false, selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), }, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 7c0b5ec57266..444badac0600 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -878,10 +878,7 @@ export default compose( initialValue: {}, }, transactionThreadReport: { - key: ({report}) => { - const transactionThreadReportID = ReportUtils.isOneTransactionReport(report) ? ReportUtils.getOneTransactionThreadReportID(report) : ''; - return transactionThreadReportID ? `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}` : undefined; - }, + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.transactionThreadReportID || 0}`, initialValue: {}, }, policyReportFields: { From 6a08ba55ac4fa485b9fc605f7ae1c2e4781abcc6 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Mon, 26 Feb 2024 22:57:06 -0800 Subject: [PATCH 168/699] minor style --- src/libs/ReportActionsUtils.ts | 5 ++--- src/pages/home/ReportScreen.js | 8 ++++++-- src/types/onyx/Report.ts | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index f909823767cd..47323ad8cd56 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -533,15 +533,14 @@ function getSortedReportActionsForDisplay(reportActions: ReportActions | ReportA * are ready for display in the ReportActionView. */ function getCombinedReportActionsForDisplay(reportActions: ReportAction[], transactionThreadReportActions: ReportAction[]): ReportAction[] { - // Filter out the created action from the transaction thread report actions, since we already have the parent report's created action - const filteredTransactionThreadReportActions = transactionThreadReportActions?.filter(action => action.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED); + const filteredTransactionThreadReportActions = transactionThreadReportActions?.filter((action) => action.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED); // Sort the combined list of parent report actions and transaction thread report actions const sortedReportActions = getSortedReportActions([...reportActions, ...filteredTransactionThreadReportActions], true); // Filter out IOU report actions because we don't want to show any preview actions for one transaction reports - return sortedReportActions.filter(action => action.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU); + return sortedReportActions.filter((action) => action.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU); } /** diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index f3b12cd5c898..fe622c5ccccb 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -212,7 +212,7 @@ function ReportScreen({ policyName: reportProp.policyName, isOptimisticReport: reportProp.isOptimisticReport, lastMentionedTime: reportProp.lastMentionedTime, - transactionThreadReportID: reportProp.transactionThreadReportID + transactionThreadReportID: reportProp.transactionThreadReportID, }), [ reportProp.lastReadTime, @@ -564,7 +564,11 @@ function ReportScreen({ > {isReportReadyForDisplay && !isLoadingInitialReportActions && !isLoading && ( ; /** The ID of the single transaction thread report associated with this report, if one exists */ - transactionThreadReportID?: string + transactionThreadReportID?: string; }, PolicyReportField['fieldID'] >; From 2317ae5055e79e2f1800c1e39fe196ffc6e5418d Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 27 Feb 2024 19:48:44 +0100 Subject: [PATCH 169/699] Refactore code and add new utilit for sections --- src/components/OptionsList/BaseOptionsList.tsx | 7 ++----- src/components/SelectionList/BaseSelectionList.tsx | 8 +++----- src/libs/getSectionsWithIndexOffset.ts | 9 +++++++++ 3 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 src/libs/getSectionsWithIndexOffset.ts diff --git a/src/components/OptionsList/BaseOptionsList.tsx b/src/components/OptionsList/BaseOptionsList.tsx index 6e3effc24e90..50e0ce31fcc2 100644 --- a/src/components/OptionsList/BaseOptionsList.tsx +++ b/src/components/OptionsList/BaseOptionsList.tsx @@ -9,6 +9,7 @@ import SectionList from '@components/SectionList'; import Text from '@components/Text'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; +import getSectionsWithIndexOffset from '@libs/getSectionsWithIndexOffset'; import type {OptionData} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; import variables from '@styles/variables'; @@ -67,6 +68,7 @@ function BaseOptionsList( const listContainerStyles = useMemo(() => listContainerStylesProp ?? [styles.flex1], [listContainerStylesProp, styles.flex1]); const contentContainerStyles = useMemo(() => [safeAreaPaddingBottomStyle, contentContainerStylesProp], [contentContainerStylesProp, safeAreaPaddingBottomStyle]); + const sectionsWithIndexOffset = getSectionsWithIndexOffset(sections); /** * This helper function is used to memoize the computation needed for getItemLayout. It is run whenever section data changes. @@ -223,11 +225,6 @@ function BaseOptionsList( return ; }; - const sectionsWithIndexOffset = sections.map((section, index) => { - const indexOffset = [...sections].splice(0, index).reduce((acc, curr) => acc + curr.data.length, 0); - return {...section, indexOffset}; - }); - return ( {isLoading ? ( diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index fbaa5f77e9d0..1cb86e58123c 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -18,6 +18,7 @@ import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; +import getSectionsWithIndexOffset from '@libs/getSectionsWithIndexOffset'; import Log from '@libs/Log'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -78,6 +79,8 @@ function BaseSelectionList( const [maxToRenderPerBatch, setMaxToRenderPerBatch] = useState(shouldUseDynamicMaxToRenderPerBatch ? 0 : CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT); const [isInitialSectionListRender, setIsInitialSectionListRender] = useState(true); + const sectionsWithIndexOffset = getSectionsWithIndexOffset(sections); + /** * Iterates through the sections and items inside each section, and builds 3 arrays along the way: * - `allOptions`: Contains all the items in the list, flattened, regardless of section @@ -377,11 +380,6 @@ function BaseSelectionList( isActive: !disableKeyboardShortcuts && isFocused, }); - const sectionsWithIndexOffset = sections.map((section, index) => { - const indexOffset = [...sections].splice(0, index).reduce((acc, curr) => acc + curr.data.length, 0); - return {...section, indexOffset}; - }); - return ( (sections: Array>) { + return sections.map((section, index) => { + const indexOffset = [...sections].splice(0, index).reduce((acc, curr) => acc + curr.data.length, 0); + return {...section, indexOffset}; + }); +} From 279cf4a5db74ecd7abea1787fbcfd8cc231e0af6 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 27 Feb 2024 21:35:01 +0100 Subject: [PATCH 170/699] Update getSectionsWithIndexOffset --- src/libs/getSectionsWithIndexOffset.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/getSectionsWithIndexOffset.ts b/src/libs/getSectionsWithIndexOffset.ts index 522412ab0949..e6c0820374e8 100644 --- a/src/libs/getSectionsWithIndexOffset.ts +++ b/src/libs/getSectionsWithIndexOffset.ts @@ -3,7 +3,7 @@ import type {SectionListData} from 'react-native'; /** Returns a list of sections with IndexOffset */ export default function getSectionsWithIndexOffset(sections: Array>) { return sections.map((section, index) => { - const indexOffset = [...sections].splice(0, index).reduce((acc, curr) => acc + curr.data.length, 0); + const indexOffset = [...sections].splice(0, index).reduce((acc, curr) => acc + (curr.data?.length ?? 0), 0); return {...section, indexOffset}; }); } From 7eb76d666f4e0fbd2c6932cacb36931a4273020c Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 27 Feb 2024 21:39:23 +0100 Subject: [PATCH 171/699] Remove unnecessary code --- src/components/SelectionList/BaseSelectionList.tsx | 1 - src/pages/workspace/categories/WorkspaceCategoriesPage.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 1cb86e58123c..f3d7bc92df13 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -78,7 +78,6 @@ function BaseSelectionList( const isFocused = useIsFocused(); const [maxToRenderPerBatch, setMaxToRenderPerBatch] = useState(shouldUseDynamicMaxToRenderPerBatch ? 0 : CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT); const [isInitialSectionListRender, setIsInitialSectionListRender] = useState(true); - const sectionsWithIndexOffset = getSectionsWithIndexOffset(sections); /** diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 7cd9972a6f57..a41d6158303f 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -128,7 +128,7 @@ function WorkspaceCategoriesPage({policyCategories, route}: WorkspaceCategoriesP {categoryList.length ? ( Date: Wed, 28 Feb 2024 05:28:16 +0700 Subject: [PATCH 172/699] feature: empty state ui for lhn --- src/components/BlockingViews/BlockingView.tsx | 63 +++++++++++++++---- .../LHNOptionsList/LHNOptionsList.tsx | 58 +++++++++++++++++ src/languages/en.ts | 6 ++ src/languages/es.ts | 6 ++ src/styles/index.ts | 9 +++ src/styles/variables.ts | 2 + 6 files changed, 133 insertions(+), 11 deletions(-) diff --git a/src/components/BlockingViews/BlockingView.tsx b/src/components/BlockingViews/BlockingView.tsx index 7b33c8054950..f83deaf1477c 100644 --- a/src/components/BlockingViews/BlockingView.tsx +++ b/src/components/BlockingViews/BlockingView.tsx @@ -1,9 +1,11 @@ import React from 'react'; -import type {ImageSourcePropType} from 'react-native'; +import type {ImageSourcePropType, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {SvgProps} from 'react-native-svg'; import AutoEmailLink from '@components/AutoEmailLink'; import Icon from '@components/Icon'; +import Lottie from '@components/Lottie'; +import type DotLottieAnimation from '@components/LottieAnimations/types'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; @@ -12,10 +14,29 @@ import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; import type {TranslationPaths} from '@src/languages/types'; -type BlockingViewProps = { - /** Expensicon for the page */ - icon: React.FC | ImageSourcePropType; +type RequiredIllustrationProps = + | { + /** Expensicon for the page */ + icon: React.FC | ImageSourcePropType; + /** + * Animation for the page + * If icon is provided, animation is not required + */ + animation?: DotLottieAnimation; + } + | { + /** Animation for the page */ + animation: DotLottieAnimation; + + /** + * Expensicon for the page + * If animation is provided, icon is not required + */ + icon?: React.FC | ImageSourcePropType; + }; + +type BlockingViewProps = RequiredIllustrationProps & { /** Color for the icon (should be from theme) */ iconColor?: string; @@ -42,9 +63,16 @@ type BlockingViewProps = { /** Whether we should embed the link with subtitle */ shouldEmbedLinkWithSubtitle?: boolean; + + /** Style for the animation */ + animationStyles?: StyleProp; + + /** Render custom subtitle */ + renderSubtitle?: () => React.ReactElement; }; function BlockingView({ + animation, icon, iconColor, title, @@ -55,6 +83,8 @@ function BlockingView({ iconHeight = variables.iconSizeSuperLarge, onLinkPress = () => Navigation.dismissModal(), shouldEmbedLinkWithSubtitle = false, + animationStyles = [], + renderSubtitle, }: BlockingViewProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -79,16 +109,27 @@ function BlockingView({ return ( - + {animation && ( + + )} + {icon && ( + + )} {title} - {shouldEmbedLinkWithSubtitle ? ( + {renderSubtitle ? ( + renderSubtitle() + ) : shouldEmbedLinkWithSubtitle ? ( {renderContent()} ) : ( {renderContent()} diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 27f424ad1b70..18832440082b 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -3,9 +3,17 @@ import type {ReactElement} from 'react'; import React, {memo, useCallback} from 'react'; import {StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import BlockingView from '@components/BlockingViews/BlockingView'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import LottieAnimations from '@components/LottieAnimations'; +import Text from '@components/Text'; import withCurrentReportID from '@components/withCurrentReportID'; +import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -33,8 +41,11 @@ function LHNOptionsList({ transactionViolations = {}, onFirstItemRendered = () => {}, }: LHNOptionsListProps) { + const theme = useTheme(); const styles = useThemeStyles(); const {canUseViolations} = usePermissions(); + const {translate} = useLocalize(); + const {isSmallScreenWidth} = useWindowDimensions(); // When the first item renders we want to call the onFirstItemRendered callback. // At this point in time we know that the list is actually displaying items. @@ -48,6 +59,40 @@ function LHNOptionsList({ onFirstItemRendered(); }, [onFirstItemRendered]); + const renderEmptyStateSubtitle = useCallback( + () => ( + + + {translate('common.emptyLHN.subtitleText1')} + + {translate('common.emptyLHN.subtitleText2')} + + {translate('common.emptyLHN.subtitleText3')} + + + ), + [theme, styles.dFlex, styles.gap1, styles.alignItemsCenter, styles.justifyContentCenter, styles.textAlignCenter, translate], + ); + /** * Function which renders a row in the list */ @@ -125,6 +170,19 @@ function LHNOptionsList({ estimatedItemSize={optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight} extraData={[currentReportID]} showsVerticalScrollIndicator={false} + ListEmptyComponent={ + isSmallScreenWidth ? ( + + + + ) : null + } /> ); diff --git a/src/languages/en.ts b/src/languages/en.ts index 4d7041d4a791..1fbc9ac0e198 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -312,6 +312,12 @@ export default { update: 'Update', member: 'Member', role: 'Role', + emptyLHN: { + title: 'Woohoo! All caught up.', + subtitleText1: 'Find a chat using the', + subtitleText2: 'button above, or create something using the', + subtitleText3: 'button below.', + }, }, location: { useCurrent: 'Use current location', diff --git a/src/languages/es.ts b/src/languages/es.ts index c9ff087d0de7..d42d0aa1faf2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -302,6 +302,12 @@ export default { update: 'Actualizar', member: 'Miembro', role: 'Role', + emptyLHN: { + title: 'Woohoo! Todo al día.', + subtitleText1: 'Encuentra un chat usando el botón', + subtitleText2: 'o crea algo usando el botón', + subtitleText3: '.', + }, }, location: { useCurrent: 'Usar ubicación actual', diff --git a/src/styles/index.ts b/src/styles/index.ts index 32392d62e5c4..3bce9d90c8ea 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3166,6 +3166,15 @@ const styles = (theme: ThemeColors) => alignItems: 'center', }, + emptyLHNAnimation: { + width: 180, + height: 180, + }, + + emptyLHNBlockingView: { + marginTop: 120, + }, + locationErrorLinkText: { textAlignVertical: 'center', fontSize: variables.fontSizeLabel, diff --git a/src/styles/variables.ts b/src/styles/variables.ts index f7c9bd055041..2f8ecac14c32 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -121,6 +121,8 @@ export default { avatarChatSpacing: 12, chatInputSpacing: 52, // 40 + avatarChatSpacing borderTopWidth: 1, + emptyLHNIconWidth: 24, // iconSizeSmall + 4*2 horizontal margin + emptyLHNIconHeight: 16, emptyWorkspaceIconWidth: 84, emptyWorkspaceIconHeight: 84, modalTopIconWidth: 200, From 16747350e4bbb97facecc84c129bb60d476afc63 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 27 Feb 2024 14:40:53 -0800 Subject: [PATCH 173/699] don't show report if transaction thread and expense report have the same currency minor style --- src/pages/home/ReportScreen.js | 1 + src/pages/home/report/ReportActionItem.js | 39 +++++++++++++++-------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index fe622c5ccccb..620ad4e23586 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -250,6 +250,7 @@ function ReportScreen({ reportProp.policyName, reportProp.isOptimisticReport, reportProp.lastMentionedTime, + reportProp.transactionThreadReportID, ], ); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 444badac0600..3baba4e2ea22 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -701,20 +701,31 @@ function ReportActionItem(props) { if (ReportUtils.isExpenseReport(props.report) || ReportUtils.isIOUReport(props.report)) { return ( - - {props.transactionThreadReport && !isEmptyObject(props.transactionThreadReport) && ( - - - + {props.transactionThreadReport && !isEmptyObject(props.transactionThreadReport) ? ( + <> + {props.transactionThreadReport.currency !== props.report.currency && ( + + )} + + + + + ) : ( + )} ); From 0fd03c00e0d9e3938490ac91ca9c133f55cb39ce Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 01:24:49 +0100 Subject: [PATCH 174/699] add taxAmount and oldTaxAmount types to ExpenseOriginalMessage --- src/libs/ReportUtils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7c4cfbbb0cb0..63d1dd019db2 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -88,6 +88,8 @@ type ExpenseOriginalMessage = { oldTag?: string; billable?: string; oldBillable?: string; + oldTaxAmount?: number; + taxAmount?: number; taxRate?: string; oldTaxRate?: string; }; From 8b212f594840864fda7ea67c8aae601291e5a50e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 01:25:47 +0100 Subject: [PATCH 175/699] add taxAmount edit request system message --- src/libs/ModifiedExpenseMessage.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index b132c66c84c6..fb5cb7756804 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -215,6 +215,14 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr } }); } + + const hasModifiedTaxAmount = reportActionOriginalMessage && 'oldTaxAmount' in reportActionOriginalMessage && 'taxAmount' in reportActionOriginalMessage; + if (hasModifiedTaxAmount) { + const taxAmount = CurrencyUtils.convertToDisplayString(reportActionOriginalMessage?.amount ?? 0); + const oldTaxAmountValue = reportActionOriginalMessage?.oldAmount ?? 0; + const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(reportActionOriginalMessage?.oldTaxAmount ?? 0) : ''; + buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), true, setFragments, removalFragments, changeFragments); + } const hasModifiedTaxRate = reportActionOriginalMessage && 'oldTaxRate' in reportActionOriginalMessage && 'taxRate' in reportActionOriginalMessage; if (hasModifiedTaxRate) { From 90bc36177c0e4a5405e9620f1ae7cadafd2c1bcb Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 27 Feb 2024 16:37:32 -0800 Subject: [PATCH 176/699] update default value for transactionThreadReportActions key --- src/pages/home/ReportScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 620ad4e23586..d0d74274a14e 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -623,7 +623,7 @@ export default compose( selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), }, transactionThreadReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.transactionThreadReportID : 0}`, + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? (report.transactionThreadReportID || '0') : '0'}`, canEvict: false, selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), }, From f05fdfeab61a771569c17668105c794199e72da6 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 02:43:20 +0100 Subject: [PATCH 177/699] use taxAmount --- src/libs/ModifiedExpenseMessage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index fb5cb7756804..0a8df556405c 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -218,9 +218,9 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr const hasModifiedTaxAmount = reportActionOriginalMessage && 'oldTaxAmount' in reportActionOriginalMessage && 'taxAmount' in reportActionOriginalMessage; if (hasModifiedTaxAmount) { - const taxAmount = CurrencyUtils.convertToDisplayString(reportActionOriginalMessage?.amount ?? 0); + const taxAmount = CurrencyUtils.convertToDisplayString(reportActionOriginalMessage?.taxAmount ?? 0); const oldTaxAmountValue = reportActionOriginalMessage?.oldAmount ?? 0; - const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(reportActionOriginalMessage?.oldTaxAmount ?? 0) : ''; + const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(oldTaxAmountValue) : ''; buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), true, setFragments, removalFragments, changeFragments); } From 5f966270ce681e62adabf5964de09f03cb3d58a3 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 02:45:16 +0100 Subject: [PATCH 178/699] add allReports from Onyx --- src/libs/ModifiedExpenseMessage.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 0a8df556405c..4ced8f0b6cf7 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -3,6 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyTagList, ReportAction} from '@src/types/onyx'; +import type * as OnyxTypes from '@src/types/onyx'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; @@ -24,6 +25,13 @@ Onyx.connect({ }, }); +let allReports: OnyxCollection = null; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => (allReports = value), +}); + /** * Builds the partial message fragment for a modified field on the expense. */ @@ -215,7 +223,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr } }); } - + const hasModifiedTaxAmount = reportActionOriginalMessage && 'oldTaxAmount' in reportActionOriginalMessage && 'taxAmount' in reportActionOriginalMessage; if (hasModifiedTaxAmount) { const taxAmount = CurrencyUtils.convertToDisplayString(reportActionOriginalMessage?.taxAmount ?? 0); From d04d366ae7a5261b1e33e059f7b9fa49bdf74588 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 02:47:24 +0100 Subject: [PATCH 179/699] add getTaxAmount Func --- src/libs/ModifiedExpenseMessage.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 4ced8f0b6cf7..9630c5220d99 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -99,6 +99,20 @@ function getForDistanceRequest(newDistance: string, oldDistance: string, newAmou }); } +/** + * Return the tax amount field from the transaction. + */ +function getTaxAmount(taxAmount: number, isFromExpenseReport: boolean): number { + // IOU requests cannot have negative values but they can be stored as negative values, let's return absolute value + if (!isFromExpenseReport) { + return Math.abs(taxAmount ?? 0); + } + + // To avoid -0 being shown, lets only change the sign if the value is other than 0. + const amount = taxAmount ?? 0; + return amount ? -amount : 0; +} + /** * Get the report action message when expense has been modified. * From 91aa0365088d16c637169711ef95aad6f545260e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 02:51:46 +0100 Subject: [PATCH 180/699] use getTaxAmount for taxAmount --- src/libs/ModifiedExpenseMessage.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 9630c5220d99..9c398f33852a 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -240,7 +240,11 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr const hasModifiedTaxAmount = reportActionOriginalMessage && 'oldTaxAmount' in reportActionOriginalMessage && 'taxAmount' in reportActionOriginalMessage; if (hasModifiedTaxAmount) { - const taxAmount = CurrencyUtils.convertToDisplayString(reportActionOriginalMessage?.taxAmount ?? 0); + const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread?.parentReportID}`] ?? null; + const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); + + const taxAmount = CurrencyUtils.convertToDisplayString(getTaxAmount(reportActionOriginalMessage?.taxAmount ?? 0, isFromExpenseReport)); const oldTaxAmountValue = reportActionOriginalMessage?.oldAmount ?? 0; const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(oldTaxAmountValue) : ''; buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), true, setFragments, removalFragments, changeFragments); From dc5fc19ced1cbeade62726c18d58678728a0f5c0 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 02:52:41 +0100 Subject: [PATCH 181/699] use getTaxAmount for oldTaxAmount --- src/libs/ModifiedExpenseMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 9c398f33852a..75bbf412012f 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -246,7 +246,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr const taxAmount = CurrencyUtils.convertToDisplayString(getTaxAmount(reportActionOriginalMessage?.taxAmount ?? 0, isFromExpenseReport)); const oldTaxAmountValue = reportActionOriginalMessage?.oldAmount ?? 0; - const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(oldTaxAmountValue) : ''; + const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(getTaxAmount(oldTaxAmountValue, isFromExpenseReport)) : ''; buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), true, setFragments, removalFragments, changeFragments); } From 70f9cde696ab478c08233c8e2a138951f594d714 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 02:54:14 +0100 Subject: [PATCH 182/699] format string with currency for taxAmount --- src/libs/ModifiedExpenseMessage.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 75bbf412012f..369090070c88 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -243,8 +243,9 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread?.parentReportID}`] ?? null; const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); + const currency = iouReport?.currency ?? ''; - const taxAmount = CurrencyUtils.convertToDisplayString(getTaxAmount(reportActionOriginalMessage?.taxAmount ?? 0, isFromExpenseReport)); + const taxAmount = CurrencyUtils.convertToDisplayString(getTaxAmount(reportActionOriginalMessage?.taxAmount ?? 0, isFromExpenseReport), currency); const oldTaxAmountValue = reportActionOriginalMessage?.oldAmount ?? 0; const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(getTaxAmount(oldTaxAmountValue, isFromExpenseReport)) : ''; buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), true, setFragments, removalFragments, changeFragments); From 184a4e19c3ea041a3fab8f40840fd9dc802d537e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 02:54:52 +0100 Subject: [PATCH 183/699] format string with currency for oldTaxAmount --- src/libs/ModifiedExpenseMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 369090070c88..c3ca42729bfb 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -247,7 +247,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr const taxAmount = CurrencyUtils.convertToDisplayString(getTaxAmount(reportActionOriginalMessage?.taxAmount ?? 0, isFromExpenseReport), currency); const oldTaxAmountValue = reportActionOriginalMessage?.oldAmount ?? 0; - const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(getTaxAmount(oldTaxAmountValue, isFromExpenseReport)) : ''; + const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(getTaxAmount(oldTaxAmountValue, isFromExpenseReport), currency) : ''; buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), true, setFragments, removalFragments, changeFragments); } From 55886f318345654356ba89d081ebe31d6173fc73 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 03:01:39 +0100 Subject: [PATCH 184/699] set valueInQuotes to false --- src/libs/ModifiedExpenseMessage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index c3ca42729bfb..1d24a403c864 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -248,7 +248,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr const taxAmount = CurrencyUtils.convertToDisplayString(getTaxAmount(reportActionOriginalMessage?.taxAmount ?? 0, isFromExpenseReport), currency); const oldTaxAmountValue = reportActionOriginalMessage?.oldAmount ?? 0; const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(getTaxAmount(oldTaxAmountValue, isFromExpenseReport), currency) : ''; - buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), true, setFragments, removalFragments, changeFragments); + buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), false, setFragments, removalFragments, changeFragments); } const hasModifiedTaxRate = reportActionOriginalMessage && 'oldTaxRate' in reportActionOriginalMessage && 'taxRate' in reportActionOriginalMessage; @@ -257,7 +257,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr reportActionOriginalMessage?.taxRate ?? '', reportActionOriginalMessage?.oldTaxRate ?? '', Localize.translateLocal('iou.taxRate'), - true, + false, setFragments, removalFragments, changeFragments, From 9d9703aca82b3880f2d5b568fa6ed6440688b5e7 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 03:03:37 +0100 Subject: [PATCH 185/699] fix duplicate import --- src/libs/ModifiedExpenseMessage.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 1d24a403c864..05803702f310 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -2,8 +2,7 @@ import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyTagList, ReportAction} from '@src/types/onyx'; -import type * as OnyxTypes from '@src/types/onyx'; +import type {PolicyTagList, Report, ReportAction} from '@src/types/onyx'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; @@ -25,7 +24,7 @@ Onyx.connect({ }, }); -let allReports: OnyxCollection = null; +let allReports: OnyxCollection = null; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, From e8e279b7fad8a11b8702bf34186683327b445892 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 03:12:16 +0100 Subject: [PATCH 186/699] handle oldTaxAmount --- src/libs/ModifiedExpenseMessage.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 05803702f310..a6491d3025fc 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -245,8 +245,9 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr const currency = iouReport?.currency ?? ''; const taxAmount = CurrencyUtils.convertToDisplayString(getTaxAmount(reportActionOriginalMessage?.taxAmount ?? 0, isFromExpenseReport), currency); - const oldTaxAmountValue = reportActionOriginalMessage?.oldAmount ?? 0; - const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(getTaxAmount(oldTaxAmountValue, isFromExpenseReport), currency) : ''; + const oldTaxAmountValue = getTaxAmount(reportActionOriginalMessage?.oldTaxAmount ?? 0, isFromExpenseReport); + const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(oldTaxAmountValue, currency) : ''; + buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), false, setFragments, removalFragments, changeFragments); } From ec0cc2775357d90870636edc2bee226ba3c86eeb Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 28 Feb 2024 14:17:01 +0700 Subject: [PATCH 187/699] middle align UI --- src/components/BlockingViews/BlockingView.tsx | 6 ++++ .../LHNOptionsList/LHNOptionsList.tsx | 30 +++++++++---------- src/styles/index.ts | 7 +++-- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/components/BlockingViews/BlockingView.tsx b/src/components/BlockingViews/BlockingView.tsx index f83deaf1477c..afdc2d547d07 100644 --- a/src/components/BlockingViews/BlockingView.tsx +++ b/src/components/BlockingViews/BlockingView.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type {CSSProperties} from 'react'; import type {ImageSourcePropType, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {SvgProps} from 'react-native-svg'; @@ -67,6 +68,8 @@ type BlockingViewProps = RequiredIllustrationProps & { /** Style for the animation */ animationStyles?: StyleProp; + animationWebStyle?: CSSProperties; + /** Render custom subtitle */ renderSubtitle?: () => React.ReactElement; }; @@ -84,6 +87,7 @@ function BlockingView({ onLinkPress = () => Navigation.dismissModal(), shouldEmbedLinkWithSubtitle = false, animationStyles = [], + animationWebStyle = {}, renderSubtitle, }: BlockingViewProps) { const styles = useThemeStyles(); @@ -112,8 +116,10 @@ function BlockingView({ {animation && ( )} {icon && ( diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 18832440082b..b032d5f0ccd5 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -45,7 +45,7 @@ function LHNOptionsList({ const styles = useThemeStyles(); const {canUseViolations} = usePermissions(); const {translate} = useLocalize(); - const {isSmallScreenWidth} = useWindowDimensions(); + const {isExtraSmallScreenHeight, isSmallScreenWidth} = useWindowDimensions(); // When the first item renders we want to call the onFirstItemRendered callback. // At this point in time we know that the list is actually displaying items. @@ -90,7 +90,7 @@ function LHNOptionsList({ ), - [theme, styles.dFlex, styles.gap1, styles.alignItemsCenter, styles.justifyContentCenter, styles.textAlignCenter, translate], + [theme, styles.alignItemsCenter, styles.textAlignCenter, translate], ); /** @@ -157,6 +157,19 @@ function LHNOptionsList({ ], ); + if (isSmallScreenWidth && data.length === 0) { + return ( + + ); + } + return ( - - - ) : null - } /> ); diff --git a/src/styles/index.ts b/src/styles/index.ts index 3bce9d90c8ea..a0de88de3cf3 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3171,9 +3171,10 @@ const styles = (theme: ThemeColors) => height: 180, }, - emptyLHNBlockingView: { - marginTop: 120, - }, + emptyLHNAnimationWeb: (isExtraSmallScreenHeight: boolean) => ({ + width: isExtraSmallScreenHeight ? 160 : 180, + height: isExtraSmallScreenHeight ? 160 : 180, + }), locationErrorLinkText: { textAlignVertical: 'center', From 0dbec9ef30fdf08d2af0fa10b48a1f1130be58da Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 28 Feb 2024 14:57:01 +0700 Subject: [PATCH 188/699] fix nested teneray operation --- src/components/BlockingViews/BlockingView.tsx | 28 +++++++++++-------- .../LHNOptionsList/LHNOptionsList.tsx | 2 +- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/components/BlockingViews/BlockingView.tsx b/src/components/BlockingViews/BlockingView.tsx index afdc2d547d07..05ffbd6a0d58 100644 --- a/src/components/BlockingViews/BlockingView.tsx +++ b/src/components/BlockingViews/BlockingView.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import type {CSSProperties} from 'react'; -import type {ImageSourcePropType, StyleProp, ViewStyle} from 'react-native'; +import type {ImageSourcePropType, StyleProp, ViewStyle, WebStyle} from 'react-native'; import {View} from 'react-native'; import type {SvgProps} from 'react-native-svg'; import AutoEmailLink from '@components/AutoEmailLink'; @@ -68,10 +67,10 @@ type BlockingViewProps = RequiredIllustrationProps & { /** Style for the animation */ animationStyles?: StyleProp; - animationWebStyle?: CSSProperties; + animationWebStyle?: WebStyle; /** Render custom subtitle */ - renderSubtitle?: () => React.ReactElement; + renderCustomSubtitle?: () => React.ReactElement; }; function BlockingView({ @@ -88,7 +87,7 @@ function BlockingView({ shouldEmbedLinkWithSubtitle = false, animationStyles = [], animationWebStyle = {}, - renderSubtitle, + renderCustomSubtitle, }: BlockingViewProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -111,6 +110,17 @@ function BlockingView({ ); } + function renderSubtitle() { + if (renderCustomSubtitle) { + return renderCustomSubtitle(); + } + return shouldEmbedLinkWithSubtitle ? ( + {renderContent()} + ) : ( + {renderContent()} + ); + } + return ( {animation && ( @@ -133,13 +143,7 @@ function BlockingView({ {title} - {renderSubtitle ? ( - renderSubtitle() - ) : shouldEmbedLinkWithSubtitle ? ( - {renderContent()} - ) : ( - {renderContent()} - )} + {renderSubtitle()} ); diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index b032d5f0ccd5..40c79c45946c 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -165,7 +165,7 @@ function LHNOptionsList({ animationWebStyle={styles.emptyLHNAnimationWeb(isExtraSmallScreenHeight)} title={translate('common.emptyLHN.title')} shouldShowLink={false} - renderSubtitle={renderEmptyStateSubtitle} + renderCustomSubtitle={renderEmptyStateSubtitle} /> ); } From d783a3c690a3d513ae768b91992413ff5d2443a5 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 09:56:50 +0100 Subject: [PATCH 189/699] add isCurrencyPressable prop --- src/pages/iou/steps/MoneyRequestAmountForm.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index cb1f73ae2207..5a5541239f42 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -33,6 +33,9 @@ type MoneyRequestAmountFormProps = { /** Whether the amount is being edited or not */ isEditing?: boolean; + /** Whether the currency symbol is pressable */ + isCurrencyPressable?: boolean; + /** Fired when back button pressed, navigates to currency selection page */ onCurrencyButtonPress: () => void; @@ -69,6 +72,7 @@ function MoneyRequestAmountForm( amount = 0, taxAmount = 0, currency = CONST.CURRENCY.USD, + isCurrencyPressable = true, isEditing = false, onCurrencyButtonPress, onSubmitButtonPress, @@ -300,7 +304,7 @@ function MoneyRequestAmountForm( setSelection({start, end}); }} onKeyPress={textInputKeyPress} - isCurrencyPressable + isCurrencyPressable={isCurrencyPressable} /> {!!formError && ( Date: Wed, 28 Feb 2024 09:57:27 +0100 Subject: [PATCH 190/699] use isCurrencyPressable prop to hide currency dropdown --- src/pages/EditRequestTaxAmountPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/EditRequestTaxAmountPage.tsx b/src/pages/EditRequestTaxAmountPage.tsx index 22ce8a4de2df..ed7f14b9091e 100644 --- a/src/pages/EditRequestTaxAmountPage.tsx +++ b/src/pages/EditRequestTaxAmountPage.tsx @@ -56,6 +56,7 @@ function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurre amount={defaultAmount} taxAmount={defaultTaxAmount} ref={textInput} + isCurrencyPressable={false} onCurrencyButtonPress={onNavigateToCurrency} onSubmitButtonPress={onSubmit} isEditing From bddbd10fe7e4b15e5e7bd5455f90eda25c1720a9 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 09:58:56 +0100 Subject: [PATCH 191/699] make onCurrencyButtonPress optional --- src/pages/iou/steps/MoneyRequestAmountForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index 5a5541239f42..e073fdefae20 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -37,7 +37,7 @@ type MoneyRequestAmountFormProps = { isCurrencyPressable?: boolean; /** Fired when back button pressed, navigates to currency selection page */ - onCurrencyButtonPress: () => void; + onCurrencyButtonPress?: () => void; /** Fired when submit button pressed, saves the given amount and navigates to the next page */ onSubmitButtonPress: ({amount, currency}: {amount: string; currency: string}) => void; From c3c4a075bf0f9ccbc6dcfbe70cab1dfb3fd427bc Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 10:01:14 +0100 Subject: [PATCH 192/699] remove onCurrencyButtonPress and props values --- src/pages/EditRequestTaxAmountPage.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/EditRequestTaxAmountPage.tsx b/src/pages/EditRequestTaxAmountPage.tsx index ed7f14b9091e..a34ed8a5252d 100644 --- a/src/pages/EditRequestTaxAmountPage.tsx +++ b/src/pages/EditRequestTaxAmountPage.tsx @@ -20,12 +20,9 @@ type EditRequestTaxAmountPageProps = { /** Callback to fire when the Save button is pressed */ onSubmit: () => void; - - /** Callback to fire when we press on the currency */ - onNavigateToCurrency: () => void; }; -function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurrency, onNavigateToCurrency, onSubmit}: EditRequestTaxAmountPageProps) { +function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurrency, onSubmit}: EditRequestTaxAmountPageProps) { const {translate} = useLocalize(); const textInput = useRef(null); @@ -57,7 +54,6 @@ function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurre taxAmount={defaultTaxAmount} ref={textInput} isCurrencyPressable={false} - onCurrencyButtonPress={onNavigateToCurrency} onSubmitButtonPress={onSubmit} isEditing /> From 687d846c1d9815bd695895bde143a2c98c73be6e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 10:02:48 +0100 Subject: [PATCH 193/699] remove onNavigateToCurrency to Props --- src/pages/EditRequestPage.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 64fb4028516c..638b3711dc2e 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -206,10 +206,6 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p defaultAmount={transactionTaxAmount} defaultTaxAmount={getTaxAmount(transactionAmount, transactionTaxCode, taxRates)} defaultCurrency={defaultCurrency} - onNavigateToCurrency={() => { - const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); - Navigation.navigate(ROUTES.EDIT_CURRENCY_REQUEST.getRoute(report.reportID, defaultCurrency, activeRoute)); - }} onSubmit={updateTaxAmount} /> ); From d824c5a0f93facf39c70dc3ac925cfca2bc23310 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 28 Feb 2024 16:25:36 +0700 Subject: [PATCH 194/699] remove storybook warning --- .storybook/webpack.config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 204f70344b18..89f7699465bf 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -37,6 +37,12 @@ module.exports = ({config}) => { config.plugins[definePluginIndex].definitions.__REACT_WEB_CONFIG__ = JSON.stringify(env); config.resolve.extensions = custom.resolve.extensions; + config.stats = { + // We can ignore the "module not installed" warning from lottie-react-native + // because we are not using the library for JSON format of Lottie animations. + warningsFilter: ['../node_modules/lottie-react-native/lib/module/LottieView/index.web.js'], + }; + const babelRulesIndex = _.findIndex(custom.module.rules, (rule) => rule.loader === 'babel-loader'); const babelRule = custom.module.rules[babelRulesIndex]; config.module.rules.push(babelRule); From 61e28f7811003c2b33d78b323e6553e589292320 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 11:02:03 +0100 Subject: [PATCH 195/699] update Tax Amount Params --- src/libs/TransactionUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 55bb591e3e6a..f8976d1ca450 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -306,14 +306,14 @@ function getAmount(transaction: OnyxEntry, isFromExpenseReport = fa /** * Return the tax amount field from the transaction. */ -function getTaxAmount(transaction: OnyxEntry, isFromExpenseReport: boolean): number { +function getTaxAmount(taxAmount: number, isFromExpenseReport: boolean): number { // IOU requests cannot have negative values but they can be stored as negative values, let's return absolute value if (!isFromExpenseReport) { - return Math.abs(transaction?.taxAmount ?? 0); + return Math.abs(taxAmount ?? 0); } // To avoid -0 being shown, lets only change the sign if the value is other than 0. - const amount = transaction?.taxAmount ?? 0; + const amount = taxAmount ?? 0; return amount ? -amount : 0; } From 64408cabc219f5d33db258a3e39163f9c13d4db5 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 11:02:35 +0100 Subject: [PATCH 196/699] update getTaxAmount args... --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 63d1dd019db2..2a5c2e868c7c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2066,7 +2066,7 @@ function getTransactionDetails(transaction: OnyxEntry, createdDateF return { created: TransactionUtils.getCreated(transaction, createdDateFormat), amount: TransactionUtils.getAmount(transaction, !isEmptyObject(report) && isExpenseReport(report)), - taxAmount: TransactionUtils.getTaxAmount(transaction, !isEmptyObject(report) && isExpenseReport(report)), + taxAmount: TransactionUtils.getTaxAmount(transaction?.taxAmount ?? 0, !isEmptyObject(report) && isExpenseReport(report)), taxCode: TransactionUtils.getTaxCode(transaction), currency: TransactionUtils.getCurrency(transaction), comment: TransactionUtils.getDescription(transaction), From 211308e7fc552cf686f2c370d1f4e569f3a37dac Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 28 Feb 2024 11:03:05 +0100 Subject: [PATCH 197/699] remove duplicate getTaxAmount --- src/libs/ModifiedExpenseMessage.ts | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index a6491d3025fc..2855344899f0 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -98,20 +98,6 @@ function getForDistanceRequest(newDistance: string, oldDistance: string, newAmou }); } -/** - * Return the tax amount field from the transaction. - */ -function getTaxAmount(taxAmount: number, isFromExpenseReport: boolean): number { - // IOU requests cannot have negative values but they can be stored as negative values, let's return absolute value - if (!isFromExpenseReport) { - return Math.abs(taxAmount ?? 0); - } - - // To avoid -0 being shown, lets only change the sign if the value is other than 0. - const amount = taxAmount ?? 0; - return amount ? -amount : 0; -} - /** * Get the report action message when expense has been modified. * @@ -244,8 +230,8 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); const currency = iouReport?.currency ?? ''; - const taxAmount = CurrencyUtils.convertToDisplayString(getTaxAmount(reportActionOriginalMessage?.taxAmount ?? 0, isFromExpenseReport), currency); - const oldTaxAmountValue = getTaxAmount(reportActionOriginalMessage?.oldTaxAmount ?? 0, isFromExpenseReport); + const taxAmount = CurrencyUtils.convertToDisplayString(TransactionUtils.getTaxAmount(reportActionOriginalMessage?.taxAmount ?? 0, isFromExpenseReport), currency); + const oldTaxAmountValue = TransactionUtils.getTaxAmount(reportActionOriginalMessage?.oldTaxAmount ?? 0, isFromExpenseReport); const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(oldTaxAmountValue, currency) : ''; buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), false, setFragments, removalFragments, changeFragments); From fc784e69167325e0724fb7733560ec56a0c266fe Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 28 Feb 2024 21:19:07 +0100 Subject: [PATCH 198/699] Remove unnecessary code --- src/components/MoneyRequestConfirmationList.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 773e98b6462e..c9d4929e1b48 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -56,7 +56,6 @@ type Option = Partial; type CategorySection = { title: string | undefined; shouldShow: boolean; - indexOffset: number; data: Option[]; }; @@ -386,14 +385,12 @@ function MoneyRequestConfirmationList({ title: translate('moneyRequestConfirmationList.paidBy'), data: [formattedPayeeOption], shouldShow: true, - indexOffset: 0, isDisabled: canModifyParticipantsValue, }, { title: translate('moneyRequestConfirmationList.splitWith'), data: formattedParticipantsList, shouldShow: true, - indexOffset: 1, }, ); } else { @@ -405,7 +402,6 @@ function MoneyRequestConfirmationList({ title: translate('common.to'), data: formattedSelectedParticipants, shouldShow: true, - indexOffset: 0, }); } return sections; From 0452b5c43152b99e41146586584b9d50bd0e4d6a Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 28 Feb 2024 23:38:24 +0100 Subject: [PATCH 199/699] initial commit --- .../images/avatars/group/default-avatar_1.svg | 7 + .../avatars/group/default-avatar_10.svg | 7 + .../avatars/group/default-avatar_11.svg | 7 + .../avatars/group/default-avatar_12.svg | 7 + .../avatars/group/default-avatar_13.svg | 7 + .../avatars/group/default-avatar_14.svg | 7 + .../avatars/group/default-avatar_15.svg | 7 + .../avatars/group/default-avatar_16.svg | 7 + .../avatars/group/default-avatar_17.svg | 7 + .../avatars/group/default-avatar_18.svg | 7 + .../images/avatars/group/default-avatar_2.svg | 7 + .../images/avatars/group/default-avatar_3.svg | 7 + .../images/avatars/group/default-avatar_4.svg | 7 + .../images/avatars/group/default-avatar_5.svg | 7 + .../images/avatars/group/default-avatar_6.svg | 7 + .../images/avatars/group/default-avatar_7.svg | 7 + .../images/avatars/group/default-avatar_8.svg | 7 + .../images/avatars/group/default-avatar_9.svg | 7 + src/CONST.ts | 1 + src/ONYXKEYS.ts | 4 + src/ROUTES.ts | 1 + src/SCREENS.ts | 1 + src/components/Icon/GroupDefaultAvatars.ts | 20 +++ src/languages/en.ts | 6 +- src/languages/es.ts | 5 +- .../AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 4 + src/libs/ReportUtils.ts | 10 ++ src/libs/actions/Report.ts | 18 ++- src/pages/NewChatConfirmPage.tsx | 147 ++++++++++++++++++ src/pages/NewChatPage.tsx | 43 +++-- .../FloatingActionButtonAndPopover.js | 3 +- src/types/onyx/NewGroupChat.ts | 7 + src/types/onyx/index.ts | 2 + 34 files changed, 382 insertions(+), 17 deletions(-) create mode 100644 assets/images/avatars/group/default-avatar_1.svg create mode 100644 assets/images/avatars/group/default-avatar_10.svg create mode 100644 assets/images/avatars/group/default-avatar_11.svg create mode 100644 assets/images/avatars/group/default-avatar_12.svg create mode 100644 assets/images/avatars/group/default-avatar_13.svg create mode 100644 assets/images/avatars/group/default-avatar_14.svg create mode 100644 assets/images/avatars/group/default-avatar_15.svg create mode 100644 assets/images/avatars/group/default-avatar_16.svg create mode 100644 assets/images/avatars/group/default-avatar_17.svg create mode 100644 assets/images/avatars/group/default-avatar_18.svg create mode 100644 assets/images/avatars/group/default-avatar_2.svg create mode 100644 assets/images/avatars/group/default-avatar_3.svg create mode 100644 assets/images/avatars/group/default-avatar_4.svg create mode 100644 assets/images/avatars/group/default-avatar_5.svg create mode 100644 assets/images/avatars/group/default-avatar_6.svg create mode 100644 assets/images/avatars/group/default-avatar_7.svg create mode 100644 assets/images/avatars/group/default-avatar_8.svg create mode 100644 assets/images/avatars/group/default-avatar_9.svg create mode 100644 src/components/Icon/GroupDefaultAvatars.ts create mode 100644 src/pages/NewChatConfirmPage.tsx create mode 100644 src/types/onyx/NewGroupChat.ts diff --git a/assets/images/avatars/group/default-avatar_1.svg b/assets/images/avatars/group/default-avatar_1.svg new file mode 100644 index 000000000000..5d97c5bf855b --- /dev/null +++ b/assets/images/avatars/group/default-avatar_1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_10.svg b/assets/images/avatars/group/default-avatar_10.svg new file mode 100644 index 000000000000..12c9dd76ae31 --- /dev/null +++ b/assets/images/avatars/group/default-avatar_10.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_11.svg b/assets/images/avatars/group/default-avatar_11.svg new file mode 100644 index 000000000000..97f17f30f3a7 --- /dev/null +++ b/assets/images/avatars/group/default-avatar_11.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_12.svg b/assets/images/avatars/group/default-avatar_12.svg new file mode 100644 index 000000000000..f917fb136582 --- /dev/null +++ b/assets/images/avatars/group/default-avatar_12.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_13.svg b/assets/images/avatars/group/default-avatar_13.svg new file mode 100644 index 000000000000..9e59fb9123a5 --- /dev/null +++ b/assets/images/avatars/group/default-avatar_13.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_14.svg b/assets/images/avatars/group/default-avatar_14.svg new file mode 100644 index 000000000000..ca071e488416 --- /dev/null +++ b/assets/images/avatars/group/default-avatar_14.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_15.svg b/assets/images/avatars/group/default-avatar_15.svg new file mode 100644 index 000000000000..f227cc0717be --- /dev/null +++ b/assets/images/avatars/group/default-avatar_15.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_16.svg b/assets/images/avatars/group/default-avatar_16.svg new file mode 100644 index 000000000000..efbb85f0b13d --- /dev/null +++ b/assets/images/avatars/group/default-avatar_16.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_17.svg b/assets/images/avatars/group/default-avatar_17.svg new file mode 100644 index 000000000000..25c015c595ca --- /dev/null +++ b/assets/images/avatars/group/default-avatar_17.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_18.svg b/assets/images/avatars/group/default-avatar_18.svg new file mode 100644 index 000000000000..a58ee6e66eff --- /dev/null +++ b/assets/images/avatars/group/default-avatar_18.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_2.svg b/assets/images/avatars/group/default-avatar_2.svg new file mode 100644 index 000000000000..ff1cc3e6dd2d --- /dev/null +++ b/assets/images/avatars/group/default-avatar_2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_3.svg b/assets/images/avatars/group/default-avatar_3.svg new file mode 100644 index 000000000000..dde31b5d02a0 --- /dev/null +++ b/assets/images/avatars/group/default-avatar_3.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_4.svg b/assets/images/avatars/group/default-avatar_4.svg new file mode 100644 index 000000000000..f6d02801bc6b --- /dev/null +++ b/assets/images/avatars/group/default-avatar_4.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_5.svg b/assets/images/avatars/group/default-avatar_5.svg new file mode 100644 index 000000000000..fdabd36e2058 --- /dev/null +++ b/assets/images/avatars/group/default-avatar_5.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_6.svg b/assets/images/avatars/group/default-avatar_6.svg new file mode 100644 index 000000000000..6f1c6b80eda6 --- /dev/null +++ b/assets/images/avatars/group/default-avatar_6.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_7.svg b/assets/images/avatars/group/default-avatar_7.svg new file mode 100644 index 000000000000..62d9a8b76bb8 --- /dev/null +++ b/assets/images/avatars/group/default-avatar_7.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_8.svg b/assets/images/avatars/group/default-avatar_8.svg new file mode 100644 index 000000000000..206b10c2322b --- /dev/null +++ b/assets/images/avatars/group/default-avatar_8.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/avatars/group/default-avatar_9.svg b/assets/images/avatars/group/default-avatar_9.svg new file mode 100644 index 000000000000..ffbe02ce57e8 --- /dev/null +++ b/assets/images/avatars/group/default-avatar_9.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/CONST.ts b/src/CONST.ts index 8abd4c087b16..ceb0e4d8e662 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -689,6 +689,7 @@ const CONST = { DOMAIN_ALL: 'domainAll', POLICY_ROOM: 'policyRoom', POLICY_EXPENSE_CHAT: 'policyExpenseChat', + GROUP_CHAT: 'group', }, WORKSPACE_CHAT_ROOMS: { ANNOUNCE: '#announce', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 78f0e61e72a9..a75455460e40 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -272,6 +272,9 @@ const ONYXKEYS = { /** Indicates whether we should store logs or not */ SHOULD_STORE_LOGS: 'shouldStoreLogs', + /** Stores new group chat draft */ + NEW_GROUP: 'newGroupChat', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -491,6 +494,7 @@ type OnyxValuesMapping = { [ONYXKEYS.IOU]: OnyxTypes.IOU; [ONYXKEYS.MODAL]: OnyxTypes.Modal; [ONYXKEYS.NETWORK]: OnyxTypes.Network; + [ONYXKEYS.NEW_GROUP]: OnyxTypes.NewGroupChat; [ONYXKEYS.CUSTOM_STATUS_DRAFT]: OnyxTypes.CustomStatusDraft; [ONYXKEYS.INPUT_FOCUSED]: boolean; [ONYXKEYS.PERSONAL_DETAILS_LIST]: OnyxTypes.PersonalDetailsList; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 22ebffd52eec..b9201410b0c5 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -174,6 +174,7 @@ const ROUTES = { NEW: 'new', NEW_CHAT: 'new/chat', + NEW_CHAT_CONFIRM: 'new/chat/confirm', NEW_ROOM: 'new/room', REPORT: 'r', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index ac75968e68b9..ac74fe99802d 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -231,6 +231,7 @@ const SCREENS = { NEW_CHAT: { ROOT: 'NewChat_Root', NEW_CHAT: 'chat', + NEW_CHAT_CONFIRM: 'NewChat_Confirm', NEW_ROOM: 'room', }, diff --git a/src/components/Icon/GroupDefaultAvatars.ts b/src/components/Icon/GroupDefaultAvatars.ts new file mode 100644 index 000000000000..7b4afb7c7309 --- /dev/null +++ b/src/components/Icon/GroupDefaultAvatars.ts @@ -0,0 +1,20 @@ +import Avatar1 from '@assets/images/avatars/group/default-avatar_1.svg'; +import Avatar2 from '@assets/images/avatars/group/default-avatar_2.svg'; +import Avatar3 from '@assets/images/avatars/group/default-avatar_3.svg'; +import Avatar4 from '@assets/images/avatars/group/default-avatar_4.svg'; +import Avatar5 from '@assets/images/avatars/group/default-avatar_5.svg'; +import Avatar6 from '@assets/images/avatars/group/default-avatar_6.svg'; +import Avatar7 from '@assets/images/avatars/group/default-avatar_7.svg'; +import Avatar8 from '@assets/images/avatars/group/default-avatar_8.svg'; +import Avatar9 from '@assets/images/avatars/group/default-avatar_9.svg'; +import Avatar10 from '@assets/images/avatars/group/default-avatar_10.svg'; +import Avatar11 from '@assets/images/avatars/group/default-avatar_11.svg'; +import Avatar12 from '@assets/images/avatars/group/default-avatar_12.svg'; +import Avatar13 from '@assets/images/avatars/group/default-avatar_13.svg'; +import Avatar14 from '@assets/images/avatars/group/default-avatar_14.svg'; +import Avatar15 from '@assets/images/avatars/group/default-avatar_15.svg'; +import Avatar16 from '@assets/images/avatars/group/default-avatar_16.svg'; +import Avatar17 from '@assets/images/avatars/group/default-avatar_17.svg'; +import Avatar18 from '@assets/images/avatars/group/default-avatar_18.svg'; + +export {Avatar1, Avatar2, Avatar3, Avatar4, Avatar5, Avatar6, Avatar7, Avatar8, Avatar9, Avatar10, Avatar11, Avatar12, Avatar13, Avatar14, Avatar15, Avatar16, Avatar17, Avatar18}; diff --git a/src/languages/en.ts b/src/languages/en.ts index cf8823f5b2be..3215ca3d4020 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -145,6 +145,7 @@ export default { twoFactorCode: 'Two-factor code', workspaces: 'Workspaces', chats: 'Chats', + group: 'Group', profile: 'Profile', referral: 'Referral', payments: 'Payments', @@ -1143,6 +1144,9 @@ export default { roomDescriptionOptional: 'Room description (optional)', explainerText: 'Set a custom decription for the room.', }, + groupConfirmPage: { + groupName: 'Group name', + }, languagePage: { language: 'Language', languages: { @@ -1281,7 +1285,7 @@ export default { }, newChatPage: { createChat: 'Create chat', - createGroup: 'Create group', + startGroup: 'Start group', addToGroup: 'Add to group', }, yearPickerPage: { diff --git a/src/languages/es.ts b/src/languages/es.ts index b3a8eef73d7c..43acf23d7ea0 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1142,6 +1142,9 @@ export default { roomDescriptionOptional: 'Descripción de la sala de chat (opcional)', explainerText: 'Establece una descripción personalizada para la sala de chat.', }, + groupConfirmPage: { + groupName: 'Nombre del grupo', + }, languagePage: { language: 'Idioma', languages: { @@ -1282,7 +1285,7 @@ export default { }, newChatPage: { createChat: 'Crear chat', - createGroup: 'Crear grupo', + startGroup: 'Grupo de inicio', addToGroup: 'Añadir al grupo', }, yearPickerPage: { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 3d0144d8cf77..adfd125e2fff 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -162,6 +162,7 @@ const SearchModalStackNavigator = createModalStackNavigator({ [SCREENS.NEW_CHAT.ROOT]: () => require('../../../pages/NewChatSelectorPage').default as React.ComponentType, + [SCREENS.NEW_CHAT.NEW_CHAT_CONFIRM]: () => require('../../../pages/NewChatConfirmPage').default as React.ComponentType, }); const NewTaskModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 7a6211ebd283..ad524bb96981 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -339,6 +339,10 @@ const config: LinkingOptions['config'] = { }, }, }, + [SCREENS.NEW_CHAT.NEW_CHAT_CONFIRM]: { + path: ROUTES.NEW_CHAT_CONFIRM, + exact: true, + }, }, }, [SCREENS.RIGHT_MODAL.NEW_TASK]: { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0f8656adfa51..689556b73bd1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -9,6 +9,7 @@ import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import * as Expensicons from '@components/Icon/Expensicons'; +import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types'; @@ -1430,6 +1431,14 @@ function getWorkspaceAvatar(report: OnyxEntry): UserUtils.AvatarSource { return !isEmpty(avatar) ? avatar : getDefaultWorkspaceAvatar(workspaceName); } +// /** +// * Helper method to return the default avatar associated with the given reportID +// * TO REWORK! +// */ +function getDefaultGroupAvatar(): IconAsset { + return defaultGroupAvatars.Avatar1; +} + /** * Returns the appropriate icons for the given chat report using the stored personalDetails. * The Avatar sources can be URLs or Icon components according to the chat type. @@ -5208,6 +5217,7 @@ export { canEditRoomVisibility, canEditPolicyDescription, getPolicyDescriptionText, + getDefaultGroupAvatar, }; export type { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index f29f8a4fbaab..cd19054672cc 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -198,6 +198,11 @@ Onyx.connect({ callback: (val) => (allRecentlyUsedReportFields = val), }); +function startNewChat() { + clearGroupChat(); + Navigation.navigate(ROUTES.NEW); +} + /** Get the private pusher channel name for a Report. */ function getReportChannelName(reportID: string): string { return `${CONST.PUSHER.PRIVATE_REPORT_CHANNEL_PREFIX}${reportID}${CONFIG.PUSHER.SUFFIX}`; @@ -733,7 +738,7 @@ function openReport( * @param userLogins list of user logins to start a chat report with. * @param shouldDismissModal a flag to determine if we should dismiss modal before navigate to report or navigate to report directly. */ -function navigateToAndOpenReport(userLogins: string[], shouldDismissModal = true) { +function navigateToAndOpenReport(userLogins: string[], shouldDismissModal = true, memberRoles?: string[], reportName?: string) { let newChat: ReportUtils.OptimisticChatReport | EmptyObject = {}; const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins(userLogins); @@ -2899,6 +2904,14 @@ function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEnt API.write(WRITE_COMMANDS.RESOLVE_ACTIONABLE_MENTION_WHISPER, parameters, {optimisticData, failureData}); } +function setGroupDraft(invitedUsersIDs: number[], groupChatAdminLogins: string[] = [], reportName: string = '') { + Onyx.set(ONYXKEYS.NEW_GROUP, {selectedOptions: invitedUsersIDs, groupChatAdminLogins, reportName}); +} + +function clearGroupChat() { + Onyx.set(ONYXKEYS.NEW_GROUP, null); +} + export { searchInServer, addComment, @@ -2969,4 +2982,7 @@ export { updateReportName, resolveActionableMentionWhisper, updateRoomVisibility, + setGroupDraft, + clearGroupChat, + startNewChat, }; diff --git a/src/pages/NewChatConfirmPage.tsx b/src/pages/NewChatConfirmPage.tsx new file mode 100644 index 000000000000..c48b6d62c053 --- /dev/null +++ b/src/pages/NewChatConfirmPage.tsx @@ -0,0 +1,147 @@ +import React, {useEffect, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import Avatar from '@components/Avatar'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OptionsSelector from '@components/OptionsSelector'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import type {OptionData} from '@libs/ReportUtils'; +import * as Report from '@userActions/Report'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; + +type NewChatConfirmPageOnyxProps = { + /** New group chat draft data */ + newGroupDraft: OnyxEntry; + + /** All of the personal details for everyone */ + allPersonalDetails: OnyxEntry; +}; + +type NewChatConfirmPageProps = NewChatConfirmPageOnyxProps; + +function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmPageProps) { + const [selectedOptions, setSelectedOptions] = useState([]); + const [options, setOptions] = useState([]); + const [currentUserOption, setCurrentUserOption] = useState(); + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const groupName = options.map((invitedUser) => (invitedUser.participantsList ? invitedUser.participantsList[0].firstName : '')).join(', '); + + useEffect(() => { + const invitedUsersPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(newGroupDraft?.selectedOptions, allPersonalDetails); + const members = OptionsListUtils.getMemberInviteOptions(invitedUsersPersonalDetails); + const currentUserOptionData = members.currentUserOption; + const options = [...members.personalDetails, currentUserOptionData] as OptionData[]; + + setCurrentUserOption(currentUserOptionData!); + setOptions(options); + setSelectedOptions(options); + }, [newGroupDraft]); + + const sections = useMemo(() => { + const sectionsList = []; + if (options) { + sectionsList.push({ + title: translate('common.members'), + data: options, + shouldShow: true, + indexOffset: 0, + }); + } + return sectionsList; + }, [options, translate, selectedOptions]); + /** + * Removes a selected option from list if already selected. + */ + const unselectOption = (option: OptionData) => { + if (!selectedOptions) { + return; + } + const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.login === option.login); + + let newSelectedOptions; + + if (isOptionInList && currentUserOption && option.accountID === currentUserOption.accountID) { + return; + } + + if (isOptionInList) { + newSelectedOptions = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login); + setSelectedOptions(newSelectedOptions); + } + }; + + const createGroup = () => { + const logins = selectedOptions.map((option) => option.login).filter((login): login is string => typeof login === 'string'); + if (logins.length < 1) { + return; + } + const accountIDs = selectedOptions.map((selectedOption: OptionData) => selectedOption.accountID) as number[]; + const creatorLogin = [currentUserOption?.login] as string[]; + Report.setGroupDraft(accountIDs, creatorLogin, groupName); + // Report.navigateToAndOpenReport(logins, true, [], ''); + }; + + const navigateBack = () => { + Navigation.goBack(ROUTES.NEW_CHAT); + }; + + return ( + + + + + + + + + + + ); +} + +NewChatConfirmPage.displayName = 'NewChatConfirmPage'; + +export default withOnyx({ + newGroupDraft: { + key: ONYXKEYS.NEW_GROUP, + }, + allPersonalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, +})(NewChatConfirmPage); diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 054f229ac16a..996efa678d4d 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -4,6 +4,7 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; +import {usePersonalDetails} from '@components/OnyxProvider'; import OptionsSelector from '@components/OptionsSelector'; import ScreenWrapper from '@components/ScreenWrapper'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; @@ -14,6 +15,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import doInteractionTask from '@libs/DoInteractionTask'; +import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; import type {OptionData} from '@libs/ReportUtils'; @@ -21,6 +23,7 @@ import variables from '@styles/variables'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {DismissedReferralBanners} from '@src/types/onyx/Account'; @@ -28,6 +31,9 @@ type NewChatPageWithOnyxProps = { /** All reports shared with the user */ reports: OnyxCollection; + /** New group chat draft data */ + newGroupDraft: OnyxEntry; + /** All of the personal details for everyone */ personalDetails: OnyxEntry; @@ -46,7 +52,7 @@ type NewChatPageProps = NewChatPageWithOnyxProps & { const excludedGroupEmails = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); -function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingForReports, dismissedReferralBanners}: NewChatPageProps) { +function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingForReports, dismissedReferralBanners, newGroupDraft}: NewChatPageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [searchTerm, setSearchTerm] = useState(''); @@ -58,6 +64,8 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF const {isSmallScreenWidth} = useWindowDimensions(); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); + const personalData = usePersonalDetails() || CONST.EMPTY_OBJECT; + const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached); @@ -147,7 +155,6 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF [], true, ); - setSelectedOptions(newSelectedOptions); setFilteredRecentReports(recentReports); setFilteredPersonalDetails(newChatPersonalDetails); @@ -164,19 +171,21 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF } Report.navigateToAndOpenReport([option.login]); }; - /** - * Creates a new group chat with all the selected options and the current user, - * or navigates to the existing chat if one with those participants already exists. + * Navigates to create group confirm page */ - const createGroup = () => { - const logins = selectedOptions.map((option) => option.login).filter((login): login is string => typeof login === 'string'); - - if (logins.length < 1) { + const navigateToConfirmPage = () => { + if (selectedOptions.length < 1) { return; } - - Report.navigateToAndOpenReport(logins); + const selectedAccountIDs = selectedOptions.map((option) => option.accountID) as number[]; + const user = OptionsListUtils.getMemberInviteOptions(personalData); + const currentUserOption = user.currentUserOption; + if (currentUserOption) { + const accountIDs = [...selectedAccountIDs, currentUserOption.accountID] as number[]; + Report.setGroupDraft(accountIDs); + Navigation.navigate(ROUTES.NEW_CHAT_CONFIRM); + } }; const updateOptions = useCallback(() => { @@ -227,6 +236,11 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF return; } updateOptions(); + if (newGroupDraft?.selectedOptions) { + const invitedUsersPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(newGroupDraft?.selectedOptions, personalDetails); + const groupSelectedOptions = OptionsListUtils.getMemberInviteOptions(invitedUsersPersonalDetails).personalDetails; + setSelectedOptions(groupSelectedOptions); + } }, [didScreenTransitionEnd, updateOptions]); const {inputCallbackRef} = useAutoFocusInput(); @@ -267,9 +281,9 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF shouldShowConfirmButton shouldShowReferralCTA={!dismissedReferralBanners[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]} referralContentType={CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT} - confirmButtonText={selectedOptions.length > 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')} + confirmButtonText={selectedOptions.length > 1 ? translate('common.next') : translate('newChatPage.createChat')} textInputAlert={isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''} - onConfirmSelection={createGroup} + onConfirmSelection={navigateToConfirmPage} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} isLoadingNewOptions={isSearchingForReports} @@ -289,6 +303,9 @@ export default withOnyx({ key: ONYXKEYS.ACCOUNT, selector: (data) => data?.dismissedReferralBanners ?? {}, }, + newGroupDraft: { + key: ONYXKEYS.NEW_GROUP, + }, reports: { key: ONYXKEYS.COLLECTION.REPORT, }, diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 573cbe370aa7..2e82cdd97e01 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -18,6 +18,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy'; +import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -159,7 +160,7 @@ function FloatingActionButtonAndPopover(props) { { icon: Expensicons.ChatBubble, text: translate('sidebarScreen.fabNewChat'), - onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.NEW)), + onSelected: () => interceptAnonymousUser(() => Report.startNewChat()), }, { icon: Expensicons.MoneyCircle, diff --git a/src/types/onyx/NewGroupChat.ts b/src/types/onyx/NewGroupChat.ts new file mode 100644 index 000000000000..b9a0d88e107d --- /dev/null +++ b/src/types/onyx/NewGroupChat.ts @@ -0,0 +1,7 @@ +type NewGroupChat = { + selectedOptions: number[]; + groupChatAdminLogins: string[]; + reportName: string; +}; + +export default NewGroupChat; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 6846fc302639..c31cc24df24a 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -25,6 +25,7 @@ import type Login from './Login'; import type MapboxAccessToken from './MapboxAccessToken'; import type Modal from './Modal'; import type Network from './Network'; +import type NewGroupChat from './NewGroupChat'; import type {OnyxUpdateEvent, OnyxUpdatesFromServer} from './OnyxUpdatesFromServer'; import type PersonalBankAccount from './PersonalBankAccount'; import type {PersonalDetailsList} from './PersonalDetails'; @@ -153,5 +154,6 @@ export type { RecentlyUsedReportFields, LastPaymentMethod, InvitedEmailsToAccountIDs, + NewGroupChat, Log, }; From 7b153084d3214db17b0067e5797552eadacc10af Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Fri, 1 Mar 2024 12:03:54 +0530 Subject: [PATCH 200/699] Incorrect header space fix: src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx Co-authored-by: Rajat --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 804d3de6bf13..05746405adf7 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -86,8 +86,6 @@ function StateSelectionPage() { Date: Fri, 1 Mar 2024 12:07:11 +0530 Subject: [PATCH 201/699] Remove unused code StateSelectionPage.tsx --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 05746405adf7..b30c9c9967c8 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -7,7 +7,6 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import searchCountryOptions from '@libs/searchCountryOptions'; import type {CountryData} from '@libs/searchCountryOptions'; @@ -26,7 +25,6 @@ type RouteParams = { function StateSelectionPage() { const route = useRoute(); const navigation = useNavigation(); - const styles = useThemeStyles(); const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); From 7b98ae0c1abad9144d84c5469a2749ed8e3a0c61 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 08:57:14 +0100 Subject: [PATCH 202/699] update libraries to newer version with types, install types --- package-lock.json | 187 ++++++++++++++++++++++------------------------ package.json | 3 +- 2 files changed, 90 insertions(+), 100 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f55ddd82868..eb1f2ca1ed91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -184,6 +184,7 @@ "@types/semver": "^7.5.4", "@types/setimmediate": "^1.0.2", "@types/underscore": "^1.11.5", + "@types/webpack-bundle-analyzer": "^4.7.0", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", "@vercel/ncc": "0.38.1", @@ -198,7 +199,7 @@ "babel-plugin-transform-remove-console": "^6.9.4", "clean-webpack-plugin": "^3.0.0", "concurrently": "^5.3.0", - "copy-webpack-plugin": "^6.4.1", + "copy-webpack-plugin": "^10.1.0", "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", @@ -21189,6 +21190,26 @@ "source-map": "^0.6.0" } }, + "node_modules/@types/webpack-bundle-analyzer": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", + "integrity": "sha512-c5i2ThslSNSG8W891BRvOd/RoCjI2zwph8maD22b1adtSns20j+0azDDMCK06DiVrzTgnwiDl5Ntmu1YRJw8Sg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "tapable": "^2.2.0", + "webpack": "^5" + } + }, + "node_modules/@types/webpack-bundle-analyzer/node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@types/webpack-env": { "version": "1.18.0", "dev": true, @@ -26724,155 +26745,123 @@ } }, "node_modules/copy-webpack-plugin": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.4.1.tgz", - "integrity": "sha512-MXyPCjdPVx5iiWyl40Va3JGh27bKzOTNY3NjUTrosD2q7dR/cLD0013uqJ3BpFbUjyONINjb6qI7nDIJujrMbA==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", + "integrity": "sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==", "dev": true, - "license": "MIT", "dependencies": { - "cacache": "^15.0.5", - "fast-glob": "^3.2.4", - "find-cache-dir": "^3.3.1", - "glob-parent": "^5.1.1", - "globby": "^11.0.1", - "loader-utils": "^2.0.0", + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^12.0.2", "normalize-path": "^3.0.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "webpack-sources": "^1.4.3" + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 12.20.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" + "webpack": "^5.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "license": "MIT", "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" + "fast-deep-equal": "^3.1.3" }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/copy-webpack-plugin/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/copy-webpack-plugin/node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/copy-webpack-plugin/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "node_modules/copy-webpack-plugin/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", + "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", "dev": true, - "license": "MIT", "dependencies": { - "semver": "^6.0.0" + "array-union": "^3.0.1", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.7", + "ignore": "^5.1.9", + "merge2": "^1.4.1", + "slash": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/copy-webpack-plugin/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dev": true, - "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">=8" + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/copy-webpack-plugin/node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/copy-webpack-plugin/node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, - "license": "MIT", "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "randombytes": "^2.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/path-exists": { + "node_modules/copy-webpack-plugin/node_modules/slash": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "dev": true, - "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/copy-webpack-plugin/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" + "node": ">=12" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/copy-webpack-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/core-js": { diff --git a/package.json b/package.json index e3c23d4538d3..b15951d49242 100644 --- a/package.json +++ b/package.json @@ -232,6 +232,7 @@ "@types/semver": "^7.5.4", "@types/setimmediate": "^1.0.2", "@types/underscore": "^1.11.5", + "@types/webpack-bundle-analyzer": "^4.7.0", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", "@vercel/ncc": "0.38.1", @@ -246,7 +247,7 @@ "babel-plugin-transform-remove-console": "^6.9.4", "clean-webpack-plugin": "^3.0.0", "concurrently": "^5.3.0", - "copy-webpack-plugin": "^6.4.1", + "copy-webpack-plugin": "^10.1.0", "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", From 630b8aea826dec37f812633766d44b46e9f119c6 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 08:58:54 +0100 Subject: [PATCH 203/699] migrate electronBuilder.config.js to TypeScript --- ...Builder.config.js => electronBuilder.config.ts} | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) rename config/{electronBuilder.config.js => electronBuilder.config.ts} (74%) diff --git a/config/electronBuilder.config.js b/config/electronBuilder.config.ts similarity index 74% rename from config/electronBuilder.config.js rename to config/electronBuilder.config.ts index e4ed685f65fe..219da2703c62 100644 --- a/config/electronBuilder.config.js +++ b/config/electronBuilder.config.ts @@ -2,25 +2,25 @@ const {version} = require('../package.json'); const pullRequestNumber = process.env.PULL_REQUEST_NUMBER; -const s3Bucket = { +const s3Bucket: Record = { production: 'expensify-cash', staging: 'staging-expensify-cash', adhoc: 'ad-hoc-expensify-cash', }; -const s3Path = { +const s3Path: Record = { production: '/', staging: '/', adhoc: process.env.PULL_REQUEST_NUMBER ? `/desktop/${pullRequestNumber}/` : '/', }; -const macIcon = { +const macIcon: Record = { production: './desktop/icon.png', staging: './desktop/icon-stg.png', adhoc: './desktop/icon-adhoc.png', }; -const isCorrectElectronEnv = ['production', 'staging', 'adhoc'].includes(process.env.ELECTRON_ENV); +const isCorrectElectronEnv: boolean = ['production', 'staging', 'adhoc'].includes(process.env.ELECTRON_ENV ?? ''); if (!isCorrectElectronEnv) { throw new Error('Invalid ELECTRON_ENV!'); @@ -37,7 +37,7 @@ module.exports = { }, mac: { category: 'public.app-category.finance', - icon: macIcon[process.env.ELECTRON_ENV], + icon: process.env.ELECTRON_ENV ? macIcon[process.env.ELECTRON_ENV] : undefined, hardenedRuntime: true, entitlements: 'desktop/entitlements.mac.plist', entitlementsInherit: 'desktop/entitlements.mac.plist', @@ -54,9 +54,9 @@ module.exports = { publish: [ { provider: 's3', - bucket: s3Bucket[process.env.ELECTRON_ENV], + bucket: process.env.ELECTRON_ENV ? s3Bucket[process.env.ELECTRON_ENV] : undefined, channel: 'latest', - path: s3Path[process.env.ELECTRON_ENV], + path: process.env.ELECTRON_ENV ? s3Path[process.env.ELECTRON_ENV] : undefined, }, ], files: ['dist', '!dist/www/{.well-known,favicon*}'], From 1dbe373fcadf2d9104e0232c463ed372b7cdafcc Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 09:03:41 +0100 Subject: [PATCH 204/699] migrate proxyConfig.js to TypeScript --- config/{proxyConfig.js => proxyConfig.ts} | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) rename config/{proxyConfig.js => proxyConfig.ts} (64%) diff --git a/config/proxyConfig.js b/config/proxyConfig.ts similarity index 64% rename from config/proxyConfig.js rename to config/proxyConfig.ts index fa09c436461f..0fecef28c1cf 100644 --- a/config/proxyConfig.js +++ b/config/proxyConfig.ts @@ -3,7 +3,14 @@ * We only specify for staging URLs as API requests are sent to the production * servers by default. */ -module.exports = { +type ProxyConfig = { + STAGING: string; + STAGING_SECURE: string; +}; + +const proxyConfig: ProxyConfig = { STAGING: '/staging/', STAGING_SECURE: '/staging-secure/', }; + +export default proxyConfig; From c6a484f6323a37553c4aad2c7df8d8fb9f7ee7d5 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 09:15:20 +0100 Subject: [PATCH 205/699] migrate webpack.common.js to TypeScript --- .../{webpack.common.js => webpack.common.ts} | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) rename config/webpack/{webpack.common.js => webpack.common.ts} (92%) diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.ts similarity index 92% rename from config/webpack/webpack.common.js rename to config/webpack/webpack.common.ts index 170198987793..71ba7e584fde 100644 --- a/config/webpack/webpack.common.js +++ b/config/webpack/webpack.common.ts @@ -1,13 +1,13 @@ -const path = require('path'); -const fs = require('fs'); -const {IgnorePlugin, DefinePlugin, ProvidePlugin, EnvironmentPlugin} = require('webpack'); -const {CleanWebpackPlugin} = require('clean-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const CopyPlugin = require('copy-webpack-plugin'); -const dotenv = require('dotenv'); -const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer'); -const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin'); -const CustomVersionFilePlugin = require('./CustomVersionFilePlugin'); +import PreloadWebpackPlugin from '@vue/preload-webpack-plugin'; +import {CleanWebpackPlugin} from 'clean-webpack-plugin'; +import CopyPlugin from 'copy-webpack-plugin'; +import dotenv from 'dotenv'; +import fs from 'fs'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import path from 'path'; +import {DefinePlugin, EnvironmentPlugin, IgnorePlugin, ProvidePlugin} from 'webpack'; +import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'; +import CustomVersionFilePlugin from './CustomVersionFilePlugin'; const includeModules = [ 'react-native-animatable', @@ -26,14 +26,14 @@ const includeModules = [ 'expo-av', ].join('|'); -const envToLogoSuffixMap = { +const envToLogoSuffixMap: Record = { production: '', staging: '-stg', dev: '-dev', adhoc: '-adhoc', }; -function mapEnvToLogoSuffix(envFile) { +function mapEnvToLogoSuffix(envFile: string): string { let env = envFile.split('.')[2]; if (typeof env === 'undefined') { env = 'dev'; @@ -43,10 +43,6 @@ function mapEnvToLogoSuffix(envFile) { /** * Get a production grade config for web or desktop - * @param {Object} env - * @param {String} env.envFile path to the env file to be used - * @param {'web'|'desktop'} env.platform - * @returns {Configuration} */ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ mode: 'production', @@ -276,4 +272,4 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ }, }); -module.exports = webpackConfig; +export default webpackConfig; From 866211c240e8fc27f1333f362633be44cc8dc231 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 09:15:38 +0100 Subject: [PATCH 206/699] migrate webpack.desktop.js to TypeScript --- config/webpack/{webpack.desktop.js => webpack.desktop.ts} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename config/webpack/{webpack.desktop.js => webpack.desktop.ts} (95%) diff --git a/config/webpack/webpack.desktop.js b/config/webpack/webpack.desktop.ts similarity index 95% rename from config/webpack/webpack.desktop.js rename to config/webpack/webpack.desktop.ts index 2612e2b190fa..252538b8b072 100644 --- a/config/webpack/webpack.desktop.js +++ b/config/webpack/webpack.desktop.ts @@ -1,6 +1,6 @@ -const path = require('path'); -const webpack = require('webpack'); -const _ = require('underscore'); +import path from 'path'; +import _ from 'underscore'; +import webpack from 'webpack'; const desktopDependencies = require('../../desktop/package.json').dependencies; const getCommonConfig = require('./webpack.common'); From 847b4b7cf938ca19a232e2661e6207adade91d6f Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 09:16:07 +0100 Subject: [PATCH 207/699] start migrating webpack.dev.js to TypeScript --- .../{webpack.dev.js => webpack.dev.ts} | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) rename config/webpack/{webpack.dev.js => webpack.dev.ts} (85%) diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.ts similarity index 85% rename from config/webpack/webpack.dev.js rename to config/webpack/webpack.dev.ts index e28383eff557..9c5cb94aa86d 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.ts @@ -1,18 +1,22 @@ -const path = require('path'); -const portfinder = require('portfinder'); -const {DefinePlugin} = require('webpack'); -const {merge} = require('webpack-merge'); -const {TimeAnalyticsPlugin} = require('time-analytics-webpack-plugin'); -const getCommonConfig = require('./webpack.common'); +/* eslint-disable @typescript-eslint/naming-convention */ +import path from 'path'; +import portfinder from 'portfinder'; +import {TimeAnalyticsPlugin} from 'time-analytics-webpack-plugin'; +import {DefinePlugin} from 'webpack'; +import {merge} from 'webpack-merge'; +import getCommonConfig from './webpack.common'; const BASE_PORT = 8082; +type EnvFile = Partial<{ + envFile: string; + platform: 'web' | 'desktop'; +}>; + /** * Configuration for the local dev server - * @param {Object} env - * @returns {Configuration} */ -module.exports = (env = {}) => +module.exports = (env: EnvFile = {}) => portfinder.getPortPromise({port: BASE_PORT}).then((port) => { // Check if the USE_WEB_PROXY variable has been provided // and rewrite any requests to the local proxy server From b29a9b336e80c3534adc54b7b01df40c1262f5fb Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 09:20:11 +0100 Subject: [PATCH 208/699] add module declaration for preload-webpack-plugin --- src/types/modules/preload-webpack-plugin.d.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/types/modules/preload-webpack-plugin.d.ts diff --git a/src/types/modules/preload-webpack-plugin.d.ts b/src/types/modules/preload-webpack-plugin.d.ts new file mode 100644 index 000000000000..8f9d33a51080 --- /dev/null +++ b/src/types/modules/preload-webpack-plugin.d.ts @@ -0,0 +1,16 @@ +declare module '@vue/preload-webpack-plugin' { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions + interface Options { + rel: string; + as: string; + fileWhitelist: RegExp[]; + include: string; + } + + declare class PreloadWebpackPlugin { + constructor(options?: Options); + apply(compiler: Compiler): void; + } + + export default PreloadWebpackPlugin; +} From 7ad015da9a4a45231f168efeebbc0b795201d346 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 09:21:30 +0100 Subject: [PATCH 209/699] update scripts and documentation --- desktop/README.md | 2 +- scripts/build-desktop.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/README.md b/desktop/README.md index 77abff97a898..4ef763c6fedf 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -90,7 +90,7 @@ mc policy set public electron-builder/electron-builder Once you have Min.IO setup and running, the next step is to temporarily revert some changes from https://github.com/Expensify/App/commit/b640b3010fd7a40783d1c04faf4489836e98038d, specifically 1. Update the `desktop-build` command in package.json to add `--publish always` at the end -2. Update electronBuilder.config.js to replace the `publish` value with the following: +2. Update electronBuilder.config.ts to replace the `publish` value with the following: ``` publish: [{ provider: 's3', diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh index 025559dc4671..2354ab9fdaa2 100755 --- a/scripts/build-desktop.sh +++ b/scripts/build-desktop.sh @@ -25,4 +25,4 @@ npx webpack --config config/webpack/webpack.desktop.js --env envFile=$ENV_FILE title "Building Desktop App Archive Using Electron" info "" shift 1 -npx electron-builder --config config/electronBuilder.config.js --publish always "$@" +npx electron-builder --config config/electronBuilder.config.ts --publish always "$@" From 0db97cfa2c0b4f31c02b1b8a05886516946172c3 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 4 Mar 2024 10:29:37 +0100 Subject: [PATCH 210/699] Remove unnecessary code --- src/components/MoneyRequestConfirmationList.js | 3 --- .../workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx | 2 +- .../workspace/workflows/WorkspaceWorkflowsApproverPage.tsx | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index dfe1d96b0e5d..b3a43cf583ea 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -378,14 +378,12 @@ function MoneyRequestConfirmationList(props) { title: translate('moneyRequestConfirmationList.paidBy'), data: [formattedPayeeOption], shouldShow: true, - indexOffset: 0, isDisabled: shouldDisablePaidBySection, }, { title: translate('moneyRequestConfirmationList.splitWith'), data: formattedParticipantsList, shouldShow: true, - indexOffset: 1, }, ); } else { @@ -397,7 +395,6 @@ function MoneyRequestConfirmationList(props) { title: translate('common.to'), data: formattedSelectedParticipants, shouldShow: true, - indexOffset: 0, }); } return sections; diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx index 84d70e799c42..49c353f8b6aa 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx @@ -83,7 +83,7 @@ function WorkspaceAutoReportingMonthlyOffsetPage({policy}: WorkspaceAutoReportin /> 0, - indexOffset: 0, }); sectionsArray.push({ title: translate('common.all'), data: formattedPolicyMembers, shouldShow: true, - indexOffset: formattedApprover.length, }); return sectionsArray; From 9dd9dad0f9411b07e0943eb216feb0e537c91afc Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 4 Mar 2024 11:10:00 +0100 Subject: [PATCH 211/699] remove ts-expect-error --- src/pages/EditRequestTaxRatePage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/EditRequestTaxRatePage.tsx b/src/pages/EditRequestTaxRatePage.tsx index 1c1dc8219ae2..099851e92209 100644 --- a/src/pages/EditRequestTaxRatePage.tsx +++ b/src/pages/EditRequestTaxRatePage.tsx @@ -28,7 +28,6 @@ function EditRequestTaxRatePage({defaultTaxRate, policyID, onSubmit}: EditReques <> Date: Mon, 4 Mar 2024 11:29:19 +0100 Subject: [PATCH 212/699] fix tax picker types --- src/components/TaxPicker.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/TaxPicker.tsx b/src/components/TaxPicker.tsx index 50e534dc9f6f..cfccb7604044 100644 --- a/src/components/TaxPicker.tsx +++ b/src/components/TaxPicker.tsx @@ -2,8 +2,6 @@ import React, {useMemo, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import type {EdgeInsets} from 'react-native-safe-area-context'; -import _ from 'underscore'; -import OptionsSelector from '@components/OptionsSelector'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -12,6 +10,7 @@ import * as TransactionUtils from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy} from '@src/types/onyx'; +import OptionsSelector from './OptionsSelector'; type TaxPickerOnyxProps = { /** The policy which the user has access to and which the report is tied to */ @@ -23,7 +22,8 @@ type TaxPickerProps = TaxPickerOnyxProps & { selectedTaxRate?: string; /** ID of the policy */ - policyID: string; + // eslint-disable-next-line react/no-unused-prop-types + policyID?: string; /** * Safe area insets required for reflecting the portion of the view, From 2197773a853b801bdabd3cccbb3d3ada812b629d Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 4 Mar 2024 22:15:19 +0700 Subject: [PATCH 213/699] middle align the ui --- .../LHNOptionsList/LHNOptionsList.tsx | 51 +++++++++---------- src/libs/SidebarUtils.ts | 8 --- src/styles/index.ts | 4 ++ 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 40c79c45946c..37354571c2ae 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -46,6 +46,7 @@ function LHNOptionsList({ const {canUseViolations} = usePermissions(); const {translate} = useLocalize(); const {isExtraSmallScreenHeight, isSmallScreenWidth} = useWindowDimensions(); + const shouldShowEmptyUI = isSmallScreenWidth && data.length === 0; // When the first item renders we want to call the onFirstItemRendered callback. // At this point in time we know that the list is actually displaying items. @@ -157,33 +158,31 @@ function LHNOptionsList({ ], ); - if (isSmallScreenWidth && data.length === 0) { - return ( - - ); - } - return ( - - + + {shouldShowEmptyUI ? ( + + ) : ( + + )} ); } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index a9cbefddec94..9f6767406c36 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -96,14 +96,6 @@ function getOrderedReportIDs( }); }); - if (reportsToDisplay.length === 0) { - // Display Concierge chat report when there is no report to be displayed - const conciergeChatReport = allReportsDictValues.find(ReportUtils.isConciergeChatReport); - if (conciergeChatReport) { - reportsToDisplay.push(conciergeChatReport); - } - } - // The LHN is split into four distinct groups, and each group is sorted a little differently. The groups will ALWAYS be in this order: // 1. Pinned/GBR - Always sorted by reportDisplayName // 2. Drafts - Always sorted by reportDisplayName diff --git a/src/styles/index.ts b/src/styles/index.ts index 5a6266759331..68f9772c6158 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3186,6 +3186,10 @@ const styles = (theme: ThemeColors) => alignItems: 'center', }, + emptyLHNWrapper: { + marginBottom: variables.bottomTabHeight, + }, + emptyLHNAnimation: { width: 180, height: 180, From 21f632801ad6f5566c5619fe0ab8cdfe924b4fee Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 16:32:52 +0100 Subject: [PATCH 214/699] fix webpack.common.ts --- config/webpack/webpack.common.ts | 1 + config/webpack/webpack.desktop.ts | 10 +++++----- config/webpack/webpack.dev.ts | 12 ++++++------ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts index 71ba7e584fde..b26a3a01e825 100644 --- a/config/webpack/webpack.common.ts +++ b/config/webpack/webpack.common.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import PreloadWebpackPlugin from '@vue/preload-webpack-plugin'; import {CleanWebpackPlugin} from 'clean-webpack-plugin'; import CopyPlugin from 'copy-webpack-plugin'; diff --git a/config/webpack/webpack.desktop.ts b/config/webpack/webpack.desktop.ts index 252538b8b072..397fb6f7a3fd 100644 --- a/config/webpack/webpack.desktop.ts +++ b/config/webpack/webpack.desktop.ts @@ -1,6 +1,6 @@ -import path from 'path'; -import _ from 'underscore'; -import webpack from 'webpack'; +const path = require('path'); +const _ = require('underscore'); +const webpack = require('webpack'); const desktopDependencies = require('../../desktop/package.json').dependencies; const getCommonConfig = require('./webpack.common'); @@ -10,8 +10,8 @@ const getCommonConfig = require('./webpack.common'); * 1. electron-main - the core that serves the app content * 2. web - the app content that would be rendered in electron * Everything is placed in desktop/dist and ready for packaging - * @param {Object} env - * @returns {webpack.Configuration[]} + * @param env + * @returns */ module.exports = (env) => { const rendererConfig = getCommonConfig({...env, platform: 'desktop'}); diff --git a/config/webpack/webpack.dev.ts b/config/webpack/webpack.dev.ts index 9c5cb94aa86d..0d9a44c0bf20 100644 --- a/config/webpack/webpack.dev.ts +++ b/config/webpack/webpack.dev.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import path from 'path'; -import portfinder from 'portfinder'; -import {TimeAnalyticsPlugin} from 'time-analytics-webpack-plugin'; -import {DefinePlugin} from 'webpack'; -import {merge} from 'webpack-merge'; -import getCommonConfig from './webpack.common'; +const path = require('path'); +const portfinder = require('portfinder'); +const {TimeAnalyticsPlugin} = require('time-analytics-webpack-plugin'); +const {DefinePlugin} = require('webpack'); +const {merge} = require('webpack-merge'); +const getCommonConfig = require('./webpack.common'); const BASE_PORT = 8082; From 59522b781097467f4003a3af1fec52c77ea511bb Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 5 Mar 2024 10:07:15 +0100 Subject: [PATCH 215/699] update imports in webpack.dev.ts --- config/webpack/webpack.dev.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/webpack/webpack.dev.ts b/config/webpack/webpack.dev.ts index 0d9a44c0bf20..45e79a3bf578 100644 --- a/config/webpack/webpack.dev.ts +++ b/config/webpack/webpack.dev.ts @@ -1,7 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import portfinder = require('portfinder'); +import TimeAnalyticsWebpackPlugin = require('time-analytics-webpack-plugin'); + +const {TimeAnalyticsPlugin} = TimeAnalyticsWebpackPlugin; const path = require('path'); -const portfinder = require('portfinder'); -const {TimeAnalyticsPlugin} = require('time-analytics-webpack-plugin'); const {DefinePlugin} = require('webpack'); const {merge} = require('webpack-merge'); const getCommonConfig = require('./webpack.common'); @@ -16,7 +18,7 @@ type EnvFile = Partial<{ /** * Configuration for the local dev server */ -module.exports = (env: EnvFile = {}) => +const getConfig = (env: EnvFile = {}) => portfinder.getPortPromise({port: BASE_PORT}).then((port) => { // Check if the USE_WEB_PROXY variable has been provided // and rewrite any requests to the local proxy server @@ -64,7 +66,7 @@ module.exports = (env: EnvFile = {}) => ], cache: { type: 'filesystem', - name: env.platform || 'default', + name: env.platform ?? 'default', buildDependencies: { // By default, webpack and loaders are build dependencies // This (also) makes all dependencies of this config file - build dependencies @@ -82,3 +84,5 @@ module.exports = (env: EnvFile = {}) => return TimeAnalyticsPlugin.wrap(config); }); + +export default getConfig; From 1acf6b24430446f1820f47854a036b94e2fb998b Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 5 Mar 2024 11:04:44 +0100 Subject: [PATCH 216/699] Fix comments --- src/components/OptionsList/BaseOptionsList.tsx | 12 ++++++------ src/components/OptionsList/types.ts | 14 +++++++++----- src/components/SelectionList/BaseSelectionList.tsx | 9 ++++----- src/components/SelectionList/types.ts | 11 +++++++---- src/libs/getSectionsWithIndexOffset.ts | 6 ++++-- src/pages/EditReportFieldDropdownPage.tsx | 1 + 6 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/components/OptionsList/BaseOptionsList.tsx b/src/components/OptionsList/BaseOptionsList.tsx index 50e0ce31fcc2..436f4c147931 100644 --- a/src/components/OptionsList/BaseOptionsList.tsx +++ b/src/components/OptionsList/BaseOptionsList.tsx @@ -14,7 +14,7 @@ import type {OptionData} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import type {BaseOptionListProps, OptionsList, OptionsListData, Section} from './types'; +import type {BaseOptionListProps, OptionsList, OptionsListData, OptionsListDataWithIndexOffset, SectionWithIndexOffset} from './types'; function BaseOptionsList( { @@ -136,7 +136,7 @@ function BaseOptionsList( * [{header}, {sectionHeader}, {item}, {item}, {sectionHeader}, {item}, {item}, {footer}] */ // eslint-disable-next-line @typescript-eslint/naming-convention - const getItemLayout = (_data: OptionsListData[] | null, flatDataArrayIndex: number) => { + const getItemLayout = (_data: OptionsListDataWithIndexOffset[] | null, flatDataArrayIndex: number) => { if (!flattenedData.current[flatDataArrayIndex]) { flattenedData.current = buildFlatSectionArray(); } @@ -164,7 +164,7 @@ function BaseOptionsList( * @return {Component} */ - const renderItem: SectionListRenderItem = ({item, index, section}) => { + const renderItem: SectionListRenderItem = ({item, index, section}) => { const isItemDisabled = isDisabled || !!section.isDisabled || !!item.isDisabled; const isSelected = selectedOptions?.some((option) => { if (option.keyForList && option.keyForList === item.keyForList) { @@ -184,7 +184,7 @@ function BaseOptionsList( option={item} showTitleTooltip={showTitleTooltip} hoverStyle={optionHoveredStyle} - optionIsFocused={!disableFocusOptions && !isItemDisabled && focusedIndex === index + (section.indexOffset ?? 0)} + optionIsFocused={!disableFocusOptions && !isItemDisabled && focusedIndex === index + section.indexOffset} onSelectRow={onSelectRow} isSelected={isSelected} showSelectedState={canSelectMultipleOptions} @@ -205,7 +205,7 @@ function BaseOptionsList( /** * Function which renders a section header component */ - const renderSectionHeader = ({section: {title, shouldShow}}: {section: OptionsListData}) => { + const renderSectionHeader = ({section: {title, shouldShow}}: {section: OptionsListDataWithIndexOffset}) => { if (!title && shouldShow && !hideSectionHeaders && sectionHeaderStyle) { return ; } @@ -238,7 +238,7 @@ function BaseOptionsList( {headerMessage} ) : null} - + ref={ref} style={listStyles} indicatorStyle="white" diff --git a/src/components/OptionsList/types.ts b/src/components/OptionsList/types.ts index c8c117d800e4..9d7ed8ecf362 100644 --- a/src/components/OptionsList/types.ts +++ b/src/components/OptionsList/types.ts @@ -2,16 +2,15 @@ import type {RefObject} from 'react'; import type {SectionList, SectionListData, StyleProp, View, ViewStyle} from 'react-native'; import type {OptionData} from '@libs/ReportUtils'; -type OptionsList = SectionList; type OptionsListData = SectionListData; +type OptionsListDataWithIndexOffset = SectionListData; + +type OptionsList = SectionList; type Section = { /** Title of the section */ title: string; - /** The initial index of this section given the total number of options in each section's data array */ - indexOffset?: number; - /** Array of options */ data: OptionData[]; @@ -22,6 +21,11 @@ type Section = { isDisabled?: boolean; }; +type SectionWithIndexOffset = Section & { + /** The initial index of this section given the total number of options in each section's data array */ + indexOffset: number; +}; + type OptionsListProps = { /** option flexStyle for the options list container */ listContainerStyles?: StyleProp; @@ -134,4 +138,4 @@ type BaseOptionListProps = OptionsListProps & { listStyles?: StyleProp; }; -export type {OptionsListProps, BaseOptionListProps, Section, OptionsList, OptionsListData}; +export type {OptionsListProps, BaseOptionListProps, Section, OptionsList, OptionsListData, SectionWithIndexOffset, OptionsListDataWithIndexOffset}; diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 920716b230fc..0adb23115b15 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -22,7 +22,7 @@ import Log from '@libs/Log'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import type {BaseSelectionListProps, ButtonOrCheckBoxRoles, FlattenedSectionsReturn, ListItem, Section, SectionListDataType} from './types'; +import type {BaseSelectionListProps, ButtonOrCheckBoxRoles, FlattenedSectionsReturn, ListItem, SectionListDataType, SectionWithIndexOffset} from './types'; function BaseSelectionList( { @@ -70,7 +70,7 @@ function BaseSelectionList( ) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const listRef = useRef>>(null); + const listRef = useRef>>(null); const textInputRef = useRef(null); const focusTimeoutRef = useRef(null); const shouldShowTextInput = !!textInputLabel; @@ -276,9 +276,8 @@ function BaseSelectionList( ); }; - const renderItem = ({item, index, section}: SectionListRenderItemInfo>) => { - const indexOffset = section.indexOffset ? section.indexOffset : 0; - const normalizedIndex = index + indexOffset; + const renderItem = ({item, index, section}: SectionListRenderItemInfo>) => { + const normalizedIndex = index + section.indexOffset; const isDisabled = !!section.isDisabled || item.isDisabled; const isItemFocused = !isDisabled && focusedIndex === normalizedIndex; // We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade. diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 005a8ab21cc1..440ea31a97e8 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -143,9 +143,6 @@ type Section = { /** Title of the section */ title?: string; - /** The initial index of this section given the total number of options in each section's data array */ - indexOffset?: number; - /** Array of options */ data?: TItem[]; @@ -156,6 +153,11 @@ type Section = { shouldShow?: boolean; }; +type SectionWithIndexOffset = Section & { + /** The initial index of this section given the total number of options in each section's data array */ + indexOffset: number; +}; + type BaseSelectionListProps = Partial & { /** Sections for the section list */ sections: Array>>; @@ -293,12 +295,13 @@ type FlattenedSectionsReturn = { type ButtonOrCheckBoxRoles = 'button' | 'checkbox'; -type SectionListDataType = SectionListData>; +type SectionListDataType = SectionListData>; export type { BaseSelectionListProps, CommonListItemProps, Section, + SectionWithIndexOffset, BaseListItemProps, UserListItemProps, RadioListItemProps, diff --git a/src/libs/getSectionsWithIndexOffset.ts b/src/libs/getSectionsWithIndexOffset.ts index e6c0820374e8..7de78d048a4d 100644 --- a/src/libs/getSectionsWithIndexOffset.ts +++ b/src/libs/getSectionsWithIndexOffset.ts @@ -1,7 +1,9 @@ import type {SectionListData} from 'react-native'; -/** Returns a list of sections with IndexOffset */ -export default function getSectionsWithIndexOffset(sections: Array>) { +/** + * Returns a list of sections with indexOffset + */ +export default function getSectionsWithIndexOffset(sections: Array>): Array> { return sections.map((section, index) => { const indexOffset = [...sections].splice(0, index).reduce((acc, curr) => acc + (curr.data?.length ?? 0), 0); return {...section, indexOffset}; diff --git a/src/pages/EditReportFieldDropdownPage.tsx b/src/pages/EditReportFieldDropdownPage.tsx index 1ad63bb2bf2f..e8f1d97494af 100644 --- a/src/pages/EditReportFieldDropdownPage.tsx +++ b/src/pages/EditReportFieldDropdownPage.tsx @@ -92,6 +92,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldID, fieldValue, textInputLabel={translate('common.search')} boldStyle sections={sections} + // Focus the first option when searching focusedIndex={0} value={searchValue} onSelectRow={(option: Record) => onSubmit({[fieldID]: option.text})} From 7d67f31369e367a20f5a2135269ad2b668e5c383 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 5 Mar 2024 11:11:28 +0100 Subject: [PATCH 217/699] Remove unnecessary code --- src/pages/workspace/tags/WorkspaceTagsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index c82740eff361..7d9ceac74203 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -113,7 +113,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { {tagList.length ? ( {}} onSelectAll={toggleAllTags} From 91de72d8c60162fc6c47a05f059fa471a9845b1b Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Tue, 5 Mar 2024 19:20:40 +0100 Subject: [PATCH 218/699] updates, api request --- src/libs/API/parameters/OpenReportParams.ts | 4 +++ src/libs/GroupChatUtils.ts | 15 ++++++++ src/libs/actions/Report.ts | 39 +++++++++++++++++---- src/pages/NewChatConfirmPage.tsx | 39 ++++++++++----------- src/pages/NewChatPage.tsx | 10 +++--- src/types/onyx/NewGroupChat.ts | 1 - 6 files changed, 74 insertions(+), 34 deletions(-) diff --git a/src/libs/API/parameters/OpenReportParams.ts b/src/libs/API/parameters/OpenReportParams.ts index 477a002516de..0db845fccdc5 100644 --- a/src/libs/API/parameters/OpenReportParams.ts +++ b/src/libs/API/parameters/OpenReportParams.ts @@ -7,6 +7,10 @@ type OpenReportParams = { createdReportActionID?: string; clientLastReadTime?: string; idempotencyKey?: string; + groupChatAdminLogins?: string; + reportName?: string; + chatType?: string; + optimisticAccountIDList?: string; }; export default OpenReportParams; diff --git a/src/libs/GroupChatUtils.ts b/src/libs/GroupChatUtils.ts index 58a82de3df53..ab3ed521d7cd 100644 --- a/src/libs/GroupChatUtils.ts +++ b/src/libs/GroupChatUtils.ts @@ -1,8 +1,22 @@ import type {OnyxEntry} from 'react-native-onyx'; +import type {OptionData} from '@libs/ReportUtils'; import type {Report} from '@src/types/onyx'; import localeCompare from './LocaleCompare'; import * as ReportUtils from './ReportUtils'; +/** + * Returns the group chat name for confirm page + */ +function getGroupChatConfirmName(participants: OptionData[]): string | undefined { + const isMultipleParticipantReport = participants.length > 1; + + return participants + .map((participant) => ReportUtils.getDisplayNameForParticipant(participant.accountID!, isMultipleParticipantReport)) + .sort((first, second) => localeCompare(first ?? '', second ?? '')) + .filter(Boolean) + .join(', '); +} + /** * Returns the report name if the report is a group chat */ @@ -20,4 +34,5 @@ function getGroupChatName(report: OnyxEntry): string | undefined { export { // eslint-disable-next-line import/prefer-default-export getGroupChatName, + getGroupChatConfirmName, }; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 9dc5971701d1..d756edfe35d8 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -70,7 +70,16 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/NewRoomForm'; -import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportMetadata, ReportUserIsTyping} from '@src/types/onyx'; +import type { + NewGroupChat, + PersonalDetails, + PersonalDetailsList, + PolicyReportField, + RecentlyUsedReportFields, + ReportActionReactions, + ReportMetadata, + ReportUserIsTyping, +} from '@src/types/onyx'; import type {Decision, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type {NotificationPreference, RoomVisibility, WriteCapability} from '@src/types/onyx/Report'; import type Report from '@src/types/onyx/Report'; @@ -92,6 +101,7 @@ type ActionSubscriber = { let conciergeChatReportID: string | undefined; let currentUserAccountID = -1; +let currentUserEmail: string | undefined; Onyx.connect({ key: ONYXKEYS.SESSION, callback: (value) => { @@ -100,7 +110,7 @@ Onyx.connect({ conciergeChatReportID = undefined; return; } - + currentUserEmail = value.email; currentUserAccountID = value.accountID; }, }); @@ -218,6 +228,12 @@ Onyx.connect({ callback: (val) => (allRecentlyUsedReportFields = val), }); +let newGroupDraft: OnyxEntry; +Onyx.connect({ + key: ONYXKEYS.NEW_GROUP, + callback: (val) => (newGroupDraft = val), +}); + function startNewChat() { clearGroupChat(); Navigation.navigate(ROUTES.NEW); @@ -626,6 +642,13 @@ function openReport( idempotencyKey: `${SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT}_${reportID}`, }; + if (newReportObject.chatType === CONST.REPORT.CHAT_TYPE.GROUP_CHAT) { + parameters.chatType = CONST.REPORT.CHAT_TYPE.GROUP_CHAT; + parameters.groupChatAdminLogins = currentUserEmail; + parameters.optimisticAccountIDList = participantAccountIDList ? participantAccountIDList.join(',') : ''; + parameters.reportName = newReportObject.reportName ?? ''; + } + if (isFromDeepLink) { parameters.shouldRetry = false; } @@ -758,14 +781,18 @@ function openReport( * @param userLogins list of user logins to start a chat report with. * @param shouldDismissModal a flag to determine if we should dismiss modal before navigate to report or navigate to report directly. */ -function navigateToAndOpenReport(userLogins: string[], shouldDismissModal = true, memberRoles?: string[], reportName?: string) { +function navigateToAndOpenReport(userLogins: string[], shouldDismissModal = true, reportName?: string) { let newChat: ReportUtils.OptimisticChatReport | EmptyObject = {}; const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins(userLogins); const chat = ReportUtils.getChatByParticipants(participantAccountIDs); if (!chat) { - newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs); + if (newGroupDraft) { + newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs, reportName, CONST.REPORT.CHAT_TYPE.GROUP_CHAT); + } else { + newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs); + } } const report = chat ?? newChat; @@ -2927,8 +2954,8 @@ function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEnt API.write(WRITE_COMMANDS.RESOLVE_ACTIONABLE_MENTION_WHISPER, parameters, {optimisticData, failureData}); } -function setGroupDraft(invitedUsersIDs: number[], groupChatAdminLogins: string[] = [], reportName: string = '') { - Onyx.set(ONYXKEYS.NEW_GROUP, {selectedOptions: invitedUsersIDs, groupChatAdminLogins, reportName}); +function setGroupDraft(invitedUsersIDs: number[], reportName: string = '') { + Onyx.set(ONYXKEYS.NEW_GROUP, {selectedOptions: invitedUsersIDs, reportName}); } function clearGroupChat() { diff --git a/src/pages/NewChatConfirmPage.tsx b/src/pages/NewChatConfirmPage.tsx index c48b6d62c053..0f83689e47c9 100644 --- a/src/pages/NewChatConfirmPage.tsx +++ b/src/pages/NewChatConfirmPage.tsx @@ -7,8 +7,10 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OptionsSelector from '@components/OptionsSelector'; import ScreenWrapper from '@components/ScreenWrapper'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as GroupChatUtils from '@libs/GroupChatUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -30,36 +32,33 @@ type NewChatConfirmPageOnyxProps = { type NewChatConfirmPageProps = NewChatConfirmPageOnyxProps; function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmPageProps) { - const [selectedOptions, setSelectedOptions] = useState([]); - const [options, setOptions] = useState([]); - const [currentUserOption, setCurrentUserOption] = useState(); const {translate} = useLocalize(); const styles = useThemeStyles(); - const groupName = options.map((invitedUser) => (invitedUser.participantsList ? invitedUser.participantsList[0].firstName : '')).join(', '); + const personalData = useCurrentUserPersonalDetails() || CONST.EMPTY_OBJECT; - useEffect(() => { + const selectedOptions = useMemo(() => { const invitedUsersPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(newGroupDraft?.selectedOptions, allPersonalDetails); const members = OptionsListUtils.getMemberInviteOptions(invitedUsersPersonalDetails); const currentUserOptionData = members.currentUserOption; const options = [...members.personalDetails, currentUserOptionData] as OptionData[]; - - setCurrentUserOption(currentUserOptionData!); - setOptions(options); - setSelectedOptions(options); + return options; }, [newGroupDraft]); + const groupName = GroupChatUtils.getGroupChatConfirmName(selectedOptions); + const sections = useMemo(() => { const sectionsList = []; - if (options) { + if (selectedOptions) { sectionsList.push({ title: translate('common.members'), - data: options, + data: selectedOptions, shouldShow: true, indexOffset: 0, }); } return sectionsList; - }, [options, translate, selectedOptions]); + }, [translate, selectedOptions]); + /** * Removes a selected option from list if already selected. */ @@ -69,27 +68,25 @@ function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmP } const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.login === option.login); - let newSelectedOptions; - - if (isOptionInList && currentUserOption && option.accountID === currentUserOption.accountID) { + if (isOptionInList && personalData && option.accountID === personalData.accountID) { return; } if (isOptionInList) { - newSelectedOptions = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login); - setSelectedOptions(newSelectedOptions); + const newSelectedAccountIDs = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login).map((option) => option.accountID) as number[]; + Report.setGroupDraft(newSelectedAccountIDs); } }; const createGroup = () => { - const logins = selectedOptions.map((option) => option.login).filter((login): login is string => typeof login === 'string'); + const optionsWithoutCreator = selectedOptions.filter((selectedOption: OptionData) => selectedOption.accountID !== personalData.accountID); + const logins = optionsWithoutCreator.map((option: OptionData) => option.login) as string[]; if (logins.length < 1) { return; } const accountIDs = selectedOptions.map((selectedOption: OptionData) => selectedOption.accountID) as number[]; - const creatorLogin = [currentUserOption?.login] as string[]; - Report.setGroupDraft(accountIDs, creatorLogin, groupName); - // Report.navigateToAndOpenReport(logins, true, [], ''); + Report.setGroupDraft(accountIDs, groupName); + Report.navigateToAndOpenReport(logins, true, ''); }; const navigateBack = () => { diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index b6cd0385aeaf..4e017777181c 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -4,10 +4,10 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; -import {usePersonalDetails} from '@components/OnyxProvider'; import OptionsSelector from '@components/OptionsSelector'; import ScreenWrapper from '@components/ScreenWrapper'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useSearchTermAndSearch from '@hooks/useSearchTermAndSearch'; @@ -64,7 +64,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF const {isSmallScreenWidth} = useWindowDimensions(); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); - const personalData = usePersonalDetails() || CONST.EMPTY_OBJECT; + const personalData = useCurrentUserPersonalDetails() || CONST.EMPTY_OBJECT; const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached); @@ -179,10 +179,8 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF return; } const selectedAccountIDs = selectedOptions.map((option) => option.accountID) as number[]; - const user = OptionsListUtils.getMemberInviteOptions(personalData); - const currentUserOption = user.currentUserOption; - if (currentUserOption) { - const accountIDs = [...selectedAccountIDs, currentUserOption.accountID] as number[]; + if (personalData) { + const accountIDs = [...selectedAccountIDs, personalData.accountID] as number[]; Report.setGroupDraft(accountIDs); Navigation.navigate(ROUTES.NEW_CHAT_CONFIRM); } diff --git a/src/types/onyx/NewGroupChat.ts b/src/types/onyx/NewGroupChat.ts index b9a0d88e107d..57fd2b1c93ae 100644 --- a/src/types/onyx/NewGroupChat.ts +++ b/src/types/onyx/NewGroupChat.ts @@ -1,6 +1,5 @@ type NewGroupChat = { selectedOptions: number[]; - groupChatAdminLogins: string[]; reportName: string; }; From 0f215046bb6bc069d00a4287ed3baf2d1044588d Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 5 Mar 2024 23:04:04 -0800 Subject: [PATCH 219/699] update utils files to no longer reference report.transactionThreadReportID --- src/libs/ReportActionsUtils.ts | 45 ++++++++++++++++++++-------------- src/libs/ReportUtils.ts | 4 +-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 47323ad8cd56..d0682861dd50 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -205,6 +205,31 @@ function isTransactionThread(parentReportAction: OnyxEntry): boole ); } +function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxCollection | null): string { + const moneyRequestReportActions = reportActions && !_.isEmpty(reportActions) ? reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] : allReportActions?.[reportID]; + const moneyRequestReportActionsArray = Object.values(moneyRequestReportActions ?? {}); + + if (!moneyRequestReportActionsArray.length) { + return '0'; + } + + // Get all IOU report actions for the report. + const iouRequestTypes: Array> = [CONST.IOU.REPORT_ACTION_TYPE.CREATE, CONST.IOU.REPORT_ACTION_TYPE.SPLIT]; + const iouRequestActions = moneyRequestReportActionsArray.filter((action) => + action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU + && (iouRequestTypes.includes(action.originalMessage.type) ?? []) + && action.childReportID + ); + + // If we don't have any IOU request actions, or we have more than one IOU request actions, this isn't a oneTransaction report and we don't want to return the transactionThreadReportActions + if (!iouRequestActions.length || iouRequestActions.length > 1) { + return '0'; + } + + // Ensure we have a childReportID associated with the IOU report action + return String(iouRequestActions[0].childReportID); +} + /** * Sort an array of reportActions by their created timestamp first, and reportActionID second * This gives us a stable order even in the case of multiple reportActions created on the same millisecond @@ -711,24 +736,6 @@ function getAllReportActions(reportID: string): ReportActions { return allReportActions?.[reportID] ?? {}; } -/** - * Gets an array of IOU report actions - */ -function getIOUReportActions(reportID: string): ReportAction[] | null { - const reportActions = Object.values(getAllReportActions(reportID)); - if (!reportActions.length) { - return null; - } - - const iouRequestTypes: Array> = [CONST.IOU.REPORT_ACTION_TYPE.CREATE, CONST.IOU.REPORT_ACTION_TYPE.SPLIT]; - const iouRequestActions = reportActions?.filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && iouRequestTypes.includes(action.originalMessage.type)) ?? []; - - if (!iouRequestActions.length) { - return null; - } - return iouRequestActions; -} - /** * Check whether a report action is an attachment (a file, such as an image or a zip). * @@ -919,8 +926,8 @@ function isCurrentActionUnread(report: Report | EmptyObject, reportAction: Repor export { extractLinksFromMessageHtml, + getOneTransactionThreadReportID, getAllReportActions, - getIOUReportActions, getIOUReportIDFromReportActionPreview, getLastClosedReportAction, getLastVisibleAction, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b3d9da91c616..3963eb90127f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1206,7 +1206,7 @@ function isMoneyRequestReport(reportOrID: OnyxEntry | string): boolean { * Checks if a report has only one transaction associated with it */ function isOneTransactionReport(report: OnyxEntry): boolean { - return report?.transactionThreadReportID !== undefined; + return ReportActionsUtils.getOneTransactionThreadReportID(report?.reportID ?? '0', null) !== '0' } /** @@ -1217,7 +1217,7 @@ function isOneTransactionThread(reportID: string, parentReport: OnyxEntry Date: Tue, 5 Mar 2024 23:05:11 -0800 Subject: [PATCH 220/699] use general report and reportActions onyx keys instead of relying on report.transactionThreadReportID --- src/pages/home/ReportScreen.js | 25 +++++++++----- src/pages/home/report/ReportActionItem.js | 40 +++++++++++++++-------- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index d0d74274a14e..b874fb7ae393 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -65,6 +65,9 @@ const propTypes = { /** The report metadata loading states */ reportMetadata: reportMetadataPropTypes, + /** All the report actions stored in Onyx */ + allReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + /** All the report actions for this report */ reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), @@ -107,6 +110,7 @@ const propTypes = { const defaultProps = { isSidebarLoaded: false, reportActions: [], + allReportActions: {}, transactionThreadReportActions: [], parentReportAction: {}, report: {}, @@ -146,8 +150,8 @@ function ReportScreen({ route, report: reportProp, reportMetadata, + allReportActions, reportActions, - transactionThreadReportActions, parentReportAction, accountManagerReportID, markReadyForHydration, @@ -212,7 +216,6 @@ function ReportScreen({ policyName: reportProp.policyName, isOptimisticReport: reportProp.isOptimisticReport, lastMentionedTime: reportProp.lastMentionedTime, - transactionThreadReportID: reportProp.transactionThreadReportID, }), [ reportProp.lastReadTime, @@ -250,7 +253,6 @@ function ReportScreen({ reportProp.policyName, reportProp.isOptimisticReport, reportProp.lastMentionedTime, - reportProp.transactionThreadReportID, ], ); @@ -286,6 +288,13 @@ function ReportScreen({ const policy = policies[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`] || {}; const isTopMostReportId = currentReportID === getReportID(route); const didSubscribeToReportLeavingEvents = useRef(false); + const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(reportID, allReportActions); + const transactionThreadReportActions = useMemo(() => { + if (transactionThreadReportID) { + return null; + } + return ReportActionsUtils.getSortedReportActionsForDisplay(Object.values(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportReportID}`])); + }, [allReportActions, transactionThreadReportID]); useEffect(() => { if (!report.reportID || shouldHideReport) { @@ -617,16 +626,14 @@ export default compose( isSidebarLoaded: { key: ONYXKEYS.IS_SIDEBAR_LOADED, }, + allReportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + }, reportActions: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getReportID(route)}`, canEvict: false, selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), }, - transactionThreadReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? (report.transactionThreadReportID || '0') : '0'}`, - canEvict: false, - selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), - }, report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${getReportID(route)}`, allowStaleData: true, @@ -678,8 +685,8 @@ export default compose( ReportScreen, (prevProps, nextProps) => prevProps.isSidebarLoaded === nextProps.isSidebarLoaded && + _.isEqual(prevProps.allReportActions, nextProps.allReportActions) && _.isEqual(prevProps.reportActions, nextProps.reportActions) && - _.isEqual(prevProps.transactionThreadReportActions, nextProps.transactionThreadReportActions) && _.isEqual(prevProps.reportMetadata, nextProps.reportMetadata) && prevProps.isComposerFullSize === nextProps.isComposerFullSize && _.isEqual(prevProps.betas, nextProps.betas) && diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 3baba4e2ea22..57794494d34f 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -111,12 +111,15 @@ const propTypes = { emojiReactions: EmojiReactionsPropTypes, + /** All reportActions shared with the user */ + reportActions: PropTypes.objectOf(reportActionPropTypes), + + /** All reports shared with the user */ + reports: PropTypes.objectOf(reportPropTypes), + /** IOU report for this action, if any */ iouReport: reportPropTypes, - /** Single transaction thread associated with the report, if any */ - transactionThreadReport: reportPropTypes, - /** Flag to show, hide the thread divider line */ shouldHideThreadDividerLine: PropTypes.bool, @@ -135,8 +138,9 @@ const defaultProps = { preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, emojiReactions: {}, shouldShowSubscriptAvatar: false, + reportActions: {}, + reports: {}, iouReport: undefined, - transactionThreadReport: undefined, shouldHideThreadDividerLine: false, userWallet: {}, parentReportActions: {}, @@ -160,6 +164,13 @@ function ReportActionItem(props) { const originalReportID = ReportUtils.getOriginalReportID(props.report.reportID, props.action); const originalReport = props.report.reportID === originalReportID ? props.report : ReportUtils.getReport(originalReportID); const isReportActionLinked = props.linkedReportActionID && props.action.reportActionID && props.linkedReportActionID === props.action.reportActionID; + const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(props.report.reportID, props.allReportActions); + const transactionThreadReport = useMemo(() => { + if (transactionThreadReportID) { + return null; + } + return props.reports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportReportID}`]; + }, [props.reports, transactionThreadReportID]); const reportScrollManager = useReportScrollManager(); @@ -701,9 +712,9 @@ function ReportActionItem(props) { if (ReportUtils.isExpenseReport(props.report) || ReportUtils.isIOUReport(props.report)) { return ( - {props.transactionThreadReport && !isEmptyObject(props.transactionThreadReport) ? ( + {transactionThreadReport && !isEmptyObject(transactionThreadReport) ? ( <> - {props.transactionThreadReport.currency !== props.report.currency && ( + {transactionThreadReport.currency !== props.report.currency && ( @@ -881,6 +892,12 @@ export default compose( key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE, }, + reportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + }, + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, iouReport: { key: ({action}) => { const iouReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action); @@ -888,10 +905,6 @@ export default compose( }, initialValue: {}, }, - transactionThreadReport: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.transactionThreadReportID || 0}`, - initialValue: {}, - }, policyReportFields: { key: ({report}) => (report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID}` : undefined), initialValue: [], @@ -921,13 +934,14 @@ export default compose( prevProps.draftMessage === nextProps.draftMessage && prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && + _.isEqual(prevProps.reportActions, nextProps.reportActions) && + _.isEqual(prevProps.reports, nextProps.reports) && _.isEqual(prevProps.emojiReactions, nextProps.emojiReactions) && _.isEqual(prevProps.action, nextProps.action) && _.isEqual(prevProps.iouReport, nextProps.iouReport) && _.isEqual(prevProps.report.pendingFields, nextProps.report.pendingFields) && _.isEqual(prevProps.report.isDeletedParentAction, nextProps.report.isDeletedParentAction) && _.isEqual(prevProps.report.errorFields, nextProps.report.errorFields) && - _.isEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && lodashGet(prevProps.report, 'statusNum') === lodashGet(nextProps.report, 'statusNum') && lodashGet(prevProps.report, 'stateNum') === lodashGet(nextProps.report, 'stateNum') && lodashGet(prevProps.report, 'parentReportID') === lodashGet(nextProps.report, 'parentReportID') && From 1819da7109e6cff37c0e53db44eec79e8c7aa148 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 6 Mar 2024 09:07:37 +0100 Subject: [PATCH 221/699] update scripts in package.json, install webpack types --- package-lock.json | 100 +++++++++++++++++++++++++++++++++++++--------- package.json | 11 ++--- 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 31933d6f3596..3f9d80e1417d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -184,6 +184,7 @@ "@types/semver": "^7.5.4", "@types/setimmediate": "^1.0.2", "@types/underscore": "^1.11.5", + "@types/webpack": "^5.28.5", "@types/webpack-bundle-analyzer": "^4.7.0", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", @@ -13261,6 +13262,20 @@ "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==", "dev": true }, + "node_modules/@storybook/builder-webpack4/node_modules/@types/webpack": { + "version": "4.41.38", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.38.tgz", + "integrity": "sha512-oOW7E931XJU1mVfCnxCVgv8GLFL768pDO5u2Gzk82i8yTIgX6i7cntyZOkZYb/JtYM8252SN9bQp9tgkVDSsRw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/@storybook/builder-webpack4/node_modules/@webassemblyjs/helper-buffer": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", @@ -16068,6 +16083,20 @@ "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==", "dev": true }, + "node_modules/@storybook/core-server/node_modules/@types/webpack": { + "version": "4.41.38", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.38.tgz", + "integrity": "sha512-oOW7E931XJU1mVfCnxCVgv8GLFL768pDO5u2Gzk82i8yTIgX6i7cntyZOkZYb/JtYM8252SN9bQp9tgkVDSsRw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/@storybook/core-server/node_modules/@webassemblyjs/helper-buffer": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", @@ -17167,6 +17196,20 @@ "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==", "dev": true }, + "node_modules/@storybook/manager-webpack4/node_modules/@types/webpack": { + "version": "4.41.38", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.38.tgz", + "integrity": "sha512-oOW7E931XJU1mVfCnxCVgv8GLFL768pDO5u2Gzk82i8yTIgX6i7cntyZOkZYb/JtYM8252SN9bQp9tgkVDSsRw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/@storybook/manager-webpack4/node_modules/@webassemblyjs/helper-buffer": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", @@ -21110,11 +21153,10 @@ } }, "node_modules/@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true, - "license": "MIT" + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.6.tgz", + "integrity": "sha512-5JcVt1u5HDmlXkwOD2nslZVllBBc7HDuOICfiZah2Z0is8M8g+ddAEawbmd3VjedfDHBzxCaXLs07QEmb7y54g==", + "dev": true }, "node_modules/@types/stack-utils": { "version": "2.0.1", @@ -21136,9 +21178,10 @@ "license": "MIT" }, "node_modules/@types/uglify-js": { - "version": "3.17.0", + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.5.tgz", + "integrity": "sha512-TU+fZFBTBcXj/GpDpDaBmgWk/gn96kMZ+uocaFUlV2f8a6WdMzzI44QBCmGcCiYR0Y6ZlNRiyUyKKt5nl/lbzQ==", "dev": true, - "license": "MIT", "dependencies": { "source-map": "^0.6.1" } @@ -21178,16 +21221,14 @@ "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==" }, "node_modules/@types/webpack": { - "version": "4.41.32", + "version": "5.28.5", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.5.tgz", + "integrity": "sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*", - "@types/tapable": "^1", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "anymatch": "^3.0.0", - "source-map": "^0.6.0" + "tapable": "^2.2.0", + "webpack": "^5" } }, "node_modules/@types/webpack-bundle-analyzer": { @@ -21216,11 +21257,10 @@ "license": "MIT" }, "node_modules/@types/webpack-sources": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", - "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-4nZOdMwSPHZ4pTEZzSp0AsTM4K7Qmu40UKW4tJDiOVs20UzYF9l+qUe4s0ftfN0pin06n+5cWWDJXH+sbhAiDw==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*", "@types/source-list-map": "*", @@ -21232,11 +21272,19 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">= 8" } }, + "node_modules/@types/webpack/node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@types/ws": { "version": "8.5.3", "dev": true, @@ -25982,6 +26030,20 @@ "webpack": "*" } }, + "node_modules/clean-webpack-plugin/node_modules/@types/webpack": { + "version": "4.41.38", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.38.tgz", + "integrity": "sha512-oOW7E931XJU1mVfCnxCVgv8GLFL768pDO5u2Gzk82i8yTIgX6i7cntyZOkZYb/JtYM8252SN9bQp9tgkVDSsRw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", diff --git a/package.json b/package.json index 614244de50bb..26969642c787 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,10 @@ "start": "npx react-native start", "web": "scripts/set-pusher-suffix.sh && concurrently npm:web-proxy npm:web-server", "web-proxy": "ts-node web/proxy.js", - "web-server": "webpack-dev-server --open --config config/webpack/webpack.dev.js", - "build": "webpack --config config/webpack/webpack.common.js --env envFile=.env.production", - "build-staging": "webpack --config config/webpack/webpack.common.js --env envFile=.env.staging", - "build-adhoc": "webpack --config config/webpack/webpack.common.js --env envFile=.env.adhoc", + "web-server": "webpack-dev-server --open --config config/webpack/webpack.dev.ts", + "build": "webpack --config config/webpack/webpack.common.ts --env envFile=.env.production", + "build-staging": "webpack --config config/webpack/webpack.common.ts --env envFile=.env.staging", + "build-adhoc": "webpack --config config/webpack/webpack.common.ts --env envFile=.env.adhoc", "desktop": "scripts/set-pusher-suffix.sh && ts-node desktop/start.js", "desktop-build": "scripts/build-desktop.sh production", "desktop-build-staging": "scripts/build-desktop.sh staging", @@ -47,7 +47,7 @@ "storybook-build-staging": "ENV=staging build-storybook -o dist/docs", "gh-actions-build": "./.github/scripts/buildActions.sh", "gh-actions-validate": "./.github/scripts/validateActionsAndWorkflows.sh", - "analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.js --env envFile=.env.production", + "analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.ts --env envFile=.env.production", "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", "test:e2e": "ts-node tests/e2e/testRunner.js --config ./config.local.ts", @@ -233,6 +233,7 @@ "@types/semver": "^7.5.4", "@types/setimmediate": "^1.0.2", "@types/underscore": "^1.11.5", + "@types/webpack": "^5.28.5", "@types/webpack-bundle-analyzer": "^4.7.0", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", From 05b5c439b3eaca8fd38c6cafdb8e117be4726fce Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 6 Mar 2024 09:20:43 +0100 Subject: [PATCH 222/699] update file extensions in documentation --- README.md | 2 +- desktop/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 72736b3fedb7..3f22cff7cb63 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ If you're using another operating system, you will need to ensure `mkcert` is in ## Running the web app 🕸 * To run the **development web app**: `npm run web` -* Changes applied to Javascript will be applied automatically via WebPack as configured in `webpack.dev.js` +* Changes applied to Javascript will be applied automatically via WebPack as configured in `webpack.dev.ts` ## Running the iOS app 📱 For an M1 Mac, read this [SO](https://stackoverflow.com/questions/64901180/how-to-run-cocoapods-on-apple-silicon-m1) for installing cocoapods. diff --git a/desktop/README.md b/desktop/README.md index 4ef763c6fedf..bd68ec571659 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -129,7 +129,7 @@ Once the command finishes, revert the version update in `package.json`, remove ` To avoid bundling unnecessary `node_modules` we use a [2 package structure](https://www.electron.build/tutorials/two-package-structure) The root [package.json](../package.json) serves for `devDependencies` and shared (renderer) `dependencies` The [desktop/package.json](./package.json) serves for desktop (electron-main) specific dependencies -We use Webpack with a [desktop specific config](../config/webpack/webpack.desktop.js) to bundle our js code +We use Webpack with a [desktop specific config](../config/webpack/webpack.desktop.ts) to bundle our js code Half of the config takes care of packaging root package dependencies - everything related to rendering App in the Electron window. Packaged under `dist/www` The other half is about bundling the `main.js` script which initializes Electron and renders `www` From 8bcb07885539f46c998659ba88e1049f92ad1e88 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 6 Mar 2024 09:29:03 +0100 Subject: [PATCH 223/699] update tsconfig --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 30708f63d12b..59f095d3f752 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,6 +31,7 @@ "moduleResolution": "node", "resolveJsonModule": true, "allowSyntheticDefaultImports": true, + "esModuleInterop": true, "skipLibCheck": true, "incremental": true, "baseUrl": ".", From 56e5b173a37a7087e65595f589f83b89ce450887 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 6 Mar 2024 09:30:26 +0100 Subject: [PATCH 224/699] update file extensions in documentation --- contributingGuides/APPLE_GOOGLE_SIGNIN.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contributingGuides/APPLE_GOOGLE_SIGNIN.md b/contributingGuides/APPLE_GOOGLE_SIGNIN.md index 4bb86e31b486..e62d78e020af 100644 --- a/contributingGuides/APPLE_GOOGLE_SIGNIN.md +++ b/contributingGuides/APPLE_GOOGLE_SIGNIN.md @@ -167,7 +167,7 @@ After you've set ngrok up to be able to run on your machine (requires configurin ngrok http 8082 --host-header="dev.new.expensify.com:8082" --subdomain=mysubdomain ``` -The `--host-header` flag is there to avoid webpack errors with header validation. In addition, add `allowedHosts: 'all'` to the dev server config in `webpack.dev.js`: +The `--host-header` flag is there to avoid webpack errors with header validation. In addition, add `allowedHosts: 'all'` to the dev server config in `webpack.dev.ts`: ```js devServer: { @@ -265,13 +265,13 @@ Google allows the web app to be hosted at localhost, but according to the current Google console configuration for the Expensify client ID, it must be hosted on port 8082. -Also note that you'll need to update the webpack.dev.js config to change `host` from `dev.new.expensify.com` to `localhost` and server type from `https` to `http`. The reason for this is that Google Sign In allows localhost, but `dev.new.expensify.com` is not a registered Google Sign In domain. +Also note that you'll need to update the webpack.dev.ts config to change `host` from `dev.new.expensify.com` to `localhost` and server type from `https` to `http`. The reason for this is that Google Sign In allows localhost, but `dev.new.expensify.com` is not a registered Google Sign In domain. ```diff -diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js +diff --git a/config/webpack/webpack.dev.ts b/config/webpack/webpack.dev.ts index e28383eff5..b14f6f34aa 100644 ---- a/config/webpack/webpack.dev.js -+++ b/config/webpack/webpack.dev.js +--- a/config/webpack/webpack.dev.ts ++++ b/config/webpack/webpack.dev.ts @@ -44,9 +44,9 @@ module.exports = (env = {}) => ...proxySettings, historyApiFallback: true, From b80efcca9aea1c1c2299cb3893ecf91780561e95 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 6 Mar 2024 09:52:40 +0100 Subject: [PATCH 225/699] update file extension in webpack.config.js --- .storybook/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 204f70344b18..07b57c687daf 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -28,7 +28,7 @@ module.exports = ({config}) => { '@react-native-community/netinfo': path.resolve(__dirname, '../__mocks__/@react-native-community/netinfo.ts'), '@react-navigation/native': path.resolve(__dirname, '../__mocks__/@react-navigation/native'), - // Module alias support for storybook files, coping from `webpack.common.js` + // Module alias support for storybook files, coping from `webpack.common.ts` ...custom.resolve.alias, }; From 7649b0298aad7ccd0dc02215445be820ee095d64 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 6 Mar 2024 10:05:48 +0100 Subject: [PATCH 226/699] migrate webpack.common.js to TypeScript --- config/webpack/webpack.common.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts index b26a3a01e825..2916064bf2a4 100644 --- a/config/webpack/webpack.common.ts +++ b/config/webpack/webpack.common.ts @@ -1,14 +1,16 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import PreloadWebpackPlugin from '@vue/preload-webpack-plugin'; -import {CleanWebpackPlugin} from 'clean-webpack-plugin'; -import CopyPlugin from 'copy-webpack-plugin'; -import dotenv from 'dotenv'; -import fs from 'fs'; -import HtmlWebpackPlugin from 'html-webpack-plugin'; -import path from 'path'; -import {DefinePlugin, EnvironmentPlugin, IgnorePlugin, ProvidePlugin} from 'webpack'; -import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'; -import CustomVersionFilePlugin from './CustomVersionFilePlugin'; +import type {Configuration} from 'webpack'; + +const {CleanWebpackPlugin} = require('clean-webpack-plugin'); +const CopyPlugin = require('copy-webpack-plugin'); +const dotenv = require('dotenv'); +const fs = require('fs'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const path = require('path'); +const {DefinePlugin, EnvironmentPlugin, IgnorePlugin, ProvidePlugin} = require('webpack'); +const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer'); +const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin'); +const CustomVersionFilePlugin = require('./CustomVersionFilePlugin'); const includeModules = [ 'react-native-animatable', @@ -45,7 +47,7 @@ function mapEnvToLogoSuffix(envFile: string): string { /** * Get a production grade config for web or desktop */ -const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ +const getCommonConfig = ({envFile = '.env', platform = 'web'}): Configuration => ({ mode: 'production', devtool: 'source-map', entry: { @@ -273,4 +275,4 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ }, }); -export default webpackConfig; +export default getCommonConfig; From a0ba0148abbbb681ba8e46c125b26661b694c59b Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 6 Mar 2024 10:08:48 +0100 Subject: [PATCH 227/699] migrate webpack.desktop.js to TypeScript --- config/webpack/webpack.desktop.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/config/webpack/webpack.desktop.ts b/config/webpack/webpack.desktop.ts index 397fb6f7a3fd..9c28afd614d5 100644 --- a/config/webpack/webpack.desktop.ts +++ b/config/webpack/webpack.desktop.ts @@ -1,27 +1,30 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import getCommonConfig from './webpack.common'; +import type {EnvFile} from './webpack.dev'; + const path = require('path'); -const _ = require('underscore'); const webpack = require('webpack'); const desktopDependencies = require('../../desktop/package.json').dependencies; -const getCommonConfig = require('./webpack.common'); /** * Desktop creates 2 configurations in parallel * 1. electron-main - the core that serves the app content * 2. web - the app content that would be rendered in electron * Everything is placed in desktop/dist and ready for packaging - * @param env - * @returns */ -module.exports = (env) => { +const getConfig = (env: EnvFile = {}) => { const rendererConfig = getCommonConfig({...env, platform: 'desktop'}); const outputPath = path.resolve(__dirname, '../../desktop/dist'); rendererConfig.name = 'renderer'; - rendererConfig.output.path = path.join(outputPath, 'www'); + if (rendererConfig.output) { + rendererConfig.output.path = path.join(outputPath, 'www'); + } // Expose react-native-config to desktop-main - const definePlugin = _.find(rendererConfig.plugins, (plugin) => plugin.constructor === webpack.DefinePlugin); + // const definePlugin = _.find(rendererConfig.plugins, (plugin) => plugin.constructor === webpack.DefinePlugin); + const definePlugin = rendererConfig.plugins?.find((plugin) => plugin?.constructor === webpack.DefinePlugin); const mainProcessConfig = { mode: 'production', @@ -38,7 +41,7 @@ module.exports = (env) => { }, resolve: rendererConfig.resolve, plugins: [definePlugin], - externals: [..._.keys(desktopDependencies), 'fsevents'], + externals: [...Object.keys(desktopDependencies), 'fsevents'], node: { /** * Disables webpack processing of __dirname and __filename, so it works like in node @@ -60,3 +63,5 @@ module.exports = (env) => { return [mainProcessConfig, rendererConfig]; }; + +export default getConfig; From 512cf42d93b4c7ddea593012515db28c7abbdf5c Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 6 Mar 2024 10:09:22 +0100 Subject: [PATCH 228/699] migrate webpack.dev.js to TypeScript --- config/webpack/webpack.dev.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/config/webpack/webpack.dev.ts b/config/webpack/webpack.dev.ts index 45e79a3bf578..56dc735edd86 100644 --- a/config/webpack/webpack.dev.ts +++ b/config/webpack/webpack.dev.ts @@ -1,12 +1,14 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import type webpack from 'webpack'; + import portfinder = require('portfinder'); import TimeAnalyticsWebpackPlugin = require('time-analytics-webpack-plugin'); +import getCommonConfig from './webpack.common'; const {TimeAnalyticsPlugin} = TimeAnalyticsWebpackPlugin; const path = require('path'); const {DefinePlugin} = require('webpack'); const {merge} = require('webpack-merge'); -const getCommonConfig = require('./webpack.common'); const BASE_PORT = 8082; @@ -18,7 +20,7 @@ type EnvFile = Partial<{ /** * Configuration for the local dev server */ -const getConfig = (env: EnvFile = {}) => +const getConfig = (env: EnvFile = {}): Promise => portfinder.getPortPromise({port: BASE_PORT}).then((port) => { // Check if the USE_WEB_PROXY variable has been provided // and rewrite any requests to the local proxy server @@ -86,3 +88,4 @@ const getConfig = (env: EnvFile = {}) => }); export default getConfig; +export type {EnvFile}; From 5ef518ace9b9fa291cd809ea9d86dc1482be6cc6 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 6 Mar 2024 10:14:50 +0100 Subject: [PATCH 229/699] update file extensions in scripts --- desktop/start.js | 4 ++-- scripts/build-desktop.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop/start.js b/desktop/start.js index 05a1b031350d..0802dabdda2f 100644 --- a/desktop/start.js +++ b/desktop/start.js @@ -10,8 +10,8 @@ portfinder port: basePort, }) .then((port) => { - const devServer = `webpack-dev-server --config config/webpack/webpack.dev.js --port ${port} --env platform=desktop`; - const buildMain = 'webpack watch --config config/webpack/webpack.desktop.js --config-name desktop-main --mode=development'; + const devServer = `webpack-dev-server --config config/webpack/webpack.dev.ts --port ${port} --env platform=desktop`; + const buildMain = 'webpack watch --config config/webpack/webpack.desktop.ts --config-name desktop-main --mode=development'; const env = { PORT: port, diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh index 2354ab9fdaa2..0c8a19c592e6 100755 --- a/scripts/build-desktop.sh +++ b/scripts/build-desktop.sh @@ -20,7 +20,7 @@ title "Bundling Desktop js Bundle Using Webpack" info " • ELECTRON_ENV: $ELECTRON_ENV" info " • ENV file: $ENV_FILE" info "" -npx webpack --config config/webpack/webpack.desktop.js --env envFile=$ENV_FILE +npx webpack --config config/webpack/webpack.desktop.ts --env envFile=$ENV_FILE title "Building Desktop App Archive Using Electron" info "" From 84010186e9196a3a4c0fe119fb54dca1f72761f4 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 6 Mar 2024 10:29:55 +0100 Subject: [PATCH 230/699] add useCallback for updateTaxAmount and updateTaxRate --- src/pages/EditRequestPage.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 638b3711dc2e..b428e3eaf820 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -125,17 +125,23 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p }); }, [parentReportAction, fieldToEdit]); - const updateTaxAmount = (transactionChanges) => { - const newTaxAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(transactionChanges.amount)); - IOU.updateMoneyRequestTaxAmount(transaction.transactionID, report.reportID, newTaxAmount, policy, policyTags, policyCategories); - Navigation.dismissModal(report.reportID); - }; - - const updateTaxRate = (transactionChanges) => { - const newTaxCode = transactionChanges.data.code; - IOU.updateMoneyRequestTaxRate(transaction.transactionID, report.reportID, newTaxCode, policy, policyTags, policyCategories); - Navigation.dismissModal(report.reportID); - }; + const updateTaxAmount = useCallback( + (transactionChanges) => { + const newTaxAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(transactionChanges.amount)); + IOU.updateMoneyRequestTaxAmount(transaction.transactionID, report.reportID, newTaxAmount, policy, policyTags, policyCategories); + Navigation.dismissModal(report.reportID); + }, + [transaction, report, policy, policyTags, policyCategories], + ); + + const updateTaxRate = useCallback( + (transactionChanges) => { + const newTaxCode = transactionChanges.data.code; + IOU.updateMoneyRequestTaxRate(transaction.transactionID, report.reportID, newTaxCode, policy, policyTags, policyCategories); + Navigation.dismissModal(report.reportID); + }, + [transaction, report, policy, policyTags, policyCategories], + ); const saveAmountAndCurrency = useCallback( ({amount, currency: newCurrency}) => { From 484c8ca9944314e0b7c220646d171ba935d8b1a9 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 6 Mar 2024 14:47:34 +0100 Subject: [PATCH 231/699] add tax types to OriginalMessageModifiedExpense --- src/types/onyx/OriginalMessage.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 06c2d2e6abce..eaab2ee6f1ba 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -251,6 +251,10 @@ type OriginalMessageModifiedExpense = { category?: string; oldTag?: string; tag?: string; + oldTaxAmount?: number; + taxAmount?: number; + oldTaxRate?: string; + taxRate?: string; oldBillable?: string; billable?: string; }; From e7e62a8f6762027ae217246e144318ed6540cfcd Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 6 Mar 2024 14:48:19 +0100 Subject: [PATCH 232/699] update getTaxAmount func --- src/libs/TransactionUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index f8976d1ca450..55bb591e3e6a 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -306,14 +306,14 @@ function getAmount(transaction: OnyxEntry, isFromExpenseReport = fa /** * Return the tax amount field from the transaction. */ -function getTaxAmount(taxAmount: number, isFromExpenseReport: boolean): number { +function getTaxAmount(transaction: OnyxEntry, isFromExpenseReport: boolean): number { // IOU requests cannot have negative values but they can be stored as negative values, let's return absolute value if (!isFromExpenseReport) { - return Math.abs(taxAmount ?? 0); + return Math.abs(transaction?.taxAmount ?? 0); } // To avoid -0 being shown, lets only change the sign if the value is other than 0. - const amount = taxAmount ?? 0; + const amount = transaction?.taxAmount ?? 0; return amount ? -amount : 0; } From 450f849975995ef04c1cec48ccaf0100870f2042 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 6 Mar 2024 14:49:19 +0100 Subject: [PATCH 233/699] add optimistic data for tax amount and code --- src/libs/ReportUtils.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a82e1fe6b941..77ae89f11e54 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2097,7 +2097,7 @@ function getTransactionDetails(transaction: OnyxEntry, createdDateF return { created: TransactionUtils.getCreated(transaction, createdDateFormat), amount: TransactionUtils.getAmount(transaction, !isEmptyObject(report) && isExpenseReport(report)), - taxAmount: TransactionUtils.getTaxAmount(transaction?.taxAmount ?? 0, !isEmptyObject(report) && isExpenseReport(report)), + taxAmount: TransactionUtils.getTaxAmount(transaction, !isEmptyObject(report) && isExpenseReport(report)), taxCode: TransactionUtils.getTaxCode(transaction), currency: TransactionUtils.getCurrency(transaction), comment: TransactionUtils.getDescription(transaction), @@ -2474,6 +2474,16 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry Date: Wed, 6 Mar 2024 14:49:45 +0100 Subject: [PATCH 234/699] update modified tax amount --- src/libs/ModifiedExpenseMessage.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 2855344899f0..fad823a8fded 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -62,6 +62,14 @@ function buildMessageFragmentForValue( } } +/** + * Get the absolute value for a tax amount. + */ +function getTaxAmountAbsValue(taxAmount: number): number { + // IOU requests cannot have negative values but they can be stored as negative values, let's return absolute value + return Math.abs(taxAmount ?? 0); +} + /** * Get the message line for a modified expense. */ @@ -227,13 +235,11 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr if (hasModifiedTaxAmount) { const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread?.parentReportID}`] ?? null; - const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); const currency = iouReport?.currency ?? ''; - const taxAmount = CurrencyUtils.convertToDisplayString(TransactionUtils.getTaxAmount(reportActionOriginalMessage?.taxAmount ?? 0, isFromExpenseReport), currency); - const oldTaxAmountValue = TransactionUtils.getTaxAmount(reportActionOriginalMessage?.oldTaxAmount ?? 0, isFromExpenseReport); + const taxAmount = CurrencyUtils.convertToDisplayString(getTaxAmountAbsValue(reportActionOriginalMessage?.taxAmount ?? 0), currency); + const oldTaxAmountValue = getTaxAmountAbsValue(reportActionOriginalMessage?.oldTaxAmount ?? 0); const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(oldTaxAmountValue, currency) : ''; - buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), false, setFragments, removalFragments, changeFragments); } From 452e0e06f8fa7b91372192e0a5bc7307f50e709d Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 6 Mar 2024 14:51:16 +0100 Subject: [PATCH 235/699] fix prettier --- src/libs/ReportUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 77ae89f11e54..9422b20beb02 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2474,9 +2474,9 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry Date: Wed, 6 Mar 2024 15:23:42 +0100 Subject: [PATCH 236/699] select tax value and pass to original message --- src/libs/ReportUtils.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9422b20beb02..a74ef227deb7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -31,6 +31,7 @@ import type { Transaction, TransactionViolation, } from '@src/types/onyx'; +import type * as OnyxTypes from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; import type { @@ -2438,7 +2439,12 @@ function getReportPreviewMessage( * * At the moment, we only allow changing one transaction field at a time. */ -function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry, transactionChanges: TransactionChanges, isFromExpenseReport: boolean): ExpenseOriginalMessage { +function getModifiedExpenseOriginalMessage( + oldTransaction: OnyxEntry, + transactionChanges: TransactionChanges, + isFromExpenseReport: boolean, + policy: OnyxEntry, +): ExpenseOriginalMessage { const originalMessage: ExpenseOriginalMessage = {}; // Remark: Comment field is the only one which has new/old prefixes for the keys (newComment/ oldComment), // all others have old/- pattern such as oldCreated/created @@ -2480,8 +2486,9 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry, transactionChanges: TransactionChanges, isFromExpenseReport: boolean, + policy: OnyxEntry, ): OptimisticModifiedExpenseReportAction { - const originalMessage = getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, isFromExpenseReport); + const originalMessage = getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, isFromExpenseReport, policy); return { actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE, actorAccountID: currentUserAccountID, From f1e1f92fcbfbb695572a8f79b9190f9a37d770d4 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 6 Mar 2024 15:24:10 +0100 Subject: [PATCH 237/699] pass polciy props --- src/libs/actions/IOU.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 86b116974091..1161b1e61b65 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1200,7 +1200,7 @@ function getUpdateMoneyRequestParams( // We don't create a modified report action if we're updating the waypoints, // since there isn't actually any optimistic data we can create for them and the report action is created on the server // with the response from the MapBox API - const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport); + const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport, policy); if (!hasPendingWaypoints) { params.reportActionID = updatedReportAction.reportActionID; @@ -2664,7 +2664,7 @@ function editRegularMoneyRequest( const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); // STEP 2: Build new modified expense report action. - const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport); + const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport, policy); const updatedTransaction = transaction ? TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport) : null; // STEP 3: Compute the IOU total and update the report preview message so LHN amount owed is correct From 89358cb5d9bb3f3b86b06c32b052ebb18f4bd606 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 6 Mar 2024 15:40:10 +0100 Subject: [PATCH 238/699] fix lint --- src/libs/ReportUtils.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a74ef227deb7..2d8bc354c825 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -31,7 +31,6 @@ import type { Transaction, TransactionViolation, } from '@src/types/onyx'; -import type * as OnyxTypes from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; import type { @@ -2443,7 +2442,7 @@ function getModifiedExpenseOriginalMessage( oldTransaction: OnyxEntry, transactionChanges: TransactionChanges, isFromExpenseReport: boolean, - policy: OnyxEntry, + policy: OnyxEntry, ): ExpenseOriginalMessage { const originalMessage: ExpenseOriginalMessage = {}; // Remark: Comment field is the only one which has new/old prefixes for the keys (newComment/ oldComment), @@ -2486,9 +2485,8 @@ function getModifiedExpenseOriginalMessage( } if ('taxCode' in transactionChanges) { - const taxRates = policy?.taxRates?.taxes; - originalMessage.oldTaxRate = taxRates && taxRates[TransactionUtils.getTaxCode(oldTransaction)].value; - originalMessage.taxRate = taxRates && transactionChanges?.taxCode && taxRates[transactionChanges.taxCode].value; + originalMessage.oldTaxRate = policy?.taxRates?.taxes[TransactionUtils.getTaxCode(oldTransaction)].value; + originalMessage.taxRate = transactionChanges?.taxCode && policy?.taxRates?.taxes[transactionChanges?.taxCode].value; } if ('billable' in transactionChanges) { @@ -3332,7 +3330,7 @@ function buildOptimisticModifiedExpenseReportAction( oldTransaction: OnyxEntry, transactionChanges: TransactionChanges, isFromExpenseReport: boolean, - policy: OnyxEntry, + policy: OnyxEntry, ): OptimisticModifiedExpenseReportAction { const originalMessage = getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, isFromExpenseReport, policy); return { From a31e70aaaf4ac883f2181c21873823274e8e282c Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 6 Mar 2024 22:58:12 +0100 Subject: [PATCH 239/699] fix errors --- src/languages/es.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/languages/es.ts b/src/languages/es.ts index 54fa994ecb5f..a286314e6452 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -135,6 +135,7 @@ export default { twoFactorCode: 'Autenticación de dos factores', workspaces: 'Espacios de trabajo', chats: 'Chats', + group: 'Grupo', profile: 'Perfil', referral: 'Remisión', payments: 'Pagos', From 89040f142e2b0b7ca721a6cb1907cb92ecc1d06f Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 6 Mar 2024 23:31:17 +0100 Subject: [PATCH 240/699] fix lint --- src/libs/GroupChatUtils.ts | 2 +- src/libs/actions/Report.ts | 8 ++++---- src/pages/NewChatConfirmPage.tsx | 6 +++--- src/pages/NewChatPage.tsx | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libs/GroupChatUtils.ts b/src/libs/GroupChatUtils.ts index ab3ed521d7cd..69a15c73d525 100644 --- a/src/libs/GroupChatUtils.ts +++ b/src/libs/GroupChatUtils.ts @@ -1,7 +1,7 @@ import type {OnyxEntry} from 'react-native-onyx'; -import type {OptionData} from '@libs/ReportUtils'; import type {Report} from '@src/types/onyx'; import localeCompare from './LocaleCompare'; +import type {OptionData} from './ReportUtils'; import * as ReportUtils from './ReportUtils'; /** diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index f3cb23c72f85..d23388ee1688 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -250,6 +250,10 @@ Onyx.connect({ callback: (val) => (newGroupDraft = val), }); +function clearGroupChat() { + Onyx.set(ONYXKEYS.NEW_GROUP, null); +} + function startNewChat() { clearGroupChat(); Navigation.navigate(ROUTES.NEW); @@ -2974,10 +2978,6 @@ function setGroupDraft(invitedUsersIDs: number[], reportName: string = '') { Onyx.set(ONYXKEYS.NEW_GROUP, {selectedOptions: invitedUsersIDs, reportName}); } -function clearGroupChat() { - Onyx.set(ONYXKEYS.NEW_GROUP, null); -} - export { searchInServer, addComment, diff --git a/src/pages/NewChatConfirmPage.tsx b/src/pages/NewChatConfirmPage.tsx index 0f83689e47c9..2a07f16b9696 100644 --- a/src/pages/NewChatConfirmPage.tsx +++ b/src/pages/NewChatConfirmPage.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; @@ -42,7 +42,7 @@ function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmP const currentUserOptionData = members.currentUserOption; const options = [...members.personalDetails, currentUserOptionData] as OptionData[]; return options; - }, [newGroupDraft]); + }, [newGroupDraft, allPersonalDetails]); const groupName = GroupChatUtils.getGroupChatConfirmName(selectedOptions); @@ -73,7 +73,7 @@ function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmP } if (isOptionInList) { - const newSelectedAccountIDs = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login).map((option) => option.accountID) as number[]; + const newSelectedAccountIDs = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login).map((optionData) => optionData.accountID) as number[]; Report.setGroupDraft(newSelectedAccountIDs); } }; diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 4e017777181c..fc106f08642d 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -239,7 +239,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF const groupSelectedOptions = OptionsListUtils.getMemberInviteOptions(invitedUsersPersonalDetails).personalDetails; setSelectedOptions(groupSelectedOptions); } - }, [didScreenTransitionEnd, updateOptions]); + }, [didScreenTransitionEnd, updateOptions, newGroupDraft?.selectedOptions, personalDetails]); const {inputCallbackRef} = useAutoFocusInput(); From 24ae5ecf357b7e62a57b1c28fd2e5dc7d8b46161 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 6 Mar 2024 23:40:51 +0100 Subject: [PATCH 241/699] fix lint --- src/libs/GroupChatUtils.ts | 2 +- src/libs/actions/Report.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/GroupChatUtils.ts b/src/libs/GroupChatUtils.ts index 69a15c73d525..d694099989a6 100644 --- a/src/libs/GroupChatUtils.ts +++ b/src/libs/GroupChatUtils.ts @@ -11,7 +11,7 @@ function getGroupChatConfirmName(participants: OptionData[]): string | undefined const isMultipleParticipantReport = participants.length > 1; return participants - .map((participant) => ReportUtils.getDisplayNameForParticipant(participant.accountID!, isMultipleParticipantReport)) + .map((participant) => ReportUtils.getDisplayNameForParticipant(participant.accountID ?? undefined, isMultipleParticipantReport)) .sort((first, second) => localeCompare(first ?? '', second ?? '')) .filter(Boolean) .join(', '); diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index d23388ee1688..02f554308f89 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2974,7 +2974,7 @@ function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEnt API.write(WRITE_COMMANDS.RESOLVE_ACTIONABLE_MENTION_WHISPER, parameters, {optimisticData, failureData}); } -function setGroupDraft(invitedUsersIDs: number[], reportName: string = '') { +function setGroupDraft(invitedUsersIDs: number[], reportName = '') { Onyx.set(ONYXKEYS.NEW_GROUP, {selectedOptions: invitedUsersIDs, reportName}); } From c941d2388e132ad241836c2a73ed2761455a7364 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Wed, 6 Mar 2024 23:07:19 -0800 Subject: [PATCH 242/699] update getOneTransactionReportID to take in only reportActions modify ReportUtils methods to send across reportActions --- src/libs/ReportActionsUtils.ts | 9 ++++----- src/libs/ReportUtils.ts | 19 ++++++++----------- src/pages/home/ReportScreen.js | 2 +- src/pages/home/report/ReportActionItem.js | 21 +++++++++++---------- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 227a67d06c48..08a97728eefe 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -205,17 +205,16 @@ function isTransactionThread(parentReportAction: OnyxEntry): boole ); } -function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxCollection | null): string { - const moneyRequestReportActions = reportActions && !_.isEmpty(reportActions) ? reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] : allReportActions?.[reportID]; - const moneyRequestReportActionsArray = Object.values(moneyRequestReportActions ?? {}); +function getOneTransactionThreadReportID(reportActions: ReportActions): string { + const reportActionsArray = Object.values(reportActions ?? {}); - if (!moneyRequestReportActionsArray.length) { + if (!reportActionsArray.length) { return '0'; } // Get all IOU report actions for the report. const iouRequestTypes: Array> = [CONST.IOU.REPORT_ACTION_TYPE.CREATE, CONST.IOU.REPORT_ACTION_TYPE.SPLIT]; - const iouRequestActions = moneyRequestReportActionsArray.filter((action) => + const iouRequestActions = reportActionsArray.filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && (iouRequestTypes.includes(action.originalMessage.type) ?? []) && action.childReportID diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6e9554c728fd..393dfec86f18 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1213,19 +1213,17 @@ function isMoneyRequestReport(reportOrID: OnyxEntry | string): boolean { /** * Checks if a report has only one transaction associated with it */ -function isOneTransactionReport(report: OnyxEntry): boolean { - return ReportActionsUtils.getOneTransactionThreadReportID(report?.reportID ?? '0', null) !== '0' +function isOneTransactionReport(reportID: string): boolean { + const reportActions = ReportActionsUtils.getAllReportActions(reportID); + return ReportActionsUtils.getOneTransactionThreadReportID(reportActions) !== '0' } /** * Checks if a report is a transaction thread associated with a report that has only one transaction */ -function isOneTransactionThread(reportID: string, parentReport: OnyxEntry | EmptyObject): boolean { - if (isEmptyObject(parentReport)) { - return false; - } - - const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(parentReport?.reportID ?? '0', null); +function isOneTransactionThread(reportID: string, parentReportID: string): boolean { + const parentReportActions = ReportActionsUtils.getAllReportActions(parentReportID); + const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(parentReportActions); return reportID === transactionThreadReportID; } @@ -1656,7 +1654,7 @@ function getIcons( const isManager = currentUserAccountID === report?.managerID; // For one transaction IOUs, display a simplified report icon - if (isOneTransactionReport(report)) { + if (isOneTransactionReport(report?.reportID ?? '0')) { return [ownerIcon]; } @@ -4039,8 +4037,7 @@ function shouldReportBeInOptionList({ } // If this is a transaction thread associated with a report that only has one transaction, omit it - const parentReport = getParentReport(report); - if (isOneTransactionThread(report.reportID, parentReport)) { + if (isOneTransactionThread(report.reportID, report.parentReportID ?? '0')) { return false; } diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 527045e8546e..22858982544d 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -288,7 +288,7 @@ function ReportScreen({ const policy = policies[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`] || {}; const isTopMostReportId = currentReportID === getReportID(route); const didSubscribeToReportLeavingEvents = useRef(false); - const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(reportID, allReportActions); + const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]); const transactionThreadReportActions = useMemo(() => { if (transactionThreadReportID) { return null; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 00289a07f448..9d0b555bba33 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -111,9 +111,6 @@ const propTypes = { emojiReactions: EmojiReactionsPropTypes, - /** All reportActions shared with the user */ - reportActions: PropTypes.objectOf(reportActionPropTypes), - /** All reports shared with the user */ reports: PropTypes.objectOf(reportPropTypes), @@ -129,6 +126,9 @@ const propTypes = { /** All the report actions belonging to the report's parent */ parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + /** All the report actions belonging to the current report */ + reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + /** Callback to be called on onPress */ onPress: PropTypes.func, }; @@ -138,12 +138,12 @@ const defaultProps = { preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, emojiReactions: {}, shouldShowSubscriptAvatar: false, - reportActions: {}, reports: {}, iouReport: undefined, shouldHideThreadDividerLine: false, userWallet: {}, parentReportActions: {}, + reportActions: {}, onPress: undefined, }; @@ -166,7 +166,7 @@ function ReportActionItem(props) { const originalReportID = ReportUtils.getOriginalReportID(props.report.reportID, props.action); const originalReport = props.report.reportID === originalReportID ? props.report : ReportUtils.getReport(originalReportID); const isReportActionLinked = props.linkedReportActionID && props.action.reportActionID && props.linkedReportActionID === props.action.reportActionID; - const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(props.report.reportID, props.allReportActions); + const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(props.reportActions); const transactionThreadReport = useMemo(() => { if (transactionThreadReportID) { return null; @@ -888,9 +888,6 @@ export default compose( key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE, }, - reportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - }, reports: { key: ONYXKEYS.COLLECTION.REPORT, }, @@ -920,6 +917,10 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID || 0}`, canEvict: false, }, + reportActions: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID || 0}`, + canEvict: false, + }, }), )( memo(ReportActionItem, (prevProps, nextProps) => { @@ -930,7 +931,6 @@ export default compose( prevProps.draftMessage === nextProps.draftMessage && prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && - _.isEqual(prevProps.reportActions, nextProps.reportActions) && _.isEqual(prevProps.reports, nextProps.reports) && _.isEqual(prevProps.emojiReactions, nextProps.emojiReactions) && _.isEqual(prevProps.action, nextProps.action) && @@ -957,7 +957,8 @@ export default compose( _.isEqual(prevProps.policyReportFields, nextProps.policyReportFields) && _.isEqual(prevProps.report.reportFields, nextProps.report.reportFields) && _.isEqual(prevProps.policy, nextProps.policy) && - _.isEqual(prevParentReportAction, nextParentReportAction) + _.isEqual(prevParentReportAction, nextParentReportAction) && + _.isEqual(prevProps.reportActions, nextProps.reportActions) ); }), ); From 2a02be85220ff8dd7449bd9b42ebce20d9506390 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Thu, 7 Mar 2024 00:39:23 -0800 Subject: [PATCH 243/699] fix some malformed logic minor style and linting updates --- src/pages/home/ReportScreen.js | 13 ++++++------- src/pages/home/report/ReportActionItem.js | 6 +++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 22858982544d..d0109bb8dc08 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -69,10 +69,7 @@ const propTypes = { allReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), /** All the report actions for this report */ - reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), - - /** The report actions for the first transaction thread associated with the report */ - transactionThreadReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)), /** The report's parentReportAction */ parentReportAction: PropTypes.shape(reportActionPropTypes), @@ -290,10 +287,12 @@ function ReportScreen({ const didSubscribeToReportLeavingEvents = useRef(false); const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]); const transactionThreadReportActions = useMemo(() => { - if (transactionThreadReportID) { - return null; + if (transactionThreadReportID === '0') { + return []; } - return ReportActionsUtils.getSortedReportActionsForDisplay(Object.values(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportReportID}`])); + + const reportActions = Object.values(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`] ?? {}); + return ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); }, [allReportActions, transactionThreadReportID]); useEffect(() => { diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 9d0b555bba33..ee728117dcd6 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -168,10 +168,10 @@ function ReportActionItem(props) { const isReportActionLinked = props.linkedReportActionID && props.action.reportActionID && props.linkedReportActionID === props.action.reportActionID; const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(props.reportActions); const transactionThreadReport = useMemo(() => { - if (transactionThreadReportID) { - return null; + if (transactionThreadReportID === '0') { + return {}; } - return props.reports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportReportID}`]; + return props.reports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? {}; }, [props.reports, transactionThreadReportID]); const reportScrollManager = useReportScrollManager(); From b5ae144058eef38eccdedfc7f9858f336830b3d4 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Thu, 7 Mar 2024 01:06:01 -0800 Subject: [PATCH 244/699] ensure we don't show outdated UI due to removed IOU requests --- src/libs/ReportActionsUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 08a97728eefe..628d88fdc76f 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -218,6 +218,7 @@ function getOneTransactionThreadReportID(reportActions: ReportActions): string { action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && (iouRequestTypes.includes(action.originalMessage.type) ?? []) && action.childReportID + && action.originalMessage.IOUTransactionID ); // If we don't have any IOU request actions, or we have more than one IOU request actions, this isn't a oneTransaction report and we don't want to return the transactionThreadReportActions From 9e55810c4787ff1afd70655a835cf62feb7f1f34 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 7 Mar 2024 18:19:12 +0700 Subject: [PATCH 245/699] resolve feedbacks --- .storybook/webpack.config.js | 6 ------ src/components/LHNOptionsList/LHNOptionsList.tsx | 10 +++++----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 89f7699465bf..204f70344b18 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -37,12 +37,6 @@ module.exports = ({config}) => { config.plugins[definePluginIndex].definitions.__REACT_WEB_CONFIG__ = JSON.stringify(env); config.resolve.extensions = custom.resolve.extensions; - config.stats = { - // We can ignore the "module not installed" warning from lottie-react-native - // because we are not using the library for JSON format of Lottie animations. - warningsFilter: ['../node_modules/lottie-react-native/lib/module/LottieView/index.web.js'], - }; - const babelRulesIndex = _.findIndex(custom.module.rules, (rule) => rule.loader === 'babel-loader'); const babelRule = custom.module.rules[babelRulesIndex]; config.module.rules.push(babelRule); diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 17dc4f4bfb6a..df8b1e23f0d4 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -47,7 +47,7 @@ function LHNOptionsList({ const {canUseViolations} = usePermissions(); const {translate} = useLocalize(); const {isExtraSmallScreenHeight, isSmallScreenWidth} = useWindowDimensions(); - const shouldShowEmptyUI = isSmallScreenWidth && data.length === 0; + const shouldShowEmptyLHN = isSmallScreenWidth && data.length === 0; // When the first item renders we want to call the onFirstItemRendered callback. // At this point in time we know that the list is actually displaying items. @@ -61,7 +61,7 @@ function LHNOptionsList({ onFirstItemRendered(); }, [onFirstItemRendered]); - const renderEmptyStateSubtitle = useCallback( + const renderEmptyLHNSubtitle = useCallback( () => ( - {shouldShowEmptyUI ? ( + + {shouldShowEmptyLHN ? ( ) : ( Date: Thu, 7 Mar 2024 14:47:59 +0100 Subject: [PATCH 246/699] start migrating CustomVersionFilePlugin to TypeScript --- ...ustomVersionFilePlugin.js => CustomVersionFilePlugin.ts} | 6 ++++-- config/webpack/webpack.dev.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) rename config/webpack/{CustomVersionFilePlugin.js => CustomVersionFilePlugin.ts} (91%) diff --git a/config/webpack/CustomVersionFilePlugin.js b/config/webpack/CustomVersionFilePlugin.ts similarity index 91% rename from config/webpack/CustomVersionFilePlugin.js rename to config/webpack/CustomVersionFilePlugin.ts index ed7c0f3dca95..4003429341c6 100644 --- a/config/webpack/CustomVersionFilePlugin.js +++ b/config/webpack/CustomVersionFilePlugin.ts @@ -1,3 +1,5 @@ +import type {Compiler} from 'webpack'; + const fs = require('fs'); const path = require('path'); const APP_VERSION = require('../../package.json').version; @@ -6,7 +8,7 @@ const APP_VERSION = require('../../package.json').version; * Simple webpack plugin that writes the app version (from package.json) and the webpack hash to './version.json' */ class CustomVersionFilePlugin { - apply(compiler) { + apply(compiler: Compiler) { compiler.hooks.done.tap( this.constructor.name, () => @@ -30,4 +32,4 @@ class CustomVersionFilePlugin { } } -module.exports = CustomVersionFilePlugin; +export default CustomVersionFilePlugin; diff --git a/config/webpack/webpack.dev.ts b/config/webpack/webpack.dev.ts index 56dc735edd86..654ee20f2a6e 100644 --- a/config/webpack/webpack.dev.ts +++ b/config/webpack/webpack.dev.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ import type webpack from 'webpack'; +import getCommonConfig from './webpack.common'; import portfinder = require('portfinder'); import TimeAnalyticsWebpackPlugin = require('time-analytics-webpack-plugin'); -import getCommonConfig from './webpack.common'; const {TimeAnalyticsPlugin} = TimeAnalyticsWebpackPlugin; const path = require('path'); From 14b7ef07bbf7ce99348facf9fef731f8abe6fee9 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 7 Mar 2024 15:24:44 +0100 Subject: [PATCH 247/699] avoid redundant API call if same tax rate is selected --- src/pages/EditRequestPage.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index b428e3eaf820..d0436b32c817 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -137,6 +137,12 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p const updateTaxRate = useCallback( (transactionChanges) => { const newTaxCode = transactionChanges.data.code; + + if (!newTaxCode) { + Navigation.dismissModal(); + return; + } + IOU.updateMoneyRequestTaxRate(transaction.transactionID, report.reportID, newTaxCode, policy, policyTags, policyCategories); Navigation.dismissModal(report.reportID); }, From f1179ce504f4b27a74b1036a308da93dcc8ce02e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 7 Mar 2024 15:27:37 +0100 Subject: [PATCH 248/699] avoid redundant API call if same tax amount inputed is selected --- src/pages/EditRequestPage.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index d0436b32c817..c01b65932beb 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -128,6 +128,11 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p const updateTaxAmount = useCallback( (transactionChanges) => { const newTaxAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(transactionChanges.amount)); + + if (newTaxAmount === TransactionUtils.getTaxAmount(transaction)) { + Navigation.dismissModal(); + return; + } IOU.updateMoneyRequestTaxAmount(transaction.transactionID, report.reportID, newTaxAmount, policy, policyTags, policyCategories); Navigation.dismissModal(report.reportID); }, From 40583469e19a41a331e5d2b94f0c7e7979ce09dc Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 7 Mar 2024 16:02:35 +0100 Subject: [PATCH 249/699] do not update taxAmount more than default taxAmount based on actual expense amount --- src/pages/EditRequestPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index c01b65932beb..cfde040cbdf5 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -77,7 +77,7 @@ const defaultProps = { const getTaxAmount = (transactionAmount, transactionTaxCode, taxRates) => { const percentage = (transactionTaxCode ? taxRates.taxes[transactionTaxCode].value : taxRates.defaultValue) || ''; - return CurrencyUtils.convertToBackendAmount(Number.parseFloat(TransactionUtils.calculateTaxAmount(percentage, transactionAmount))); + return CurrencyUtils.convertToBackendAmount(Number.parseFloat(TransactionUtils.calculateTaxAmount(percentage, Math.abs(transactionAmount)))); }; function EditRequestPage({report, route, policy, policyCategories, policyTags, parentReportActions, transaction}) { @@ -221,7 +221,7 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p return ( From 5021d915b18ed5307619cd39d63d7ac41a52833a Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 7 Mar 2024 17:07:23 +0100 Subject: [PATCH 250/699] remove optional chaining --- src/pages/EditRequestPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index cfde040cbdf5..0498c98b087a 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -221,7 +221,7 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p return ( From 397278d3665de12a40768a1ee4e789ac1ee8c56b Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Thu, 7 Mar 2024 21:54:37 +0530 Subject: [PATCH 251/699] Prettier formatting --- src/pages/settings/Profile/PersonalDetails/AddressPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx index 9d9d16a6a388..77d551b283ec 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx @@ -13,8 +13,8 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as PersonalDetails from '@userActions/PersonalDetails'; -import CONST from '@src/CONST'; import type {FormOnyxValues} from '@src/components/Form/types'; +import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; From 1d690097fba01052520760254e64cb7ed21b16c9 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Thu, 7 Mar 2024 21:36:12 +0100 Subject: [PATCH 252/699] split changes, Join remove --- src/libs/ReportUtils.ts | 90 ++++++++++++++++++++++++++++++++ src/libs/actions/IOU.ts | 2 +- src/libs/actions/Report.ts | 19 +++++-- src/pages/NewChatConfirmPage.tsx | 3 +- src/pages/home/HeaderView.js | 8 ++- src/types/onyx/Report.ts | 2 +- 6 files changed, 114 insertions(+), 10 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7f7ba31bd583..105402fcf59c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -20,6 +20,7 @@ import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type { Beta, + Participants, PersonalDetails, PersonalDetailsList, Policy, @@ -257,6 +258,39 @@ type OptimisticChatReport = Pick< isOptimisticReport: true; }; +type OptimisticGroupChatReport = Pick< + Report, + | 'type' + | 'chatType' + | 'chatReportID' + | 'iouReportID' + | 'isOwnPolicyExpenseChat' + | 'isPinned' + | 'lastActorAccountID' + | 'lastMessageTranslationKey' + | 'lastMessageHtml' + | 'lastMessageText' + | 'lastReadTime' + | 'lastVisibleActionCreated' + | 'notificationPreference' + | 'oldPolicyName' + | 'ownerAccountID' + | 'pendingFields' + | 'parentReportActionID' + | 'parentReportID' + | 'participants' + | 'policyID' + | 'reportID' + | 'reportName' + | 'stateNum' + | 'statusNum' + | 'visibility' + | 'description' + | 'writeCapability' +> & { + isOptimisticReport: true; +}; + type OptimisticTaskReportAction = Pick< ReportAction, | 'reportActionID' @@ -915,6 +949,10 @@ function isSelfDM(report: OnyxEntry): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.SELF_DM; } +function isGroupChatType(report: OnyxEntry): boolean { + return getChatType(report) === CONST.REPORT.CHAT_TYPE.GROUP_CHAT; +} + /** * Only returns true if this is our main 1:1 DM report with Concierge */ @@ -3520,6 +3558,56 @@ function buildOptimisticChatReport( }; } +/** + * Builds an optimistic group chat report with a randomly generated reportID and as much information as we currently have + */ +function buildOptimisticGroupChatReport( + participantList: Participants, + reportName: string = CONST.REPORT.DEFAULT_REPORT_NAME, + chatType: ValueOf | undefined = undefined, + policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, + ownerAccountID: number = CONST.REPORT.OWNER_ACCOUNT_ID_FAKE, + isOwnPolicyExpenseChat = false, + oldPolicyName = '', + visibility: ValueOf | undefined = undefined, + writeCapability: ValueOf | undefined = undefined, + notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + parentReportActionID = '', + parentReportID = '', + description = '', +): OptimisticGroupChatReport { + const currentTime = DateUtils.getDBTime(); + const isNewlyCreatedWorkspaceChat = chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT && isOwnPolicyExpenseChat; + return { + isOptimisticReport: true, + type: CONST.REPORT.TYPE.CHAT, + chatType, + isOwnPolicyExpenseChat, + isPinned: reportName === CONST.REPORT.WORKSPACE_CHAT_ROOMS.ADMINS || isNewlyCreatedWorkspaceChat, + lastActorAccountID: 0, + lastMessageTranslationKey: '', + lastMessageHtml: '', + lastMessageText: undefined, + lastReadTime: currentTime, + lastVisibleActionCreated: currentTime, + notificationPreference, + oldPolicyName, + ownerAccountID: ownerAccountID || CONST.REPORT.OWNER_ACCOUNT_ID_FAKE, + parentReportActionID, + parentReportID, + // For group chats we need to have participants object + participants: participantList, + policyID, + reportID: generateReportID(), + reportName, + stateNum: 0, + statusNum: 0, + visibility, + description, + writeCapability, + }; +} + function getCurrentUserAvatarOrDefault(): UserUtils.AvatarSource { return allPersonalDetails?.[currentUserAccountID ?? '']?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID); } @@ -5189,6 +5277,7 @@ export { buildOptimisticWorkspaceChats, buildOptimisticTaskReport, buildOptimisticChatReport, + buildOptimisticGroupChatReport, buildOptimisticClosedReportAction, buildOptimisticCreatedReportAction, buildOptimisticRenamedRoomReportAction, @@ -5327,6 +5416,7 @@ export { getDefaultGroupAvatar, canAddOrDeleteTransactions, shouldCreateNewMoneyRequestReport, + isGroupChatType, }; export type { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index cb3aa20ab6a7..49ccc30a5439 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1655,7 +1655,7 @@ function createSplitsAndOnyxData( const currentUserEmailForIOUSplit = PhoneNumber.addSMSDomainIfPhoneNumber(currentUserLogin); const participantAccountIDs = participants.map((participant) => Number(participant.accountID)); const existingSplitChatReport = - existingSplitChatReportID || participants[0].reportID + participants.length > 1 && (existingSplitChatReportID || participants[0].reportID) ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${existingSplitChatReportID || participants[0].reportID}`] : ReportUtils.getChatByParticipants(participantAccountIDs); const splitChatReport = existingSplitChatReport ?? ReportUtils.buildOptimisticChatReport(participantAccountIDs); diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 02f554308f89..9ebb1b12ab58 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -81,7 +81,7 @@ import type { ReportUserIsTyping, } from '@src/types/onyx'; import type {Decision, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; -import type {NotificationPreference, RoomVisibility, WriteCapability} from '@src/types/onyx/Report'; +import type {NotificationPreference, Participant, Participants, RoomVisibility, WriteCapability} from '@src/types/onyx/Report'; import type Report from '@src/types/onyx/Report'; import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; @@ -803,13 +803,24 @@ function openReport( */ function navigateToAndOpenReport(userLogins: string[], shouldDismissModal = true, reportName?: string) { let newChat: ReportUtils.OptimisticChatReport | EmptyObject = {}; - + let chat: ReportUtils.OptimisticChatReport | EmptyObject = {}; const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins(userLogins); - const chat = ReportUtils.getChatByParticipants(participantAccountIDs); + if (!newGroupDraft) { + chat = ReportUtils.getChatByParticipants(participantAccountIDs); + } if (!chat) { if (newGroupDraft) { - newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs, reportName, CONST.REPORT.CHAT_TYPE.GROUP_CHAT); + const participants: Participants = participantAccountIDs.reduce((obj: Participants, accountID: number) => { + const participant: Participant = { + hidden: false, + role: accountID === currentUserAccountID ? 'admin' : 'member', + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return {...obj, [accountID]: participant}; + }, {} as Participants); + + newChat = ReportUtils.buildOptimisticGroupChatReport(participants, reportName, CONST.REPORT.CHAT_TYPE.GROUP_CHAT); } else { newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs); } diff --git a/src/pages/NewChatConfirmPage.tsx b/src/pages/NewChatConfirmPage.tsx index 2a07f16b9696..fb90c96af228 100644 --- a/src/pages/NewChatConfirmPage.tsx +++ b/src/pages/NewChatConfirmPage.tsx @@ -79,8 +79,7 @@ function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmP }; const createGroup = () => { - const optionsWithoutCreator = selectedOptions.filter((selectedOption: OptionData) => selectedOption.accountID !== personalData.accountID); - const logins = optionsWithoutCreator.map((option: OptionData) => option.login) as string[]; + const logins = selectedOptions.map((option: OptionData) => option.login) as string[]; if (logins.length < 1) { return; } diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 10d2d1414c3a..36296b329d2c 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -94,6 +94,7 @@ function HeaderView(props) { const theme = useTheme(); const styles = useThemeStyles(); const isSelfDM = ReportUtils.isSelfDM(props.report); + const isGroupChat = ReportUtils.isGroupChat(props.report) || ReportUtils.isGroupChatType(props.report); // Currently, currentUser is not included in participantAccountIDs, so for selfDM, we need to add the currentUser as participants. const participants = isSelfDM ? [props.session.accountID] : lodashGet(props.report, 'participantAccountIDs', []); const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails); @@ -106,7 +107,7 @@ function HeaderView(props) { const isTaskReport = ReportUtils.isTaskReport(props.report); const reportHeaderData = !isTaskReport && !isChatThread && props.report.parentReportID ? props.parentReport : props.report; // Use sorted display names for the title for group chats on native small screen widths - const title = ReportUtils.isGroupChat(props.report) ? getGroupChatName(props.report) : ReportUtils.getReportName(reportHeaderData); + const title = isGroupChat ? getGroupChatName(props.report) : ReportUtils.getReportName(reportHeaderData); const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(reportHeaderData); const isConcierge = ReportUtils.hasSingleParticipant(props.report) && _.contains(participants, CONST.ACCOUNT_ID.CONCIERGE); @@ -169,7 +170,10 @@ function HeaderView(props) { ), ); - const canJoinOrLeave = !isSelfDM && (isChatThread || isUserCreatedPolicyRoom || canLeaveRoom); + const canJoinOrLeave = !isSelfDM && !isGroupChat && (isChatThread || isUserCreatedPolicyRoom || canLeaveRoom); + console.log('canJoinOrLeave :>> ', canJoinOrLeave); + console.log('isGroupChat :>> ', isGroupChat); + console.log('props.report :>> ', props.report); const canJoin = canJoinOrLeave && !isWhisperAction && props.report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const canLeave = canJoinOrLeave && ((isChatThread && props.report.notificationPreference.length) || isUserCreatedPolicyRoom || canLeaveRoom); if (canJoin) { diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 22618bb357d0..f12bd0a7e38f 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -180,4 +180,4 @@ type ReportCollectionDataSet = CollectionDataSet Date: Thu, 7 Mar 2024 21:56:50 +0100 Subject: [PATCH 253/699] fix lint, ta --- src/pages/home/HeaderView.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 36296b329d2c..ffe1238212d5 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -171,9 +171,6 @@ function HeaderView(props) { ); const canJoinOrLeave = !isSelfDM && !isGroupChat && (isChatThread || isUserCreatedPolicyRoom || canLeaveRoom); - console.log('canJoinOrLeave :>> ', canJoinOrLeave); - console.log('isGroupChat :>> ', isGroupChat); - console.log('props.report :>> ', props.report); const canJoin = canJoinOrLeave && !isWhisperAction && props.report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const canLeave = canJoinOrLeave && ((isChatThread && props.report.notificationPreference.length) || isUserCreatedPolicyRoom || canLeaveRoom); if (canJoin) { From 0928709f023fc8f58cb3ae1dfa937b2f3b01d476 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Thu, 7 Mar 2024 22:00:40 +0100 Subject: [PATCH 254/699] ts fix --- src/libs/actions/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 9ebb1b12ab58..368ef9b75b19 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -803,7 +803,7 @@ function openReport( */ function navigateToAndOpenReport(userLogins: string[], shouldDismissModal = true, reportName?: string) { let newChat: ReportUtils.OptimisticChatReport | EmptyObject = {}; - let chat: ReportUtils.OptimisticChatReport | EmptyObject = {}; + let chat: OnyxEntry | EmptyObject = {}; const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins(userLogins); if (!newGroupDraft) { From f6cfcb399ee646c46a3b9ae82ba7b781c91bed33 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Thu, 7 Mar 2024 13:19:08 -0800 Subject: [PATCH 255/699] use transaction currency instead of checking between transactionThreadReport and report --- src/pages/home/ReportScreen.js | 2 +- src/pages/home/report/ReportActionItem.js | 29 +++++++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index d0109bb8dc08..d6cb010373f9 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -291,7 +291,7 @@ function ReportScreen({ return []; } - const reportActions = Object.values(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`] ?? {}); + const reportActions = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`] ?? []; return ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); }, [allReportActions, transactionThreadReportID]); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index ee728117dcd6..fb4a1f52b51a 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -62,7 +62,6 @@ import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; @@ -78,6 +77,7 @@ import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import reportActionPropTypes from './reportActionPropTypes'; import ReportAttachmentsContext from './ReportAttachmentsContext'; +import transactionPropTypes from '@components/transactionPropTypes'; const propTypes = { ...windowDimensionsPropTypes, @@ -129,6 +129,9 @@ const propTypes = { /** All the report actions belonging to the current report */ reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + /** All the transactions shared wit hthe user */ + transactions: PropTypes.objectOf(PropTypes.shape(transactionPropTypes)), + /** Callback to be called on onPress */ onPress: PropTypes.func, }; @@ -144,6 +147,7 @@ const defaultProps = { userWallet: {}, parentReportActions: {}, reportActions: {}, + transactions: {}, onPress: undefined, }; @@ -167,13 +171,20 @@ function ReportActionItem(props) { const originalReport = props.report.reportID === originalReportID ? props.report : ReportUtils.getReport(originalReportID); const isReportActionLinked = props.linkedReportActionID && props.action.reportActionID && props.linkedReportActionID === props.action.reportActionID; const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(props.reportActions); + let transaction = {}; const transactionThreadReport = useMemo(() => { if (transactionThreadReportID === '0') { return {}; } - return props.reports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? {}; - }, [props.reports, transactionThreadReportID]); + const report = props.reports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? {}; + + // Get the transaction associated with the report + const transactionID = props.reportActions?.[report.parentReportActionID ?? '']?.originalMessage?.IOUTransactionID ?? 0; + transaction = props.transactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + return report; + }, [props.reports, transactionThreadReportID, props.reportActions, props.transactions]); + const transactionCurrency = !_.isEmpty(transaction) ? (transaction.modifiedCurrency ?? transaction.currency) : props.report.currency; const reportScrollManager = useReportScrollManager(); const highlightedBackgroundColorIfNeeded = useMemo( @@ -707,9 +718,9 @@ function ReportActionItem(props) { if (ReportUtils.isExpenseReport(props.report) || ReportUtils.isIOUReport(props.report)) { return ( - {transactionThreadReport && !isEmptyObject(transactionThreadReport) ? ( + {transactionThreadReport && !_.isEmpty(transactionThreadReport) ? ( <> - {transactionThreadReport.currency !== props.report.currency && ( + {transactionCurrency !== props.report.currency && ( @@ -921,6 +932,9 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID || 0}`, canEvict: false, }, + transactions: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + }, }), )( memo(ReportActionItem, (prevProps, nextProps) => { @@ -958,7 +972,8 @@ export default compose( _.isEqual(prevProps.report.reportFields, nextProps.report.reportFields) && _.isEqual(prevProps.policy, nextProps.policy) && _.isEqual(prevParentReportAction, nextParentReportAction) && - _.isEqual(prevProps.reportActions, nextProps.reportActions) + _.isEqual(prevProps.reportActions, nextProps.reportActions) && + _.isEqual(prevProps.transactions, nextProps.transactions) ); }), ); From 481a49180550c1e2057433f781a02d3b301f5a4c Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Thu, 7 Mar 2024 23:06:29 +0100 Subject: [PATCH 256/699] updated to SelectionList --- src/pages/NewChatConfirmPage.tsx | 71 ++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/src/pages/NewChatConfirmPage.tsx b/src/pages/NewChatConfirmPage.tsx index fb90c96af228..7b09152e7815 100644 --- a/src/pages/NewChatConfirmPage.tsx +++ b/src/pages/NewChatConfirmPage.tsx @@ -3,12 +3,16 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import Avatar from '@components/Avatar'; +import Badge from '@components/Badge'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import OptionsSelector from '@components/OptionsSelector'; import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import TableListItem from '@components/SelectionList/TableListItem'; +import type {ListItem} from '@components/SelectionList/types'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import * as GroupChatUtils from '@libs/GroupChatUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -33,6 +37,7 @@ type NewChatConfirmPageProps = NewChatConfirmPageOnyxProps; function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmPageProps) { const {translate} = useLocalize(); + const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); const personalData = useCurrentUserPersonalDetails() || CONST.EMPTY_OBJECT; @@ -46,34 +51,50 @@ function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmP const groupName = GroupChatUtils.getGroupChatConfirmName(selectedOptions); - const sections = useMemo(() => { - const sectionsList = []; - if (selectedOptions) { - sectionsList.push({ - title: translate('common.members'), - data: selectedOptions, - shouldShow: true, - indexOffset: 0, - }); - } - return sectionsList; - }, [translate, selectedOptions]); - + const sections = useMemo( + () => + selectedOptions + .map((selectedOption) => { + const accountID = selectedOption.accountID; + let roleBadge = null; + const isAdmin = personalData.accountID === selectedOption.accountID; + if (isAdmin) { + roleBadge = ( + + ); + } + return { + value: selectedOption?.text ?? '', + text: selectedOption?.text ?? '', + keyForList: selectedOption?.keyForList ?? '', + isSelected: !isAdmin, + rightElement: roleBadge, + accountID, + icons: selectedOption?.icons, + }; + }) + .sort((a, b) => a.text.toLowerCase().localeCompare(b.text.toLowerCase())), + [selectedOptions, personalData.accountID, translate, styles.textStrong, styles.justifyContentCenter, styles.badgeBordered, styles.activeItemBadge, StyleUtils], + ); /** * Removes a selected option from list if already selected. */ - const unselectOption = (option: OptionData) => { + const unselectOption = (option: ListItem) => { if (!selectedOptions) { return; } - const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.login === option.login); + const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.accountID === option.accountID); if (isOptionInList && personalData && option.accountID === personalData.accountID) { return; } if (isOptionInList) { - const newSelectedAccountIDs = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login).map((optionData) => optionData.accountID) as number[]; + const newSelectedAccountIDs = selectedOptions.filter((selectedOption) => selectedOption.accountID !== option.accountID).map((optionData) => optionData.accountID) as number[]; Report.setGroupDraft(newSelectedAccountIDs); } }; @@ -114,17 +135,13 @@ function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmP description={translate('groupConfirmPage.groupName')} /> - From 0140912da9f85a1eb17e7942a517c048c64b3c0f Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Thu, 7 Mar 2024 22:58:34 -0800 Subject: [PATCH 257/699] merge main and address conflicts from ts migrations for ReportActionItem --- .eslintrc.js | 3 +- .../javascript/authorChecklist/index.js | 16 +- .../javascript/awaitStagingDeploys/index.js | 16 +- .../javascript/checkDeployBlockers/index.js | 16 +- .../createOrUpdateStagingDeploy.js | 11 +- .../createOrUpdateStagingDeploy/index.js | 27 +- .../javascript/getArtifactInfo/index.js | 16 +- .../getDeployPullRequestList/index.js | 16 +- .../javascript/getPullRequestDetails/index.js | 16 +- .../javascript/getReleaseBody/index.js | 16 +- .../javascript/isStagingDeployLocked/index.js | 16 +- .../markPullRequestsAsDeployed/index.js | 16 +- .../javascript/postTestBuildComment/index.js | 16 +- .../reopenIssueWithComment/index.js | 16 +- .../javascript/reviewerChecklist/index.js | 16 +- .../javascript/verifySignedCommits/index.js | 16 +- .github/libs/GithubUtils.js | 16 +- android/app/build.gradle | 4 +- .../Change-or-add-email-address.md | 24 + docs/redirects.csv | 9 +- ios/NewExpensify/Info.plist | 4 +- ios/NewExpensifyTests/Info.plist | 4 +- ios/NotificationServiceExtension/Info.plist | 4 +- package-lock.json | 4 +- package.json | 2 +- src/CONST.ts | 674 ++++++++++++++ src/ONYXKEYS.ts | 13 +- src/ROUTES.ts | 17 + src/SCREENS.ts | 4 + src/components/AddressSearch/index.tsx | 3 +- .../AttachmentCarousel/CarouselItem.js | 1 - .../Attachments/AttachmentView/index.js | 3 +- src/components/Badge.tsx | 34 +- src/components/Button/index.tsx | 2 +- src/components/DistanceEReceipt.tsx | 3 +- src/components/DistanceRequest/index.tsx | 1 + src/components/DraggableList/index.tsx | 6 +- src/components/FloatingActionButton.tsx | 11 +- src/components/Form/FormWrapper.tsx | 8 +- src/components/FormScrollView.tsx | 7 +- src/components/HeaderPageLayout.tsx | 3 +- src/components/KYCWall/BaseKYCWall.tsx | 12 +- src/components/MoneyReportHeader.tsx | 2 +- .../MoneyRequestConfirmationList.js | 8 +- src/components/MoneyRequestHeader.tsx | 16 +- ...oraryForRefactorRequestConfirmationList.js | 34 +- src/components/OnyxProvider.tsx | 6 +- .../OptionsSelector/BaseOptionsSelector.js | 3 +- src/components/PDFView/PDFPasswordForm.js | 3 +- src/components/Picker/BasePicker.tsx | 1 + src/components/Popover/types.ts | 5 +- src/components/PopoverProvider/index.tsx | 7 +- src/components/PopoverProvider/types.ts | 11 +- src/components/PopoverWithoutOverlay/types.ts | 5 +- .../Pressable/GenericPressable/types.ts | 5 +- .../types.ts | 2 +- .../ReportActionItemEmojiReactions.tsx | 4 +- .../ReportActionItem/MoneyReportView.tsx | 175 ++-- .../ReportActionItem/MoneyRequestView.tsx | 5 +- .../ReportActionItem/TaskAction.tsx | 10 +- src/components/ScrollView.tsx | 28 + src/components/ScrollViewWithContext.tsx | 11 +- src/components/VideoPlayer/BaseVideoPlayer.js | 8 +- src/components/createOnyxContext.tsx | 8 +- src/languages/en.ts | 13 + src/languages/es.ts | 13 + src/libs/API/parameters/AcceptJoinRequest.ts | 5 + .../CreateWorkspaceCategoriesParams.ts | 10 + src/libs/API/parameters/DeclineJoinRequest.ts | 5 + .../API/parameters/JoinPolicyInviteLink.ts | 6 + .../API/parameters/PayMoneyRequestParams.ts | 1 + src/libs/API/parameters/index.ts | 4 + src/libs/API/types.ts | 10 + src/libs/DistanceRequestUtils.ts | 29 +- src/libs/EmojiUtils.ts | 16 +- src/libs/ErrorUtils.ts | 2 + .../Navigation/AppNavigator/AuthScreens.tsx | 9 + .../AppNavigator/ModalStackNavigators.tsx | 3 + .../BottomTabBar.tsx | 4 +- src/libs/Navigation/NavigationRoot.tsx | 2 +- .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 4 +- src/libs/Navigation/linkingConfig/config.ts | 10 + src/libs/Navigation/types.ts | 18 + .../PushNotification/NotificationType.ts | 2 + ...bscribeToReportCommentPushNotifications.ts | 26 +- .../reportWithoutHasDraftSelector.ts | 5 +- src/libs/OptionsListUtils.ts | 21 +- src/libs/Permissions.ts | 5 + src/libs/PolicyUtils.ts | 14 +- src/libs/Pusher/pusher.ts | 8 +- src/libs/PusherUtils.ts | 8 +- src/libs/ReportActionsUtils.ts | 43 +- src/libs/ReportUtils.ts | 219 +++-- src/libs/SidebarUtils.ts | 8 +- src/libs/TaskUtils.ts | 18 +- src/libs/actions/IOU.ts | 60 +- src/libs/actions/OnyxUpdateManager.ts | 3 +- src/libs/actions/OnyxUpdates.ts | 17 +- src/libs/actions/Policy.ts | 214 ++++- src/libs/actions/Report.ts | 30 +- src/libs/actions/Task.ts | 24 +- src/libs/actions/User.ts | 26 +- src/libs/calculateAnchorPosition.ts | 6 +- .../index.android.ts | 2 +- .../focusTextInputAfterAnimation/index.ts | 2 +- .../focusTextInputAfterAnimation/types.ts | 2 +- src/libs/getClickedTargetLocation/types.ts | 2 +- src/libs/isReportMessageAttachment.ts | 10 +- .../KeyReportActionsDraftByReportActionID.ts | 1 + .../navigateAfterJoinRequest/index.desktop.ts | 8 + src/libs/navigateAfterJoinRequest/index.ts | 8 + .../navigateAfterJoinRequest/index.web.ts | 8 + src/pages/DetailsPage.tsx | 3 +- src/pages/EnablePayments/TermsStep.js | 2 +- src/pages/FlagCommentPage.tsx | 3 +- src/pages/GetAssistancePage.tsx | 3 +- src/pages/KeyboardShortcutsPage.tsx | 3 +- .../ManageTeamsExpensesPage.tsx | 3 +- .../PurposeForUsingExpensifyPage.tsx | 3 +- .../PrivateNotes/PrivateNotesListPage.tsx | 2 +- src/pages/ProfilePage.js | 3 +- .../ReimbursementAccount/BankAccountStep.js | 3 +- .../BankInfo/substeps/Confirmation.tsx | 3 +- .../ConfirmationUBO.tsx | 3 +- .../substeps/CompanyOwnersListUBO.tsx | 3 +- .../substeps/ConfirmationBusiness.tsx | 2 +- .../components/FinishChatCard.tsx | 2 +- .../ContinueBankAccountSetup.js | 2 +- .../EnableBankAccount/EnableBankAccount.tsx | 2 +- .../PersonalInfo/substeps/Confirmation.tsx | 3 +- .../RequestorOnfidoStep.js | 2 +- .../VerifyIdentity/VerifyIdentity.tsx | 3 +- src/pages/ReportDetailsPage.tsx | 3 +- src/pages/ShareCodePage.tsx | 3 +- src/pages/home/ReportScreenContext.ts | 5 +- .../BaseReportActionContextMenu.tsx | 4 +- .../report/ContextMenu/ContextMenuActions.tsx | 21 +- .../MiniReportActionContextMenu/types.ts | 2 +- .../PopoverReportActionContextMenu.tsx | 8 +- .../ContextMenu/ReportActionContextMenu.ts | 2 +- ...portActionItem.js => ReportActionItem.tsx} | 839 +++++++++--------- .../report/ReportActionItemBasicMessage.tsx | 2 +- .../home/report/ReportActionItemCreated.tsx | 2 +- .../home/report/ReportActionItemFragment.tsx | 1 + .../report/ReportActionItemMessageEdit.tsx | 5 +- .../report/ReportActionItemParentAction.tsx | 1 - .../home/report/ReportActionItemSingle.tsx | 29 +- .../home/report/ReportActionItemThread.tsx | 3 +- src/pages/home/report/ReportActionsView.js | 7 +- src/pages/home/sidebar/AllSettingsScreen.tsx | 2 +- src/pages/iou/MoneyRequestSelectorPage.js | 4 +- src/pages/iou/request/IOURequestStartPage.js | 12 +- ...yForRefactorRequestParticipantsSelector.js | 14 +- .../iou/request/step/IOURequestStepTag.js | 8 +- .../iou/steps/MoneyRequestAmountForm.tsx | 3 +- .../MoneyRequestParticipantsSelector.js | 11 +- src/pages/settings/AboutPage/AboutPage.tsx | 3 +- src/pages/settings/AppDownloadLinks.tsx | 2 +- .../ExitSurvey/ExitSurveyConfirmPage.tsx | 8 +- src/pages/settings/InitialSettingsPage.tsx | 19 +- .../settings/Preferences/PreferencesPage.js | 3 +- .../Contacts/ContactMethodDetailsPage.tsx | 3 +- .../Profile/Contacts/ContactMethodsPage.tsx | 3 +- src/pages/settings/Profile/ProfilePage.js | 3 +- .../settings/Report/ReportSettingsPage.tsx | 3 +- .../Security/SecuritySettingsPage.tsx | 3 +- .../TwoFactorAuth/Steps/CodesStep.tsx | 3 +- .../TwoFactorAuth/Steps/EnabledStep.tsx | 3 +- .../TwoFactorAuth/Steps/VerifyStep.tsx | 3 +- .../settings/Wallet/ExpensifyCardPage.tsx | 3 +- .../settings/Wallet/TransferBalancePage.tsx | 3 +- .../settings/Wallet/WalletPage/WalletPage.tsx | 7 +- src/pages/signin/SAMLSignInPage/index.tsx | 2 +- src/pages/signin/SignInPageLayout/index.tsx | 7 +- src/pages/tasks/NewTaskPage.js | 12 +- ...Modal.js => TaskAssigneeSelectorModal.tsx} | 134 +-- ...riptionPage.js => TaskDescriptionPage.tsx} | 91 +- ... => TaskShareDestinationSelectorModal.tsx} | 83 +- .../{TaskTitlePage.js => TaskTitlePage.tsx} | 100 +-- src/pages/workspace/WorkspaceInitialPage.tsx | 3 +- src/pages/workspace/WorkspaceJoinUserPage.tsx | 80 ++ src/pages/workspace/WorkspaceMembersPage.tsx | 60 +- src/pages/workspace/WorkspaceProfilePage.tsx | 39 +- .../workspace/WorkspaceProfileSharePage.tsx | 3 +- src/pages/workspace/WorkspacesListPage.tsx | 50 +- src/pages/workspace/WorkspacesListRow.tsx | 49 +- .../categories/CreateCategoryPage.tsx | 110 +++ .../categories/WorkspaceCategoriesPage.tsx | 69 +- .../members/WorkspaceMemberDetailsPage.tsx | 152 ++++ ...orkspaceMemberDetailsRoleSelectionPage.tsx | 87 ++ .../WorkspaceRateAndUnitPage/InitialPage.tsx | 12 +- src/styles/utils/index.ts | 9 +- src/styles/utils/spacing.ts | 4 + src/types/form/WorkspaceCategoryCreateForm.ts | 18 + src/types/form/index.ts | 1 + src/types/onyx/LastSelectedDistanceRates.ts | 3 + src/types/onyx/OnyxUpdatesFromServer.ts | 2 +- src/types/onyx/OriginalMessage.ts | 18 +- src/types/onyx/Policy.ts | 18 +- src/types/onyx/PolicyJoinMember.ts | 17 + src/types/onyx/Task.ts | 2 +- src/types/onyx/index.ts | 10 +- src/utils/arrayDifference.ts | 9 + tests/e2e/utils/{logger.js => logger.ts} | 32 +- tests/perf-test/SidebarUtils.perf-test.ts | 129 +-- tests/ui/UnreadIndicatorsTest.js | 88 +- tests/unit/GithubUtilsTest.ts | 62 +- .../{LocalizeTests.js => LocalizeTests.ts} | 0 tests/unit/ReportActionsUtilsTest.ts | 7 +- tests/unit/ReportUtilsTest.js | 52 +- .../{TranslateTest.js => TranslateTest.ts} | 69 +- ....js => createOrUpdateStagingDeployTest.ts} | 15 +- ...metersTest.js => enhanceParametersTest.ts} | 1 + ...terTest.js => nativeVersionUpdaterTest.ts} | 0 tests/utils/PusherHelper.ts | 17 +- tests/utils/collections/reportActions.ts | 2 + 216 files changed, 3747 insertions(+), 1540 deletions(-) create mode 100644 docs/articles/expensify-classic/settings/account-settings/Change-or-add-email-address.md create mode 100644 src/components/ScrollView.tsx create mode 100644 src/libs/API/parameters/AcceptJoinRequest.ts create mode 100644 src/libs/API/parameters/CreateWorkspaceCategoriesParams.ts create mode 100644 src/libs/API/parameters/DeclineJoinRequest.ts create mode 100644 src/libs/API/parameters/JoinPolicyInviteLink.ts create mode 100644 src/libs/navigateAfterJoinRequest/index.desktop.ts create mode 100644 src/libs/navigateAfterJoinRequest/index.ts create mode 100644 src/libs/navigateAfterJoinRequest/index.web.ts rename src/pages/home/report/{ReportActionItem.js => ReportActionItem.tsx} (51%) rename src/pages/tasks/{TaskAssigneeSelectorModal.js => TaskAssigneeSelectorModal.tsx} (63%) rename src/pages/tasks/{TaskDescriptionPage.js => TaskDescriptionPage.tsx} (61%) rename src/pages/tasks/{TaskShareDestinationSelectorModal.js => TaskShareDestinationSelectorModal.tsx} (62%) rename src/pages/tasks/{TaskTitlePage.js => TaskTitlePage.tsx} (50%) create mode 100644 src/pages/workspace/WorkspaceJoinUserPage.tsx create mode 100644 src/pages/workspace/categories/CreateCategoryPage.tsx create mode 100644 src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx create mode 100644 src/pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage.tsx create mode 100644 src/types/form/WorkspaceCategoryCreateForm.ts create mode 100644 src/types/onyx/LastSelectedDistanceRates.ts create mode 100644 src/types/onyx/PolicyJoinMember.ts create mode 100644 src/utils/arrayDifference.ts rename tests/e2e/utils/{logger.js => logger.ts} (66%) rename tests/unit/{LocalizeTests.js => LocalizeTests.ts} (100%) rename tests/unit/{TranslateTest.js => TranslateTest.ts} (63%) rename tests/unit/{createOrUpdateStagingDeployTest.js => createOrUpdateStagingDeployTest.ts} (97%) rename tests/unit/{enhanceParametersTest.js => enhanceParametersTest.ts} (96%) rename tests/unit/{nativeVersionUpdaterTest.js => nativeVersionUpdaterTest.ts} (100%) diff --git a/.eslintrc.js b/.eslintrc.js index c0c95d3f5686..5451cfff6534 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,13 +1,14 @@ const restrictedImportPaths = [ { name: 'react-native', - importNames: ['useWindowDimensions', 'StatusBar', 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', 'Text'], + importNames: ['useWindowDimensions', 'StatusBar', 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', 'Text', 'ScrollView'], message: [ '', "For 'useWindowDimensions', please use 'src/hooks/useWindowDimensions' instead.", "For 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from 'src/components/Pressable' instead.", "For 'StatusBar', please use 'src/libs/StatusBar' instead.", "For 'Text', please use '@components/Text' instead.", + "For 'ScrollView', please use '@components/ScrollView' instead.", ].join('\n'), }, { diff --git a/.github/actions/javascript/authorChecklist/index.js b/.github/actions/javascript/authorChecklist/index.js index 528a0a11498a..e267769dc457 100644 --- a/.github/actions/javascript/authorChecklist/index.js +++ b/.github/actions/javascript/authorChecklist/index.js @@ -283,14 +283,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -325,11 +325,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -359,7 +359,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/awaitStagingDeploys/index.js b/.github/actions/javascript/awaitStagingDeploys/index.js index f042dbb38a91..dd2aef38e1ee 100644 --- a/.github/actions/javascript/awaitStagingDeploys/index.js +++ b/.github/actions/javascript/awaitStagingDeploys/index.js @@ -395,14 +395,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -437,11 +437,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -471,7 +471,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/checkDeployBlockers/index.js b/.github/actions/javascript/checkDeployBlockers/index.js index 8e10f8b1d8b6..82092be7e0eb 100644 --- a/.github/actions/javascript/checkDeployBlockers/index.js +++ b/.github/actions/javascript/checkDeployBlockers/index.js @@ -362,14 +362,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -404,11 +404,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -438,7 +438,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js b/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js index 4441348a3c36..1752ae62f86c 100644 --- a/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js +++ b/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js @@ -40,8 +40,11 @@ async function run() { // Next, we generate the checklist body let checklistBody = ''; + let checklistAssignees = []; if (shouldCreateNewDeployChecklist) { - checklistBody = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber)); + const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber)); + checklistBody = issueBody; + checklistAssignees = issueAssignees; } else { // Generate the updated PR list, preserving the previous state of `isVerified` for existing PRs const PRList = _.reduce( @@ -94,7 +97,7 @@ async function run() { } const didVersionChange = newVersionTag !== currentChecklistData.tag; - checklistBody = await GithubUtils.generateStagingDeployCashBody( + const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBody( newVersionTag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), @@ -105,6 +108,8 @@ async function run() { didVersionChange ? false : currentChecklistData.isFirebaseChecked, didVersionChange ? false : currentChecklistData.isGHStatusChecked, ); + checklistBody = issueBody; + checklistAssignees = issueAssignees; } // Finally, create or update the checklist @@ -119,7 +124,7 @@ async function run() { ...defaultPayload, title: `Deploy Checklist: New Expensify ${format(new Date(), CONST.DATE_FORMAT_STRING)}`, labels: [CONST.LABELS.STAGING_DEPLOY], - assignees: [CONST.APPLAUSE_BOT], + assignees: [CONST.APPLAUSE_BOT].concat(checklistAssignees), }); console.log(`Successfully created new StagingDeployCash! 🎉 ${newChecklist.html_url}`); return newChecklist; diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js index 154dacbdc3c3..9c9a42709af0 100644 --- a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js @@ -49,8 +49,11 @@ async function run() { // Next, we generate the checklist body let checklistBody = ''; + let checklistAssignees = []; if (shouldCreateNewDeployChecklist) { - checklistBody = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber)); + const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber)); + checklistBody = issueBody; + checklistAssignees = issueAssignees; } else { // Generate the updated PR list, preserving the previous state of `isVerified` for existing PRs const PRList = _.reduce( @@ -103,7 +106,7 @@ async function run() { } const didVersionChange = newVersionTag !== currentChecklistData.tag; - checklistBody = await GithubUtils.generateStagingDeployCashBody( + const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBody( newVersionTag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), @@ -114,6 +117,8 @@ async function run() { didVersionChange ? false : currentChecklistData.isFirebaseChecked, didVersionChange ? false : currentChecklistData.isGHStatusChecked, ); + checklistBody = issueBody; + checklistAssignees = issueAssignees; } // Finally, create or update the checklist @@ -128,7 +133,7 @@ async function run() { ...defaultPayload, title: `Deploy Checklist: New Expensify ${format(new Date(), CONST.DATE_FORMAT_STRING)}`, labels: [CONST.LABELS.STAGING_DEPLOY], - assignees: [CONST.APPLAUSE_BOT], + assignees: [CONST.APPLAUSE_BOT].concat(checklistAssignees), }); console.log(`Successfully created new StagingDeployCash! 🎉 ${newChecklist.html_url}`); return newChecklist; @@ -434,14 +439,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -476,11 +481,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -510,7 +515,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/getArtifactInfo/index.js b/.github/actions/javascript/getArtifactInfo/index.js index ea56ff5f4ebd..e4f7634bd849 100644 --- a/.github/actions/javascript/getArtifactInfo/index.js +++ b/.github/actions/javascript/getArtifactInfo/index.js @@ -321,14 +321,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -363,11 +363,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -397,7 +397,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js index f272929d536a..f941c9524856 100644 --- a/.github/actions/javascript/getDeployPullRequestList/index.js +++ b/.github/actions/javascript/getDeployPullRequestList/index.js @@ -377,14 +377,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -419,11 +419,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -453,7 +453,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/getPullRequestDetails/index.js b/.github/actions/javascript/getPullRequestDetails/index.js index b8d7d821d64e..f4168af28802 100644 --- a/.github/actions/javascript/getPullRequestDetails/index.js +++ b/.github/actions/javascript/getPullRequestDetails/index.js @@ -329,14 +329,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -371,11 +371,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -405,7 +405,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/getReleaseBody/index.js b/.github/actions/javascript/getReleaseBody/index.js index cc1321ce5cd5..547aafe23038 100644 --- a/.github/actions/javascript/getReleaseBody/index.js +++ b/.github/actions/javascript/getReleaseBody/index.js @@ -329,14 +329,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -371,11 +371,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -405,7 +405,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/isStagingDeployLocked/index.js b/.github/actions/javascript/isStagingDeployLocked/index.js index 8124c5795a5a..4938b5bb7745 100644 --- a/.github/actions/javascript/isStagingDeployLocked/index.js +++ b/.github/actions/javascript/isStagingDeployLocked/index.js @@ -313,14 +313,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -355,11 +355,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -389,7 +389,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/index.js b/.github/actions/javascript/markPullRequestsAsDeployed/index.js index 36cd0aaefe4a..2e6ab7e018dd 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/index.js +++ b/.github/actions/javascript/markPullRequestsAsDeployed/index.js @@ -478,14 +478,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -520,11 +520,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -554,7 +554,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js index 329e0d3aad5d..9dd23d68ca0a 100644 --- a/.github/actions/javascript/postTestBuildComment/index.js +++ b/.github/actions/javascript/postTestBuildComment/index.js @@ -388,14 +388,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -430,11 +430,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -464,7 +464,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/reopenIssueWithComment/index.js b/.github/actions/javascript/reopenIssueWithComment/index.js index 6a5f89badb5e..42196053f63f 100644 --- a/.github/actions/javascript/reopenIssueWithComment/index.js +++ b/.github/actions/javascript/reopenIssueWithComment/index.js @@ -283,14 +283,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -325,11 +325,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -359,7 +359,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/reviewerChecklist/index.js b/.github/actions/javascript/reviewerChecklist/index.js index 322b529b89bf..22335b36bd2b 100644 --- a/.github/actions/javascript/reviewerChecklist/index.js +++ b/.github/actions/javascript/reviewerChecklist/index.js @@ -283,14 +283,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -325,11 +325,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -359,7 +359,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/actions/javascript/verifySignedCommits/index.js b/.github/actions/javascript/verifySignedCommits/index.js index ba188d3a2b86..239f20c9d258 100644 --- a/.github/actions/javascript/verifySignedCommits/index.js +++ b/.github/actions/javascript/verifySignedCommits/index.js @@ -283,14 +283,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -325,11 +325,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -359,7 +359,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js index 0cd407c78153..e988167850ec 100644 --- a/.github/libs/GithubUtils.js +++ b/.github/libs/GithubUtils.js @@ -250,14 +250,14 @@ class GithubUtils { .then((data) => { // The format of this map is following: // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' // } const internalQAPRMap = _.reduce( _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), (map, pr) => { // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); + map[pr.html_url] = pr.merged_by.login; return map; }, {}, @@ -292,11 +292,11 @@ class GithubUtils { if (!_.isEmpty(internalQAPRMap)) { console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; + issueBody += ` - ${mergerMention}`; issueBody += '\r\n'; }); issueBody += '\r\n\r\n'; @@ -326,7 +326,9 @@ class GithubUtils { issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const issueAssignees = _.values(internalQAPRMap); + const issue = {issueBody, issueAssignees}; + return issue; }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } diff --git a/android/app/build.gradle b/android/app/build.gradle index aedb0b9fbc13..cbe11156b093 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001044707 - versionName "1.4.47-7" + versionCode 1001044900 + versionName "1.4.49-0" } flavorDimensions "default" diff --git a/docs/articles/expensify-classic/settings/account-settings/Change-or-add-email-address.md b/docs/articles/expensify-classic/settings/account-settings/Change-or-add-email-address.md new file mode 100644 index 000000000000..754b9a7f9ac0 --- /dev/null +++ b/docs/articles/expensify-classic/settings/account-settings/Change-or-add-email-address.md @@ -0,0 +1,24 @@ +--- +title: Change or add email address +description: Update your Expensify email address or add a secondary email +--- +
+ +The primary email address on your Expensify account is the email that receives email updates and notifications for your account. You can add a secondary email address in order to +- Change your primary email to a new one. +- Connect your personal email address as a secondary login if your primary email address is one from your employer. This allows you to always have access to your Expensify account, even if your employer changes. + +{% include info.html %} +Before you can remove a primary email address, you must add a new one to your Expensify account and make it the primary using the steps below. Email addresses must be added as a secondary login before they can be made the primary. +{% include end-info.html %} + +*Note: This process is currently not available from the mobile app and must be completed from the Expensify website.* + +1. Hover over Settings, then click **Account**. +2. Under the Account Details tab, scroll down to the Secondary Logins section and click **Add Secondary Login**. +3. Enter the email address or phone number you wish to use as a secondary login. For phone numbers, be sure to include the international code, if applicable. +4. Find the email or text message from Expensify containing the Magic Code and enter it into the field. +5. To make the new email address the primary address for your account, click **Make Primary**. + +You can keep both logins, or you can click **Remove** next to the old email address to delete it from your account. +
diff --git a/docs/redirects.csv b/docs/redirects.csv index 4ed309467f13..097c0ad2679e 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -46,8 +46,8 @@ https://community.expensify.com/discussion/5366/deep-dive-troubleshooting-credit https://community.expensify.com/discussion/9554/how-to-set-up-global-reimbursemen,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursements https://community.expensify.com/discussion/4463/how-to-remove-or-manage-settings-for-imported-personal-cards,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards https://community.expensify.com/discussion/5793/how-to-connect-your-personal-card-to-import-expenses,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards -https://community.expensify.com/discussion/4826/how-to-set-your-annual-subscription-size,https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription -https://community.expensify.com/discussion/5667/deep-dive-how-does-the-annual-subscription-billing-work,https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription +https://community.expensify.com/discussion/4826/how-to-set-your-annual-subscription-size,https://use.expensify.com/ +https://community.expensify.com/discussion/5667/deep-dive-how-does-the-annual-subscription-billing-work,https://use.expensify.com/ https://help.expensify.com/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager,https://help.expensify.com/articles/expensify-classic/expensify-partner-program/Your-Expensify-Partner-Manager https://help.expensify.com/expensify-classic/hubs/getting-started/plan-types,https://use.expensify.com/ https://help.expensify.com/articles/expensify-classic/getting-started/Employees,https://help.expensify.com/articles/expensify-classic/getting-started/Join-your-company's-workspace @@ -60,3 +60,8 @@ https://help.expensify.com/articles/expensify-classic/account-settings/Preferenc https://help.expensify.com/articles/expensify-classic/account-settings/Merge-Accounts,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Merge-accounts https://help.expensify.com/articles/expensify-classic/getting-started/Individual-Users,https://help.expensify.com/articles/expensify-classic/getting-started/Create-a-workspace-for-yourself https://help.expensify.com/articles/expensify-classic/getting-started/Invite-Members,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Invite-Members +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription,https://use.expensify.com/ +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription,https://use.expensify.com/ +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription,https://use.expensify.com/ +https://help.expensify.com/articles/expensify-classic/settings/Merge-Accounts,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Merge-accounts +https://help.expensify.com/articles/expensify-classic/settings/Preferences,https://help.expensify.com/expensify-classic/hubs/settings/account-settings diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 5d93786a5ad6..bccea916e01a 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.47 + 1.4.49 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.47.7 + 1.4.49.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index fad0a170d4ab..058476f03a9d 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.47 + 1.4.49 CFBundleSignature ???? CFBundleVersion - 1.4.47.7 + 1.4.49.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 220fdd322c6e..869b3aebab44 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.47 + 1.4.49 CFBundleVersion - 1.4.47.7 + 1.4.49.0 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index cc717e8d6a0f..bfab80fe3148 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.47-7", + "version": "1.4.49-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.47-7", + "version": "1.4.49-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 5b498cb09dc2..a78c23c3c960 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.47-7", + "version": "1.4.49-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/CONST.ts b/src/CONST.ts index 1e3b33d5d760..70fecab70c39 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6,6 +6,12 @@ import * as KeyCommand from 'react-native-key-command'; import * as Url from './libs/Url'; import SCREENS from './SCREENS'; +type RateAndUnit = { + unit: string; + rate: number; +}; +type CurrencyDefaultMileageRate = Record; + // Creating a default array and object this way because objects ({}) and arrays ([]) are not stable types. // Freezing the array ensures that it cannot be unintentionally modified. const EMPTY_ARRAY = Object.freeze([]); @@ -313,6 +319,7 @@ const CONST = { BETA_COMMENT_LINKING: 'commentLinking', VIOLATIONS: 'violations', REPORT_FIELDS: 'reportFields', + P2P_DISTANCE_REQUESTS: 'p2pDistanceRequests', WORKFLOWS_DELAYED_SUBMISSION: 'workflowsDelayedSubmission', }, BUTTON_STATES: { @@ -568,6 +575,7 @@ const CONST = { LIMIT: 50, TYPE: { ADDCOMMENT: 'ADDCOMMENT', + ACTIONABLEJOINREQUEST: 'ACTIONABLEJOINREQUEST', APPROVED: 'APPROVED', CHRONOSOOOLIST: 'CHRONOSOOOLIST', CLOSED: 'CLOSED', @@ -671,6 +679,10 @@ const CONST = { INVITE: 'invited', NOTHING: 'nothing', }, + ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION: { + ACCEPT: 'accept', + DECLINE: 'decline', + }, ARCHIVE_REASON: { DEFAULT: 'default', ACCOUNT_CLOSED: 'accountClosed', @@ -1414,6 +1426,7 @@ const CONST = { MILEAGE_IRS_RATE: 0.655, DEFAULT_RATE: 'Default Rate', RATE_DECIMALS: 3, + FAKE_P2P_ID: '_FAKE_P2P_ID_', }, TERMS: { @@ -1646,6 +1659,7 @@ const CONST = { FORM_CHARACTER_LIMIT: 50, LEGAL_NAMES_CHARACTER_LIMIT: 150, LOGIN_CHARACTER_LIMIT: 254, + CATEGORY_NAME_LIMIT: 256, TITLE_CHARACTER_LIMIT: 100, DESCRIPTION_LIMIT: 500, @@ -1726,6 +1740,7 @@ const CONST = { MAX_64BIT_LEFT_PART: 92233, MAX_64BIT_MIDDLE_PART: 7203685, MAX_64BIT_RIGHT_PART: 4775807, + INVALID_CATEGORY_NAME: '###', // When generating a random value to fit in 7 digits (for the `middle` or `right` parts above), this is the maximum value to multiply by Math.random(). MAX_INT_FOR_RANDOM_7_DIGIT_VALUE: 10000000, @@ -3108,6 +3123,7 @@ const CONST = { ONYX_UPDATE_TYPES: { HTTPS: 'https', PUSHER: 'pusher', + AIRSHIP: 'airship', }, EVENTS: { SCROLLING: 'scrolling', @@ -3340,6 +3356,664 @@ const CONST = { ADDRESS: 3, }, }, + CURRENCY_TO_DEFAULT_MILEAGE_RATE: JSON.parse(`{ + "AED": { + "rate": 396, + "unit": "km" + }, + "AFN": { + "rate": 8369, + "unit": "km" + }, + "ALL": { + "rate": 11104, + "unit": "km" + }, + "AMD": { + "rate": 56842, + "unit": "km" + }, + "ANG": { + "rate": 193, + "unit": "km" + }, + "AOA": { + "rate": 67518, + "unit": "km" + }, + "ARS": { + "rate": 9873, + "unit": "km" + }, + "AUD": { + "rate": 85, + "unit": "km" + }, + "AWG": { + "rate": 195, + "unit": "km" + }, + "AZN": { + "rate": 183, + "unit": "km" + }, + "BAM": { + "rate": 177, + "unit": "km" + }, + "BBD": { + "rate": 216, + "unit": "km" + }, + "BDT": { + "rate": 9130, + "unit": "km" + }, + "BGN": { + "rate": 177, + "unit": "km" + }, + "BHD": { + "rate": 40, + "unit": "km" + }, + "BIF": { + "rate": 210824, + "unit": "km" + }, + "BMD": { + "rate": 108, + "unit": "km" + }, + "BND": { + "rate": 145, + "unit": "km" + }, + "BOB": { + "rate": 745, + "unit": "km" + }, + "BRL": { + "rate": 594, + "unit": "km" + }, + "BSD": { + "rate": 108, + "unit": "km" + }, + "BTN": { + "rate": 7796, + "unit": "km" + }, + "BWP": { + "rate": 1180, + "unit": "km" + }, + "BYN": { + "rate": 280, + "unit": "km" + }, + "BYR": { + "rate": 2159418, + "unit": "km" + }, + "BZD": { + "rate": 217, + "unit": "km" + }, + "CAD": { + "rate": 70, + "unit": "km" + }, + "CDF": { + "rate": 213674, + "unit": "km" + }, + "CHF": { + "rate": 100, + "unit": "km" + }, + "CLP": { + "rate": 77249, + "unit": "km" + }, + "CNY": { + "rate": 702, + "unit": "km" + }, + "COP": { + "rate": 383668, + "unit": "km" + }, + "CRC": { + "rate": 65899, + "unit": "km" + }, + "CUC": { + "rate": 108, + "unit": "km" + }, + "CUP": { + "rate": 2776, + "unit": "km" + }, + "CVE": { + "rate": 6112, + "unit": "km" + }, + "CZK": { + "rate": 2356, + "unit": "km" + }, + "DJF": { + "rate": 19151, + "unit": "km" + }, + "DKK": { + "rate": 673, + "unit": "km" + }, + "DOP": { + "rate": 6144, + "unit": "km" + }, + "DZD": { + "rate": 14375, + "unit": "km" + }, + "EEK": { + "rate": 1576, + "unit": "km" + }, + "EGP": { + "rate": 1696, + "unit": "km" + }, + "ERN": { + "rate": 1617, + "unit": "km" + }, + "ETB": { + "rate": 4382, + "unit": "km" + }, + "EUR": { + "rate": 3, + "unit": "km" + }, + "FJD": { + "rate": 220, + "unit": "km" + }, + "FKP": { + "rate": 77, + "unit": "km" + }, + "GBP": { + "rate": 45, + "unit": "mi" + }, + "GEL": { + "rate": 359, + "unit": "km" + }, + "GHS": { + "rate": 620, + "unit": "km" + }, + "GIP": { + "rate": 77, + "unit": "km" + }, + "GMD": { + "rate": 5526, + "unit": "km" + }, + "GNF": { + "rate": 1081319, + "unit": "km" + }, + "GTQ": { + "rate": 832, + "unit": "km" + }, + "GYD": { + "rate": 22537, + "unit": "km" + }, + "HKD": { + "rate": 837, + "unit": "km" + }, + "HNL": { + "rate": 2606, + "unit": "km" + }, + "HRK": { + "rate": 684, + "unit": "km" + }, + "HTG": { + "rate": 8563, + "unit": "km" + }, + "HUF": { + "rate": 33091, + "unit": "km" + }, + "IDR": { + "rate": 1555279, + "unit": "km" + }, + "ILS": { + "rate": 356, + "unit": "km" + }, + "INR": { + "rate": 7805, + "unit": "km" + }, + "IQD": { + "rate": 157394, + "unit": "km" + }, + "IRR": { + "rate": 4539961, + "unit": "km" + }, + "ISK": { + "rate": 13518, + "unit": "km" + }, + "JMD": { + "rate": 15794, + "unit": "km" + }, + "JOD": { + "rate": 77, + "unit": "km" + }, + "JPY": { + "rate": 11748, + "unit": "km" + }, + "KES": { + "rate": 11845, + "unit": "km" + }, + "KGS": { + "rate": 9144, + "unit": "km" + }, + "KHR": { + "rate": 437658, + "unit": "km" + }, + "KMF": { + "rate": 44418, + "unit": "km" + }, + "KPW": { + "rate": 97043, + "unit": "km" + }, + "KRW": { + "rate": 121345, + "unit": "km" + }, + "KWD": { + "rate": 32, + "unit": "km" + }, + "KYD": { + "rate": 90, + "unit": "km" + }, + "KZT": { + "rate": 45396, + "unit": "km" + }, + "LAK": { + "rate": 1010829, + "unit": "km" + }, + "LBP": { + "rate": 164153, + "unit": "km" + }, + "LKR": { + "rate": 21377, + "unit": "km" + }, + "LRD": { + "rate": 18709, + "unit": "km" + }, + "LSL": { + "rate": 1587, + "unit": "km" + }, + "LTL": { + "rate": 348, + "unit": "km" + }, + "LVL": { + "rate": 71, + "unit": "km" + }, + "LYD": { + "rate": 486, + "unit": "km" + }, + "MAD": { + "rate": 967, + "unit": "km" + }, + "MDL": { + "rate": 1910, + "unit": "km" + }, + "MGA": { + "rate": 406520, + "unit": "km" + }, + "MKD": { + "rate": 5570, + "unit": "km" + }, + "MMK": { + "rate": 152083, + "unit": "km" + }, + "MNT": { + "rate": 306788, + "unit": "km" + }, + "MOP": { + "rate": 863, + "unit": "km" + }, + "MRO": { + "rate": 38463, + "unit": "km" + }, + "MRU": { + "rate": 3862, + "unit": "km" + }, + "MUR": { + "rate": 4340, + "unit": "km" + }, + "MVR": { + "rate": 1667, + "unit": "km" + }, + "MWK": { + "rate": 84643, + "unit": "km" + }, + "MXN": { + "rate": 2219, + "unit": "km" + }, + "MYR": { + "rate": 444, + "unit": "km" + }, + "MZN": { + "rate": 7772, + "unit": "km" + }, + "NAD": { + "rate": 1587, + "unit": "km" + }, + "NGN": { + "rate": 42688, + "unit": "km" + }, + "NIO": { + "rate": 3772, + "unit": "km" + }, + "NOK": { + "rate": 917, + "unit": "km" + }, + "NPR": { + "rate": 12474, + "unit": "km" + }, + "NZD": { + "rate": 151, + "unit": "km" + }, + "OMR": { + "rate": 42, + "unit": "km" + }, + "PAB": { + "rate": 108, + "unit": "km" + }, + "PEN": { + "rate": 401, + "unit": "km" + }, + "PGK": { + "rate": 380, + "unit": "km" + }, + "PHP": { + "rate": 5234, + "unit": "km" + }, + "PKR": { + "rate": 16785, + "unit": "km" + }, + "PLN": { + "rate": 415, + "unit": "km" + }, + "PYG": { + "rate": 704732, + "unit": "km" + }, + "QAR": { + "rate": 393, + "unit": "km" + }, + "RON": { + "rate": 443, + "unit": "km" + }, + "RSD": { + "rate": 10630, + "unit": "km" + }, + "RUB": { + "rate": 8074, + "unit": "km" + }, + "RWF": { + "rate": 107182, + "unit": "km" + }, + "SAR": { + "rate": 404, + "unit": "km" + }, + "SBD": { + "rate": 859, + "unit": "km" + }, + "SCR": { + "rate": 2287, + "unit": "km" + }, + "SDG": { + "rate": 41029, + "unit": "km" + }, + "SEK": { + "rate": 917, + "unit": "km" + }, + "SGD": { + "rate": 145, + "unit": "km" + }, + "SHP": { + "rate": 77, + "unit": "km" + }, + "SLL": { + "rate": 1102723, + "unit": "km" + }, + "SOS": { + "rate": 62604, + "unit": "km" + }, + "SRD": { + "rate": 1526, + "unit": "km" + }, + "STD": { + "rate": 2223309, + "unit": "km" + }, + "STN": { + "rate": 2232, + "unit": "km" + }, + "SVC": { + "rate": 943, + "unit": "km" + }, + "SYP": { + "rate": 82077, + "unit": "km" + }, + "SZL": { + "rate": 1585, + "unit": "km" + }, + "THB": { + "rate": 3328, + "unit": "km" + }, + "TJS": { + "rate": 1230, + "unit": "km" + }, + "TMT": { + "rate": 378, + "unit": "km" + }, + "TND": { + "rate": 295, + "unit": "km" + }, + "TOP": { + "rate": 245, + "unit": "km" + }, + "TRY": { + "rate": 845, + "unit": "km" + }, + "TTD": { + "rate": 732, + "unit": "km" + }, + "TWD": { + "rate": 3055, + "unit": "km" + }, + "TZS": { + "rate": 250116, + "unit": "km" + }, + "UAH": { + "rate": 2985, + "unit": "km" + }, + "UGX": { + "rate": 395255, + "unit": "km" + }, + "USD": { + "rate": 67, + "unit": "mi" + }, + "UYU": { + "rate": 4777, + "unit": "km" + }, + "UZS": { + "rate": 1131331, + "unit": "km" + }, + "VEB": { + "rate": 679346, + "unit": "km" + }, + "VEF": { + "rate": 26793449, + "unit": "km" + }, + "VES": { + "rate": 194381905, + "unit": "km" + }, + "VND": { + "rate": 2487242, + "unit": "km" + }, + "VUV": { + "rate": 11748, + "unit": "km" + }, + "WST": { + "rate": 272, + "unit": "km" + }, + "XAF": { + "rate": 59224, + "unit": "km" + }, + "XCD": { + "rate": 291, + "unit": "km" + }, + "XOF": { + "rate": 59224, + "unit": "km" + }, + "XPF": { + "rate": 10783, + "unit": "km" + }, + "YER": { + "rate": 27037, + "unit": "km" + }, + "ZAR": { + "rate": 1588, + "unit": "km" + }, + "ZMK": { + "rate": 566489, + "unit": "km" + }, + "ZMW": { + "rate": 2377, + "unit": "km" + } + }`) as CurrencyDefaultMileageRate, EXIT_SURVEY: { REASONS: { diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index f6b5c635e4ae..31e22491e2b9 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1,4 +1,4 @@ -import type {OnyxEntry} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type CONST from './CONST'; import type * as FormTypes from './types/form'; @@ -128,6 +128,9 @@ const ONYXKEYS = { /** This NVP contains the choice that the user made on the engagement modal */ NVP_INTRO_SELECTED: 'introSelected', + /** The NVP with the last distance rate used per policy */ + NVP_LAST_SELECTED_DISTANCE_RATES: 'lastSelectedDistanceRates', + /** Does this user have push notifications enabled for this device? */ PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', @@ -284,6 +287,7 @@ const ONYXKEYS = { POLICY_MEMBERS: 'policyMembers_', POLICY_DRAFTS: 'policyDrafts_', POLICY_MEMBERS_DRAFTS: 'policyMembersDrafts_', + POLICY_JOIN_MEMBER: 'policyJoinMember_', POLICY_CATEGORIES: 'policyCategories_', POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', @@ -326,6 +330,8 @@ const ONYXKEYS = { ADD_DEBIT_CARD_FORM: 'addDebitCardForm', ADD_DEBIT_CARD_FORM_DRAFT: 'addDebitCardFormDraft', WORKSPACE_SETTINGS_FORM: 'workspaceSettingsForm', + WORKSPACE_CATEGORY_CREATE_FORM: 'workspaceCategoryCreate', + WORKSPACE_CATEGORY_CREATE_FORM_DRAFT: 'workspaceCategoryCreateDraft', WORKSPACE_SETTINGS_FORM_DRAFT: 'workspaceSettingsFormDraft', WORKSPACE_DESCRIPTION_FORM: 'workspaceDescriptionForm', WORKSPACE_DESCRIPTION_FORM_DRAFT: 'workspaceDescriptionFormDraft', @@ -407,6 +413,7 @@ type AllOnyxKeys = DeepValueOf; type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM]: FormTypes.AddDebitCardForm; [ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM]: FormTypes.WorkspaceSettingsForm; + [ONYXKEYS.FORMS.WORKSPACE_CATEGORY_CREATE_FORM]: FormTypes.WorkspaceCategoryCreateForm; [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM]: FormTypes.WorkspaceRateAndUnitForm; [ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM]: FormTypes.CloseAccountForm; [ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM]: FormTypes.ProfileSettingsForm; @@ -481,6 +488,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; [ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string; [ONYXKEYS.COLLECTION.NEXT_STEP]: OnyxTypes.ReportNextStep; + [ONYXKEYS.COLLECTION.POLICY_JOIN_MEMBER]: OnyxTypes.PolicyJoinMember; }; type OnyxValuesMapping = { @@ -524,6 +532,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[]; [ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL]: boolean; [ONYXKEYS.NVP_INTRO_SELECTED]: OnyxTypes.IntroSelected; + [ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES]: OnyxTypes.LastSelectedDistanceRates; [ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED]: boolean; [ONYXKEYS.PLAID_DATA]: OnyxTypes.PlaidData; [ONYXKEYS.IS_PLAID_DISABLED]: boolean; @@ -582,7 +591,7 @@ type OnyxFormDraftKey = keyof OnyxFormDraftValuesMapping; type OnyxValueKey = keyof OnyxValuesMapping; type OnyxKey = OnyxValueKey | OnyxCollectionKey | OnyxFormKey | OnyxFormDraftKey; -type OnyxValue = OnyxEntry; +type OnyxValue = TOnyxKey extends keyof OnyxCollectionValuesMapping ? OnyxCollection : OnyxEntry; type MissingOnyxKeysError = `Error: Types don't match, OnyxKey type is missing: ${Exclude}`; /** If this type errors, it means that the `OnyxKey` type is missing some keys. */ diff --git a/src/ROUTES.ts b/src/ROUTES.ts index cfc287ba2cdc..2ed9fbc3666e 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -478,6 +478,10 @@ const ROUTES = { route: 'workspace/:policyID/avatar', getRoute: (policyID: string) => `workspace/${policyID}/avatar` as const, }, + WORKSPACE_JOIN_USER: { + route: 'workspace/:policyID/join', + getRoute: (policyID: string, inviterEmail: string) => `workspace/${policyID}/join?email=${inviterEmail}` as const, + }, WORKSPACE_SETTINGS_CURRENCY: { route: 'workspace/:policyID/settings/currency', getRoute: (policyID: string) => `workspace/${policyID}/settings/currency` as const, @@ -546,10 +550,23 @@ const ROUTES = { route: 'workspace/:policyID/categories/settings', getRoute: (policyID: string) => `workspace/${policyID}/categories/settings` as const, }, + WORKSPACE_CATEGORY_CREATE: { + route: 'workspace/:policyID/categories/new', + getRoute: (policyID: string) => `workspace/${policyID}/categories/new` as const, + }, WORKSPACE_TAGS: { route: 'workspace/:policyID/tags', getRoute: (policyID: string) => `workspace/${policyID}/tags` as const, }, + WORKSPACE_MEMBER_DETAILS: { + route: 'workspace/:policyID/members/:accountID', + getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/members/${accountID}`, backTo), + }, + WORKSPACE_MEMBER_ROLE_SELECTION: { + route: 'workspace/:policyID/members/:accountID/role-selection', + getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/members/${accountID}/role-selection`, backTo), + }, + // Referral program promotion REFERRAL_DETAILS_MODAL: { route: 'referral/:contentType', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 2369fe435feb..6fc61aec61a0 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -128,6 +128,7 @@ const SCREENS = { SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', DESKTOP_SIGN_IN_REDIRECT: 'DesktopSignInRedirect', SAML_SIGN_IN: 'SAMLSignIn', + WORKSPACE_JOIN_USER: 'WorkspaceJoinUser', MONEY_REQUEST: { MANUAL_TAB: 'manual', @@ -223,8 +224,11 @@ const SCREENS = { DESCRIPTION: 'Workspace_Profile_Description', SHARE: 'Workspace_Profile_Share', NAME: 'Workspace_Profile_Name', + CATEGORY_CREATE: 'Category_Create', CATEGORY_SETTINGS: 'Category_Settings', CATEGORIES_SETTINGS: 'Categories_Settings', + MEMBER_DETAILS: 'Workspace_Member_Details', + MEMBER_DETAILS_ROLE_SELECTION: 'Workspace_Member_Details_Role_Selection', }, EDIT_REQUEST: { diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx index 39c91c2a0789..a2e3f5d9948e 100644 --- a/src/components/AddressSearch/index.tsx +++ b/src/components/AddressSearch/index.tsx @@ -1,11 +1,12 @@ import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; -import {ActivityIndicator, Keyboard, LogBox, ScrollView, View} from 'react-native'; +import {ActivityIndicator, Keyboard, LogBox, View} from 'react-native'; import type {LayoutChangeEvent} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; import type {GooglePlaceData, GooglePlaceDetail} from 'react-native-google-places-autocomplete'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import LocationErrorMessage from '@components/LocationErrorMessage'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index e924cb8c13e9..b2c9fed64467 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -109,7 +109,6 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) { isHovered={isModalHovered} isFocused={isFocused} optionalVideoDuration={item.duration} - isUsedInCarousel />
diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index f6a56dc73088..461548f0d2b1 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -1,7 +1,7 @@ import Str from 'expensify-common/lib/str'; import PropTypes from 'prop-types'; import React, {memo, useEffect, useState} from 'react'; -import {ActivityIndicator, ScrollView, View} from 'react-native'; +import {ActivityIndicator, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import * as AttachmentsPropTypes from '@components/Attachments/propTypes'; @@ -9,6 +9,7 @@ import DistanceEReceipt from '@components/DistanceEReceipt'; import EReceipt from '@components/EReceipt'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index 5be33e6ff2ec..635645b0035b 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -2,8 +2,12 @@ import React, {useCallback} from 'react'; import type {GestureResponderEvent, PressableStateCallbackType, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; +import type IconAsset from '@src/types/utils/IconAsset'; +import Icon from './Icon'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Text from './Text'; @@ -31,11 +35,29 @@ type BadgeProps = { /** Callback to be called on onPress */ onPress?: (event?: GestureResponderEvent | KeyboardEvent) => void; + + /** The icon asset to display to the left of the text */ + icon?: IconAsset | null; + + /** Any additional styles to pass to the left icon container. */ + iconStyles?: StyleProp; }; -function Badge({success = false, error = false, pressable = false, text, environment = CONST.ENVIRONMENT.DEV, badgeStyles, textStyles, onPress = () => {}}: BadgeProps) { +function Badge({ + success = false, + error = false, + pressable = false, + text, + environment = CONST.ENVIRONMENT.DEV, + badgeStyles, + textStyles, + onPress = () => {}, + icon, + iconStyles = [], +}: BadgeProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const theme = useTheme(); const textColorStyles = success || error ? styles.textWhite : undefined; const Wrapper = pressable ? PressableWithoutFeedback : View; @@ -53,6 +75,16 @@ function Badge({success = false, error = false, pressable = false, text, environ aria-label={!pressable ? text : undefined} accessible={false} > + {icon && ( + + + + )} , + ref: ForwardedRef, ) { const theme = useTheme(); const styles = useThemeStyles(); diff --git a/src/components/DistanceEReceipt.tsx b/src/components/DistanceEReceipt.tsx index 941d63c1bf94..fda0c5441734 100644 --- a/src/components/DistanceEReceipt.tsx +++ b/src/components/DistanceEReceipt.tsx @@ -1,5 +1,5 @@ import React, {useMemo} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import EReceiptBackground from '@assets/images/eReceipt_background.svg'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -15,6 +15,7 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import ImageSVG from './ImageSVG'; import PendingMapView from './MapView/PendingMapView'; +import ScrollView from './ScrollView'; import Text from './Text'; import ThumbnailImage from './ThumbnailImage'; diff --git a/src/components/DistanceRequest/index.tsx b/src/components/DistanceRequest/index.tsx index 9900656057ce..8920c9a4a92b 100644 --- a/src/components/DistanceRequest/index.tsx +++ b/src/components/DistanceRequest/index.tsx @@ -2,6 +2,7 @@ import type {RouteProp} from '@react-navigation/native'; import lodashIsEqual from 'lodash/isEqual'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports import type {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; diff --git a/src/components/DraggableList/index.tsx b/src/components/DraggableList/index.tsx index dc78a3ce6222..418f3e93eac4 100644 --- a/src/components/DraggableList/index.tsx +++ b/src/components/DraggableList/index.tsx @@ -1,7 +1,9 @@ import React, {useCallback} from 'react'; import {DragDropContext, Draggable, Droppable} from 'react-beautiful-dnd'; import type {OnDragEndResponder} from 'react-beautiful-dnd'; -import {ScrollView} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {ScrollView as RNScrollView} from 'react-native'; +import ScrollView from '@components/ScrollView'; import useThemeStyles from '@hooks/useThemeStyles'; import type {DraggableListProps} from './types'; import useDraggableInPortal from './useDraggableInPortal'; @@ -37,7 +39,7 @@ function DraggableList( // eslint-disable-next-line @typescript-eslint/naming-convention ListFooterComponent, }: DraggableListProps, - ref: React.ForwardedRef, + ref: React.ForwardedRef, ) { const styles = useThemeStyles(); /** diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 9b68916c4003..88938f31cd79 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -1,6 +1,7 @@ import type {ForwardedRef} from 'react'; import React, {forwardRef, useEffect, useRef} from 'react'; -import type {GestureResponderEvent, Role} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {GestureResponderEvent, Role, Text} from 'react-native'; import {Platform, View} from 'react-native'; import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import Svg, {Path} from 'react-native-svg'; @@ -58,12 +59,12 @@ type FloatingActionButtonProps = { role: Role; }; -function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: FloatingActionButtonProps, ref: ForwardedRef) { +function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: FloatingActionButtonProps, ref: ForwardedRef) { const {success, buttonDefaultBG, textLight, textDark} = useTheme(); const styles = useThemeStyles(); const borderRadius = styles.floatingActionButton.borderRadius; const {translate} = useLocalize(); - const fabPressable = useRef(null); + const fabPressable = useRef(null); const sharedValue = useSharedValue(isActive ? 1 : 0); const buttonRef = ref; @@ -112,9 +113,9 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo { - fabPressable.current = el; + fabPressable.current = el ?? null; if (buttonRef && 'current' in buttonRef) { - buttonRef.current = el; + buttonRef.current = el ?? null; } }} accessibilityLabel={accessibilityLabel} diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index 5615f3b87cfa..5c2488ca144a 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -1,13 +1,15 @@ import React, {useCallback, useMemo, useRef} from 'react'; import type {RefObject} from 'react'; -import type {StyleProp, View, ViewStyle} from 'react-native'; -import {Keyboard, ScrollView} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {ScrollView as RNScrollView, StyleProp, View, ViewStyle} from 'react-native'; +import {Keyboard} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import FormElement from '@components/FormElement'; import SafeAreaConsumer from '@components/SafeAreaConsumer'; import type {SafeAreaChildrenProps} from '@components/SafeAreaConsumer/types'; +import ScrollView from '@components/ScrollView'; import ScrollViewWithContext from '@components/ScrollViewWithContext'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; @@ -60,7 +62,7 @@ function FormWrapper({ disablePressOnEnter = true, }: FormWrapperProps) { const styles = useThemeStyles(); - const formRef = useRef(null); + const formRef = useRef(null); const formContentRef = useRef(null); const errorMessage = useMemo(() => (formState ? ErrorUtils.getLatestErrorMessage(formState) : undefined), [formState]); diff --git a/src/components/FormScrollView.tsx b/src/components/FormScrollView.tsx index ade167e9e628..91f5a825a38a 100644 --- a/src/components/FormScrollView.tsx +++ b/src/components/FormScrollView.tsx @@ -1,15 +1,16 @@ import type {ForwardedRef} from 'react'; import React from 'react'; -import type {ScrollViewProps} from 'react-native'; -import {ScrollView} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {ScrollView as RNScrollView, ScrollViewProps} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; +import ScrollView from './ScrollView'; type FormScrollViewProps = ScrollViewProps & { /** Form elements */ children: React.ReactNode; }; -function FormScrollView({children, ...rest}: FormScrollViewProps, ref: ForwardedRef) { +function FormScrollView({children, ...rest}: FormScrollViewProps, ref: ForwardedRef) { const styles = useThemeStyles(); return ( (null); - const transferBalanceButtonRef = useRef(null); + const anchorRef = useRef(null); + const transferBalanceButtonRef = useRef(null); const [shouldShowAddPaymentMenu, setShouldShowAddPaymentMenu] = useState(false); @@ -111,7 +111,7 @@ function KYCWall({ return; } - const buttonPosition = getClickedTargetLocation(transferBalanceButtonRef.current); + const buttonPosition = getClickedTargetLocation(transferBalanceButtonRef.current as HTMLDivElement); const position = getAnchorPosition(buttonPosition); setPositionAddPaymentMenu(position); @@ -162,7 +162,7 @@ function KYCWall({ } // Use event target as fallback if anchorRef is null for safety - const targetElement = anchorRef.current ?? (event?.currentTarget as HTMLElement); + const targetElement = anchorRef.current ?? (event?.currentTarget as HTMLDivElement); transferBalanceButtonRef.current = targetElement; @@ -181,7 +181,7 @@ function KYCWall({ return; } - const clickedElementLocation = getClickedTargetLocation(targetElement); + const clickedElementLocation = getClickedTargetLocation(targetElement as HTMLDivElement); const position = getAnchorPosition(clickedElementLocation); setPositionAddPaymentMenu(position); diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 102f85ea49b9..5784be21bac3 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -88,7 +88,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0; const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; - const shouldShowNextStep = isFromPaidPolicy && !!nextStep?.message?.length; + const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length; const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index fa6de1c2e4f4..68114dcf4e4c 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -217,11 +217,12 @@ function MoneyRequestConfirmationList(props) { const {onSendMoney, onConfirm, onSelectParticipant} = props; const {translate, toLocaleDigit} = useLocalize(); const transaction = props.transaction; - const {canUseViolations} = usePermissions(); + const {canUseP2PDistanceRequests, canUseViolations} = usePermissions(); const isTypeRequest = props.iouType === CONST.IOU.TYPE.REQUEST; const isSplitBill = props.iouType === CONST.IOU.TYPE.SPLIT; const isTypeSend = props.iouType === CONST.IOU.TYPE.SEND; + const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isSplitBill); const isSplitWithScan = isSplitBill && props.isScanRequest; @@ -721,13 +722,14 @@ function MoneyRequestConfirmationList(props) { )} {props.isDistanceRequest && ( Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(props.iouType, props.reportID))} - disabled={didConfirm || !isTypeRequest} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + disabled={didConfirm || !canEditDistance} interactive={!props.isReadOnly} /> )} diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index fe8cc3506b3f..7f9ab3fe0dc9 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -7,7 +7,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as HeaderUtils from '@libs/HeaderUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -79,16 +78,11 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); const isPending = TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction); - const isRequestModifiable = !isSettled && !isApproved && !ReportActionsUtils.isDeletedAction(parentReportAction); - const canModifyRequest = isActionOwner && !isSettled && !isApproved && !ReportActionsUtils.isDeletedAction(parentReportAction); - let canDeleteRequest = canModifyRequest; + const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); + const canHoldOrUnholdRequest = !isSettled && !isApproved && !isDeletedParentAction; - if (ReportUtils.isPaidGroupPolicyExpenseReport(moneyRequestReport)) { - // If it's a paid policy expense report, only allow deleting the request if it's in draft state or instantly submitted state or the user is the policy admin - canDeleteRequest = - canDeleteRequest && - (ReportUtils.isDraftExpenseReport(moneyRequestReport) || ReportUtils.isExpenseReportWithInstantSubmittedState(moneyRequestReport) || PolicyUtils.isPolicyAdmin(policy)); - } + // If the report supports adding transactions to it, then it also supports deleting transactions from it. + const canDeleteRequest = isActionOwner && ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) && !isDeletedParentAction; const changeMoneyRequestStatus = () => { if (isOnHold) { @@ -108,7 +102,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, }, [canDeleteRequest]); const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(report)]; - if (isRequestModifiable) { + if (canHoldOrUnholdRequest) { const isRequestIOU = parentReport?.type === 'iou'; const isHoldCreator = ReportUtils.isHoldCreator(transaction, report?.reportID) && isRequestIOU; const canModifyStatus = isPolicyAdmin || isActionOwner || isApprover; diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 74a480a2eff7..968e1dfbfdca 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -1,6 +1,7 @@ import {useIsFocused} from '@react-navigation/native'; import {format} from 'date-fns'; import Str from 'expensify-common/lib/str'; +import {isUndefined} from 'lodash'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; @@ -245,11 +246,12 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const theme = useTheme(); const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); - const {canUseViolations} = usePermissions(); + const {canUseP2PDistanceRequests, canUseViolations} = usePermissions(); const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST; const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; const isTypeSend = iouType === CONST.IOU.TYPE.SEND; + const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isTypeSplit); const {unit, rate, currency} = mileageRate; const distance = lodashGet(transaction, 'routes.route0.distance', 0); @@ -490,6 +492,31 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ IOU.setMoneyRequestMerchant(transaction.transactionID, distanceMerchant, true); }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transaction]); + // Auto select the category if there is only one enabled category and it is required + useEffect(() => { + const enabledCategories = _.filter(policyCategories, (category) => category.enabled); + if (iouCategory || !shouldShowCategories || enabledCategories.length !== 1 || !isCategoryRequired) { + return; + } + IOU.setMoneyRequestCategory(transaction.transactionID, enabledCategories[0].name); + }, [iouCategory, shouldShowCategories, policyCategories, transaction, isCategoryRequired]); + + // Auto select the tag if there is only one enabled tag and it is required + useEffect(() => { + let updatedTagsString = TransactionUtils.getTag(transaction); + policyTagLists.forEach((tagList, index) => { + const enabledTags = _.filter(tagList.tags, (tag) => tag.enabled); + const isTagListRequired = isUndefined(tagList.required) ? false : tagList.required && canUseViolations; + if (!isTagListRequired || enabledTags.length !== 1 || TransactionUtils.getTag(transaction, index)) { + return; + } + updatedTagsString = IOUUtils.insertTagIntoTransactionTagsString(updatedTagsString, enabledTags[0] ? enabledTags[0].name : '', index); + }); + if (updatedTagsString !== TransactionUtils.getTag(transaction) && updatedTagsString) { + IOU.setMoneyRequestTag(transaction.transactionID, updatedTagsString); + } + }, [policyTagLists, transaction, policyTags, isTagRequired, canUseViolations]); + /** * @param {Object} option */ @@ -689,13 +716,14 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ item: ( Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} - disabled={didConfirm || !isTypeRequest} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + disabled={didConfirm || !canEditDistance} interactive={!isReadOnly} /> ), diff --git a/src/components/OnyxProvider.tsx b/src/components/OnyxProvider.tsx index d14aec90fa10..0bc9130ea4a8 100644 --- a/src/components/OnyxProvider.tsx +++ b/src/components/OnyxProvider.tsx @@ -8,8 +8,8 @@ import createOnyxContext from './createOnyxContext'; const [withNetwork, NetworkProvider, NetworkContext] = createOnyxContext(ONYXKEYS.NETWORK); const [withPersonalDetails, PersonalDetailsProvider, , usePersonalDetails] = createOnyxContext(ONYXKEYS.PERSONAL_DETAILS_LIST); const [withCurrentDate, CurrentDateProvider] = createOnyxContext(ONYXKEYS.CURRENT_DATE); -const [withReportActionsDrafts, ReportActionsDraftsProvider] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS); -const [withBlockedFromConcierge, BlockedFromConciergeProvider] = createOnyxContext(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); +const [withReportActionsDrafts, ReportActionsDraftsProvider, , useReportActionsDrafts] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS); +const [withBlockedFromConcierge, BlockedFromConciergeProvider, , useBlockedFromConcierge] = createOnyxContext(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); const [withBetas, BetasProvider, BetasContext, useBetas] = createOnyxContext(ONYXKEYS.BETAS); const [withReportCommentDrafts, ReportCommentDraftsProvider] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); const [withPreferredTheme, PreferredThemeProvider, PreferredThemeContext] = createOnyxContext(ONYXKEYS.PREFERRED_THEME); @@ -66,5 +66,7 @@ export { useFrequentlyUsedEmojis, withPreferredEmojiSkinTone, PreferredEmojiSkinToneContext, + useBlockedFromConcierge, + useReportActionsDrafts, useSession, }; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index c0258f1252ef..40fb1115ac36 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -1,7 +1,7 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import _ from 'underscore'; import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager'; import Button from '@components/Button'; @@ -9,6 +9,7 @@ import FixedFooter from '@components/FixedFooter'; import FormHelpMessage from '@components/FormHelpMessage'; import OptionsList from '@components/OptionsList'; import ReferralProgramCTA from '@components/ReferralProgramCTA'; +import ScrollView from '@components/ScrollView'; import ShowMoreButton from '@components/ShowMoreButton'; import TextInput from '@components/TextInput'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; diff --git a/src/components/PDFView/PDFPasswordForm.js b/src/components/PDFView/PDFPasswordForm.js index 10596bb9faf9..97d893b511dd 100644 --- a/src/components/PDFView/PDFPasswordForm.js +++ b/src/components/PDFView/PDFPasswordForm.js @@ -1,8 +1,9 @@ import PropTypes from 'prop-types'; import React, {useEffect, useMemo, useRef, useState} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import _ from 'underscore'; import Button from '@components/Button'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/components/Picker/BasePicker.tsx b/src/components/Picker/BasePicker.tsx index 1bee95532104..c86d3b71c1d9 100644 --- a/src/components/Picker/BasePicker.tsx +++ b/src/components/Picker/BasePicker.tsx @@ -1,6 +1,7 @@ import lodashDefer from 'lodash/defer'; import type {ForwardedRef, ReactElement, ReactNode, RefObject} from 'react'; import React, {forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; +// eslint-disable-next-line no-restricted-imports import type {ScrollView} from 'react-native'; import {View} from 'react-native'; import RNPickerSelect from 'react-native-picker-select'; diff --git a/src/components/Popover/types.ts b/src/components/Popover/types.ts index e06037f47b63..314c1ba141c3 100644 --- a/src/components/Popover/types.ts +++ b/src/components/Popover/types.ts @@ -1,5 +1,6 @@ import type {RefObject} from 'react'; -import type {View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {Text, View} from 'react-native'; import type {PopoverAnchorPosition} from '@components/Modal/types'; import type BaseModalProps from '@components/Modal/types'; import type {WindowDimensionsProps} from '@components/withWindowDimensions/types'; @@ -20,7 +21,7 @@ type PopoverProps = BaseModalProps & anchorAlignment?: AnchorAlignment; /** The anchor ref of the popover */ - anchorRef: RefObject; + anchorRef: RefObject; /** Whether disable the animations */ disableAnimation?: boolean; diff --git a/src/components/PopoverProvider/index.tsx b/src/components/PopoverProvider/index.tsx index 67481b41d50b..cc6c84477525 100644 --- a/src/components/PopoverProvider/index.tsx +++ b/src/components/PopoverProvider/index.tsx @@ -1,6 +1,7 @@ import type {RefObject} from 'react'; import React, {createContext, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import type {View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {Text, View} from 'react-native'; import type {AnchorRef, PopoverContextProps, PopoverContextValue} from './types'; const PopoverContext = createContext({ @@ -10,7 +11,7 @@ const PopoverContext = createContext({ isOpen: false, }); -function elementContains(ref: RefObject | undefined, target: EventTarget | null) { +function elementContains(ref: RefObject | undefined, target: EventTarget | null) { if (ref?.current && 'contains' in ref.current && ref?.current?.contains(target as Node)) { return true; } @@ -21,7 +22,7 @@ function PopoverContextProvider(props: PopoverContextProps) { const [isOpen, setIsOpen] = useState(false); const activePopoverRef = useRef(null); - const closePopover = useCallback((anchorRef?: RefObject): boolean => { + const closePopover = useCallback((anchorRef?: RefObject): boolean => { if (!activePopoverRef.current || (anchorRef && anchorRef !== activePopoverRef.current.anchorRef)) { return false; } diff --git a/src/components/PopoverProvider/types.ts b/src/components/PopoverProvider/types.ts index 2a366ae2a712..5022aee0f843 100644 --- a/src/components/PopoverProvider/types.ts +++ b/src/components/PopoverProvider/types.ts @@ -1,5 +1,6 @@ import type {ReactNode, RefObject} from 'react'; -import type {View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {Text, View} from 'react-native'; type PopoverContextProps = { children: ReactNode; @@ -8,14 +9,14 @@ type PopoverContextProps = { type PopoverContextValue = { onOpen?: (popoverParams: AnchorRef) => void; popover?: AnchorRef | Record | null; - close: (anchorRef?: RefObject) => void; + close: (anchorRef?: RefObject) => void; isOpen: boolean; }; type AnchorRef = { - ref: RefObject; - close: (anchorRef?: RefObject) => void; - anchorRef: RefObject; + ref: RefObject; + close: (anchorRef?: RefObject) => void; + anchorRef: RefObject; }; export type {PopoverContextProps, PopoverContextValue, AnchorRef}; diff --git a/src/components/PopoverWithoutOverlay/types.ts b/src/components/PopoverWithoutOverlay/types.ts index 0d24cdd4bd9f..8fe40119ca61 100644 --- a/src/components/PopoverWithoutOverlay/types.ts +++ b/src/components/PopoverWithoutOverlay/types.ts @@ -1,5 +1,6 @@ import type {RefObject} from 'react'; -import type {View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {Text, View} from 'react-native'; import type BaseModalProps from '@components/Modal/types'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; @@ -14,7 +15,7 @@ type PopoverWithoutOverlayProps = ChildrenProps & }; /** The anchor ref of the popover */ - anchorRef: RefObject; + anchorRef: RefObject; /** A react-native-animatable animation timing for the modal display animation */ animationInTiming?: number; diff --git a/src/components/Pressable/GenericPressable/types.ts b/src/components/Pressable/GenericPressable/types.ts index 2dd2e17e0454..9040a844e5a7 100644 --- a/src/components/Pressable/GenericPressable/types.ts +++ b/src/components/Pressable/GenericPressable/types.ts @@ -1,5 +1,6 @@ import type {ElementRef, ForwardedRef, RefObject} from 'react'; -import type {GestureResponderEvent, HostComponent, PressableStateCallbackType, PressableProps as RNPressableProps, StyleProp, View, ViewStyle} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {GestureResponderEvent, HostComponent, PressableStateCallbackType, PressableProps as RNPressableProps, Text as RNText, StyleProp, View, ViewStyle} from 'react-native'; import type {ValueOf} from 'type-fest'; import type {Shortcut} from '@libs/KeyboardShortcut'; import type CONST from '@src/CONST'; @@ -138,7 +139,7 @@ type PressableProps = RNPressableProps & noDragArea?: boolean; }; -type PressableRef = ForwardedRef; +type PressableRef = ForwardedRef; export default PressableProps; export type {PressableRef}; diff --git a/src/components/PressableWithSecondaryInteraction/types.ts b/src/components/PressableWithSecondaryInteraction/types.ts index aa67d45d66fb..b07c867daeb3 100644 --- a/src/components/PressableWithSecondaryInteraction/types.ts +++ b/src/components/PressableWithSecondaryInteraction/types.ts @@ -4,7 +4,7 @@ import type {ParsableStyle} from '@styles/utils/types'; type PressableWithSecondaryInteractionProps = PressableWithFeedbackProps & { /** The function that should be called when this pressable is pressed */ - onPress: (event?: GestureResponderEvent) => void; + onPress?: (event?: GestureResponderEvent) => void; /** The function that should be called when this pressable is pressedIn */ onPressIn?: (event?: GestureResponderEvent) => void; diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.tsx b/src/components/Reactions/ReportActionItemEmojiReactions.tsx index 7e95ab670b7e..c6bf4f9e4016 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.tsx +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -23,7 +23,7 @@ type ReportActionItemEmojiReactionsProps = WithCurrentUserPersonalDetailsProps & emojiReactions: OnyxEntry; /** The user's preferred locale. */ - preferredLocale: OnyxEntry; + preferredLocale?: OnyxEntry; /** The report action that these reactions are for */ reportAction: ReportAction; @@ -155,7 +155,7 @@ function ReportActionItemEmojiReactions({ shouldDisableOpacity={!!reportAction.pendingAction} > (popoverReactionListAnchors.current[reaction.reactionEmojiName] = ref)} + ref={(ref) => (popoverReactionListAnchors.current[reaction.reactionEmojiName] = ref ?? null)} count={reaction.reactionCount} emojiCodes={reaction.emojiCodes} onPress={reaction.onPress} diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index f0cd8dc1b4b5..60dbfc07966a 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -2,6 +2,7 @@ import Str from 'expensify-common/lib/str'; import React, {useMemo} from 'react'; import type {StyleProp, TextStyle} from 'react-native'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -26,7 +27,7 @@ type MoneyReportViewProps = { report: Report; /** Policy that the report belongs to */ - policy: Policy; + policy: OnyxEntry; /** Policy report fields */ policyReportFields: PolicyReportField[]; @@ -67,107 +68,111 @@ function MoneyReportView({report, policy, policyReportFields, shouldShowHorizont - {ReportUtils.reportFieldsEnabled(report) && - sortedPolicyReportFields.map((reportField) => { - const isTitleField = ReportUtils.isReportFieldOfTypeTitle(reportField); - const fieldValue = isTitleField ? report.reportName : reportField.value ?? reportField.defaultValue; - const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, reportField, policy); - - return ( - - Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))} - shouldShowRightIcon - disabled={isFieldDisabled} - wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} - shouldGreyOutWhenDisabled={false} - numberOfLinesTitle={0} - interactive - shouldStackHorizontally={false} - onSecondaryInteraction={() => {}} - hoverAndPressStyle={false} - titleWithTooltips={[]} - /> - - ); - })} - - - - {translate('common.total')} - - - - {isSettled && ( - - - - )} - - {formattedTotalAmount} - - - - {Boolean(shouldShowBreakdown) && ( + {!ReportUtils.isClosedExpenseReportWithNoExpenses(report) && ( <> - - - - {translate('cardTransactions.outOfPocket')} - - - - - {formattedOutOfPocketAmount} - - - - + {ReportUtils.reportFieldsEnabled(report) && + sortedPolicyReportFields.map((reportField) => { + const isTitleField = ReportUtils.isReportFieldOfTypeTitle(reportField); + const fieldValue = isTitleField ? report.reportName : reportField.value ?? reportField.defaultValue; + const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, reportField, policy); + + return ( + + Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))} + shouldShowRightIcon + disabled={isFieldDisabled} + wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} + shouldGreyOutWhenDisabled={false} + numberOfLinesTitle={0} + interactive + shouldStackHorizontally={false} + onSecondaryInteraction={() => {}} + hoverAndPressStyle={false} + titleWithTooltips={[]} + /> + + ); + })} + - {translate('cardTransactions.companySpend')} + {translate('common.total')} + {isSettled && ( + + + + )} - {formattedCompanySpendAmount} + {formattedTotalAmount} + {Boolean(shouldShowBreakdown) && ( + <> + + + + {translate('cardTransactions.outOfPocket')} + + + + + {formattedOutOfPocketAmount} + + + + + + + {translate('cardTransactions.companySpend')} + + + + + {formattedCompanySpendAmount} + + + + + )} + )} - ); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 34d039153de7..1d5d443d3761 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -24,7 +24,6 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; @@ -474,8 +473,8 @@ export default withOnyx { - const parentReportAction = ReportActionsUtils.getParentReportAction(report); + key: ({report, parentReportActions}) => { + const parentReportAction = parentReportActions?.[report.parentReportActionID ?? '']; const originalMessage = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage : undefined; const transactionID = originalMessage?.IOUTransactionID ?? 0; return `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`; diff --git a/src/components/ReportActionItem/TaskAction.tsx b/src/components/ReportActionItem/TaskAction.tsx index b10be4e86fe8..7e9262bb4c05 100644 --- a/src/components/ReportActionItem/TaskAction.tsx +++ b/src/components/ReportActionItem/TaskAction.tsx @@ -1,20 +1,24 @@ import React from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import RenderHTML from '@components/RenderHTML'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; import * as TaskUtils from '@libs/TaskUtils'; +import type {ReportAction} from '@src/types/onyx'; type TaskActionProps = { /** Name of the reportAction action */ - actionName: string; + action: OnyxEntry; }; -function TaskAction({actionName}: TaskActionProps) { +function TaskAction({action}: TaskActionProps) { const styles = useThemeStyles(); + const message = TaskUtils.getTaskReportActionMessage(action); return ( - {TaskUtils.getTaskReportActionMessage(actionName)} + {message.html ? ${message.html}`} /> : {message.text}} ); } diff --git a/src/components/ScrollView.tsx b/src/components/ScrollView.tsx new file mode 100644 index 000000000000..a61c592015ee --- /dev/null +++ b/src/components/ScrollView.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import type {ForwardedRef} from 'react'; +// eslint-disable-next-line no-restricted-imports +import {ScrollView as RNScrollView} from 'react-native'; +import type {ScrollViewProps} from 'react-native'; + +function ScrollView({children, scrollIndicatorInsets, ...props}: ScrollViewProps, ref: ForwardedRef) { + return ( + + {children} + + ); +} + +ScrollView.displayName = 'ScrollView'; + +export type {ScrollViewProps}; + +export default React.forwardRef(ScrollView); diff --git a/src/components/ScrollViewWithContext.tsx b/src/components/ScrollViewWithContext.tsx index 1ac53651a542..1b9bb2c09f56 100644 --- a/src/components/ScrollViewWithContext.tsx +++ b/src/components/ScrollViewWithContext.tsx @@ -1,13 +1,14 @@ import type {ForwardedRef, ReactNode} from 'react'; import React, {createContext, forwardRef, useMemo, useRef, useState} from 'react'; -import type {NativeScrollEvent, NativeSyntheticEvent, ScrollViewProps} from 'react-native'; -import {ScrollView} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {NativeScrollEvent, NativeSyntheticEvent, ScrollView as RNScrollView, ScrollViewProps} from 'react-native'; +import ScrollView from './ScrollView'; const MIN_SMOOTH_SCROLL_EVENT_THROTTLE = 16; type ScrollContextValue = { contentOffsetY: number; - scrollViewRef: ForwardedRef; + scrollViewRef: ForwardedRef; }; const ScrollContext = createContext({ @@ -28,9 +29,9 @@ type ScrollViewWithContextProps = Partial & { * Using this wrapper will automatically handle scrolling to the picker's * when the picker modal is opened */ -function ScrollViewWithContext({onScroll, scrollEventThrottle, children, ...restProps}: ScrollViewWithContextProps, ref: ForwardedRef) { +function ScrollViewWithContext({onScroll, scrollEventThrottle, children, ...restProps}: ScrollViewWithContextProps, ref: ForwardedRef) { const [contentOffsetY, setContentOffsetY] = useState(0); - const defaultScrollViewRef = useRef(null); + const defaultScrollViewRef = useRef(null); const scrollViewRef = ref ?? defaultScrollViewRef; const setContextScrollPosition = (event: NativeSyntheticEvent) => { diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index 23d0bb6f816b..92d829e9d0db 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -82,7 +82,7 @@ function BaseVideoPlayer({ setIsPopoverVisible(false); }; - // fix for iOS mWeb: preventing iOS native player edfault behavior from pausing the video when exiting fullscreen + // fix for iOS mWeb: preventing iOS native player default behavior from pausing the video when exiting fullscreen const preventPausingWhenExitingFullscreen = useCallback( (isVideoPlaying) => { if (videoResumeTryNumber.current === 0 || isVideoPlaying) { @@ -121,6 +121,7 @@ function BaseVideoPlayer({ const handleFullscreenUpdate = useCallback( (e) => { onFullscreenUpdate(e); + // fix for iOS native and mWeb: when switching to fullscreen and then exiting // the fullscreen mode while playing, the video pauses if (!isPlaying || e.fullscreenUpdate !== VideoFullscreenUpdate.PLAYER_DID_DISMISS) { @@ -139,7 +140,8 @@ function BaseVideoPlayer({ const bindFunctions = useCallback(() => { currentVideoPlayerRef.current._onPlaybackStatusUpdate = handlePlaybackStatusUpdate; currentVideoPlayerRef.current._onFullscreenUpdate = handleFullscreenUpdate; - // update states after binding + + // Update states after binding currentVideoPlayerRef.current.getStatusAsync().then((status) => { handlePlaybackStatusUpdate(status); }); @@ -149,6 +151,7 @@ function BaseVideoPlayer({ if (!isUploading) { return; } + // If we are uploading a new video, we want to immediately set the video player ref. currentVideoPlayerRef.current = videoPlayerRef.current; }, [url, currentVideoPlayerRef, isUploading]); @@ -162,6 +165,7 @@ function BaseVideoPlayer({ if (shouldUseSharedVideoElementRef.current) { return; } + // If it's not a shared video player, clear the video player ref. currentVideoPlayerRef.current = null; }, diff --git a/src/components/createOnyxContext.tsx b/src/components/createOnyxContext.tsx index 50cda00b17b4..c19b8006c86c 100644 --- a/src/components/createOnyxContext.tsx +++ b/src/components/createOnyxContext.tsx @@ -3,7 +3,7 @@ import type {ComponentType, ForwardedRef, ForwardRefExoticComponent, PropsWithou import React, {createContext, forwardRef, useContext} from 'react'; import {withOnyx} from 'react-native-onyx'; import getComponentDisplayName from '@libs/getComponentDisplayName'; -import type {OnyxKey, OnyxValue, OnyxValues} from '@src/ONYXKEYS'; +import type {OnyxKey, OnyxValue} from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; // Provider types @@ -32,11 +32,11 @@ type CreateOnyxContext = [ WithOnyxKey, ComponentType, TOnyxKey>>, React.Context>, - () => OnyxValues[TOnyxKey], + () => NonNullable>, ]; export default (onyxKeyName: TOnyxKey): CreateOnyxContext => { - const Context = createContext>(null); + const Context = createContext>(null as OnyxValue); function Provider(props: ProviderPropsWithOnyx): ReactNode { return {props.children}; } @@ -86,7 +86,7 @@ export default (onyxKeyName: TOnyxKey): CreateOnyxCont if (value === null) { throw new Error(`useOnyxContext must be used within a OnyxProvider [key: ${onyxKeyName}]`); } - return value; + return value as NonNullable>; }; return [withOnyxKey, ProviderWithOnyx, Context, useOnyxContext]; diff --git a/src/languages/en.ts b/src/languages/en.ts index 0a52cca62ef5..3575854ee7e2 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1754,6 +1754,7 @@ export default { workspaceType: 'Workspace type', workspaceAvatar: 'Workspace avatar', mustBeOnlineToViewMembers: 'You must be online in order to view members of this workspace.', + requested: 'Requested', }, type: { free: 'Free', @@ -1770,6 +1771,10 @@ export default { subtitle: 'Add a category to organize your spend.', }, genericFailureMessage: 'An error occurred while updating the category, please try again.', + addCategory: 'Add category', + categoryRequiredError: 'Category name is required.', + existingCategoryError: 'A category with this name already exists.', + invalidCategoryName: 'Invalid category name.', }, tags: { requiresTag: 'Members must tag all spend', @@ -1805,6 +1810,9 @@ export default { genericFailureMessage: 'An error occurred removing a user from the workspace, please try again.', removeMembersPrompt: 'Are you sure you want to remove these members?', removeMembersTitle: 'Remove members', + removeMemberButtonTitle: 'Remove from workspace', + removeMemberPrompt: ({memberName}) => `Are you sure you want to remove ${memberName}`, + removeMemberTitle: 'Remove member', makeMember: 'Make member', makeAdmin: 'Make admin', selectAll: 'Select all', @@ -2196,6 +2204,7 @@ export default { viewAttachment: 'View attachment', }, parentReportAction: { + deletedReport: '[Deleted report]', deletedMessage: '[Deleted message]', deletedRequest: '[Deleted request]', reversedTransaction: '[Reversed transaction]', @@ -2239,6 +2248,10 @@ export default { invite: 'Invite them', nothing: 'Do nothing', }, + actionableMentionJoinWorkspaceOptions: { + accept: 'Accept', + decline: 'Decline', + }, teachersUnitePage: { teachersUnite: 'Teachers Unite', joinExpensifyOrg: 'Join Expensify.org in eliminating injustice around the world and help teachers split their expenses for classrooms in need!', diff --git a/src/languages/es.ts b/src/languages/es.ts index 013255c1e11e..51a83e55fee2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1778,6 +1778,7 @@ export default { workspaceType: 'Tipo de espacio de trabajo', workspaceAvatar: 'Espacio de trabajo avatar', mustBeOnlineToViewMembers: 'Debes estar en línea para poder ver los miembros de este espacio de trabajo.', + requested: 'Solicitado', }, type: { free: 'Gratis', @@ -1794,6 +1795,10 @@ export default { subtitle: 'Añade una categoría para organizar tu gasto.', }, genericFailureMessage: 'Se ha producido un error al intentar eliminar la categoría. Por favor, inténtalo más tarde.', + addCategory: 'Añadir categoría', + categoryRequiredError: 'Lo nombre de la categoría es obligatorio.', + existingCategoryError: 'Ya existe una categoría con este nombre.', + invalidCategoryName: 'Lo nombre de la categoría es invalido.', }, tags: { requiresTag: 'Los miembros deben etiquetar todos los gastos', @@ -1829,6 +1834,9 @@ export default { genericFailureMessage: 'Se ha producido un error al intentar eliminar a un usuario del espacio de trabajo. Por favor, inténtalo más tarde.', removeMembersPrompt: '¿Estás seguro de que deseas eliminar a estos miembros?', removeMembersTitle: 'Eliminar miembros', + removeMemberButtonTitle: 'Quitar del espacio de trabajo', + removeMemberPrompt: ({memberName}) => `¿Estás seguro de que deseas eliminar a ${memberName}`, + removeMemberTitle: 'Eliminar miembro', makeMember: 'Hacer miembro', makeAdmin: 'Hacer administrador', selectAll: 'Seleccionar todo', @@ -2684,6 +2692,7 @@ export default { viewAttachment: 'Ver archivo adjunto', }, parentReportAction: { + deletedReport: '[Informe eliminado]', deletedMessage: '[Mensaje eliminado]', deletedRequest: '[Solicitud eliminada]', reversedTransaction: '[Transacción anulada]', @@ -2705,6 +2714,10 @@ export default { invite: 'Invitar', nothing: 'No hacer nada', }, + actionableMentionJoinWorkspaceOptions: { + accept: 'Aceptar', + decline: 'Rechazar', + }, moderation: { flagDescription: 'Todos los mensajes marcados se enviarán a un moderador para su revisión.', chooseAReason: 'Elige abajo un motivo para reportarlo:', diff --git a/src/libs/API/parameters/AcceptJoinRequest.ts b/src/libs/API/parameters/AcceptJoinRequest.ts new file mode 100644 index 000000000000..4c7b6a00b2fb --- /dev/null +++ b/src/libs/API/parameters/AcceptJoinRequest.ts @@ -0,0 +1,5 @@ +type AcceptJoinRequestParams = { + requests: string; +}; + +export default AcceptJoinRequestParams; diff --git a/src/libs/API/parameters/CreateWorkspaceCategoriesParams.ts b/src/libs/API/parameters/CreateWorkspaceCategoriesParams.ts new file mode 100644 index 000000000000..629a66c2e657 --- /dev/null +++ b/src/libs/API/parameters/CreateWorkspaceCategoriesParams.ts @@ -0,0 +1,10 @@ +type CreateWorkspaceCategoriesParams = { + policyID: string; + /** + * Stringified JSON object with type of following structure: + * Array<{name: string;}> + */ + categories: string; +}; + +export default CreateWorkspaceCategoriesParams; diff --git a/src/libs/API/parameters/DeclineJoinRequest.ts b/src/libs/API/parameters/DeclineJoinRequest.ts new file mode 100644 index 000000000000..da0b147254d8 --- /dev/null +++ b/src/libs/API/parameters/DeclineJoinRequest.ts @@ -0,0 +1,5 @@ +type DeclineJoinRequestParams = { + requests: string; +}; + +export default DeclineJoinRequestParams; diff --git a/src/libs/API/parameters/JoinPolicyInviteLink.ts b/src/libs/API/parameters/JoinPolicyInviteLink.ts new file mode 100644 index 000000000000..4b280b8cd8c6 --- /dev/null +++ b/src/libs/API/parameters/JoinPolicyInviteLink.ts @@ -0,0 +1,6 @@ +type JoinPolicyInviteLinkParams = { + policyID: string; + inviterEmail: string; +}; + +export default JoinPolicyInviteLinkParams; diff --git a/src/libs/API/parameters/PayMoneyRequestParams.ts b/src/libs/API/parameters/PayMoneyRequestParams.ts index edf05b6ce528..4a769f057e10 100644 --- a/src/libs/API/parameters/PayMoneyRequestParams.ts +++ b/src/libs/API/parameters/PayMoneyRequestParams.ts @@ -5,6 +5,7 @@ type PayMoneyRequestParams = { chatReportID: string; reportActionID: string; paymentMethodType: PaymentMethodType; + amount?: number; }; export default PayMoneyRequestParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 00e8b5e761ad..f529032130bb 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -149,9 +149,13 @@ export type {default as AcceptACHContractForBankAccount} from './AcceptACHContra export type {default as UpdateWorkspaceDescriptionParams} from './UpdateWorkspaceDescriptionParams'; export type {default as UpdateWorkspaceMembersRoleParams} from './UpdateWorkspaceMembersRoleParams'; export type {default as SetWorkspaceCategoriesEnabledParams} from './SetWorkspaceCategoriesEnabledParams'; +export type {default as CreateWorkspaceCategoriesParams} from './CreateWorkspaceCategoriesParams'; export type {default as SetWorkspaceRequiresCategoryParams} from './SetWorkspaceRequiresCategoryParams'; export type {default as SetWorkspaceAutoReportingParams} from './SetWorkspaceAutoReportingParams'; export type {default as SetWorkspaceAutoReportingFrequencyParams} from './SetWorkspaceAutoReportingFrequencyParams'; export type {default as SetWorkspaceAutoReportingMonthlyOffsetParams} from './SetWorkspaceAutoReportingMonthlyOffsetParams'; export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams'; export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams'; +export type {default as AcceptJoinRequestParams} from './AcceptJoinRequest'; +export type {default as DeclineJoinRequestParams} from './DeclineJoinRequest'; +export type {default as JoinPolicyInviteLinkParams} from './JoinPolicyInviteLink'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index ee4ce1ea3670..1b41ced4f1d7 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -115,6 +115,7 @@ const WRITE_COMMANDS = { CREATE_WORKSPACE: 'CreateWorkspace', CREATE_WORKSPACE_FROM_IOU_PAYMENT: 'CreateWorkspaceFromIOUPayment', SET_WORKSPACE_CATEGORIES_ENABLED: 'SetWorkspaceCategoriesEnabled', + CREATE_WORKSPACE_CATEGORIES: 'CreateWorkspaceCategories', SET_WORKSPACE_REQUIRES_CATEGORY: 'SetWorkspaceRequiresCategory', CREATE_TASK: 'CreateTask', CANCEL_TASK: 'CancelTask', @@ -156,6 +157,9 @@ const WRITE_COMMANDS = { CANCEL_PAYMENT: 'CancelPayment', ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT: 'AcceptACHContractForBankAccount', SWITCH_TO_OLD_DOT: 'SwitchToOldDot', + JOIN_POLICY_VIA_INVITE_LINK: 'JoinWorkspaceViaInviteLink', + ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest', + DECLINE_JOIN_REQUEST: 'DeclineJoinRequest', } as const; type WriteCommand = ValueOf; @@ -264,6 +268,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.CREATE_WORKSPACE]: Parameters.CreateWorkspaceParams; [WRITE_COMMANDS.CREATE_WORKSPACE_FROM_IOU_PAYMENT]: Parameters.CreateWorkspaceFromIOUPaymentParams; [WRITE_COMMANDS.SET_WORKSPACE_CATEGORIES_ENABLED]: Parameters.SetWorkspaceCategoriesEnabledParams; + [WRITE_COMMANDS.CREATE_WORKSPACE_CATEGORIES]: Parameters.CreateWorkspaceCategoriesParams; [WRITE_COMMANDS.SET_WORKSPACE_REQUIRES_CATEGORY]: Parameters.SetWorkspaceRequiresCategoryParams; [WRITE_COMMANDS.CREATE_TASK]: Parameters.CreateTaskParams; [WRITE_COMMANDS.CANCEL_TASK]: Parameters.CancelTaskParams; @@ -310,6 +315,9 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET]: Parameters.SetWorkspaceAutoReportingMonthlyOffsetParams; [WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; + [WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams; + [WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams; + [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; }; const READ_COMMANDS = { @@ -386,6 +394,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = { OPEN_OLD_DOT_LINK: 'OpenOldDotLink', REVEAL_EXPENSIFY_CARD_DETAILS: 'RevealExpensifyCardDetails', GET_MISSING_ONYX_MESSAGES: 'GetMissingOnyxMessages', + JOIN_POLICY_VIA_INVITE_LINK: 'JoinWorkspaceViaInviteLink', RECONNECT_APP: 'ReconnectApp', } as const; @@ -397,6 +406,7 @@ type SideEffectRequestCommandParameters = { [SIDE_EFFECT_REQUEST_COMMANDS.OPEN_OLD_DOT_LINK]: Parameters.OpenOldDotLinkParams; [SIDE_EFFECT_REQUEST_COMMANDS.REVEAL_EXPENSIFY_CARD_DETAILS]: Parameters.RevealExpensifyCardDetailsParams; [SIDE_EFFECT_REQUEST_COMMANDS.GET_MISSING_ONYX_MESSAGES]: Parameters.GetMissingOnyxMessagesParams; + [SIDE_EFFECT_REQUEST_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams; [SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP]: Parameters.ReconnectAppParams; }; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index a42cb6a8f756..aef615018b4c 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -7,6 +7,7 @@ import * as CurrencyUtils from './CurrencyUtils'; import * as PolicyUtils from './PolicyUtils'; type DefaultMileageRate = { + customUnitRateID?: string; rate?: number; currency?: string; unit: Unit; @@ -38,6 +39,7 @@ function getDefaultMileageRate(policy: OnyxEntry): DefaultMileageRate | } return { + customUnitRateID: distanceRate.customUnitRateID, rate: distanceRate.rate, currency: distanceRate.currency, unit: distanceUnit.attributes.unit, @@ -76,6 +78,27 @@ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string return convertedDistance.toFixed(2); } +/** + * @param hasRoute Whether the route exists for the distance request + * @param distanceInMeters Distance traveled + * @param unit Unit that should be used to display the distance + * @param rate Expensable amount allowed per unit + * @param translate Translate function + * @returns A string that describes the distance traveled + */ +function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, rate: number, translate: LocaleContextProps['translate']): string { + if (!hasRoute || !rate) { + return translate('iou.routePending'); + } + + const distanceInUnits = getRoundedDistanceInUnits(distanceInMeters, unit); + const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); + const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); + const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; + + return `${distanceInUnits} ${unitString}`; +} + /** * @param hasRoute Whether the route exists for the distance request * @param distanceInMeters Distance traveled @@ -99,15 +122,13 @@ function getDistanceMerchant( return translate('iou.routePending'); } - const distanceInUnits = getRoundedDistanceInUnits(distanceInMeters, unit); - const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); + const formattedDistance = getDistanceForDisplay(hasRoute, distanceInMeters, unit, rate, translate); const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); - const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; const ratePerUnit = PolicyUtils.getUnitRateValue({rate}, toLocaleDigit); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const currencySymbol = CurrencyUtils.getCurrencySymbol(currency) || `${currency} `; - return `${distanceInUnits} ${unitString} @ ${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; + return `${formattedDistance} @ ${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; } /** diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index cab0f48d75fd..33cda171f24b 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -242,9 +242,13 @@ function getFrequentlyUsedEmojis(newEmoji: Emoji | Emoji[]): FrequentlyUsedEmoji /** * Given an emoji item object, return an emoji code based on its type. */ -const getEmojiCodeWithSkinColor = (item: Emoji, preferredSkinToneIndex: number): string => { +const getEmojiCodeWithSkinColor = (item: Emoji, preferredSkinToneIndex: OnyxEntry): string | undefined => { const {code, types} = item; - if (types?.[preferredSkinToneIndex]) { + if (!preferredSkinToneIndex) { + return; + } + + if (typeof preferredSkinToneIndex === 'number' && types?.[preferredSkinToneIndex]) { return types[preferredSkinToneIndex]; } @@ -305,7 +309,7 @@ function getAddedEmojis(currentEmojis: Emoji[], formerEmojis: Emoji[]): Emoji[] * Replace any emoji name in a text with the emoji icon. * If we're on mobile, we also add a space after the emoji granted there's no text after it. */ -function replaceEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji { +function replaceEmojis(text: string, preferredSkinTone: OnyxEntry = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji { // emojisTrie is importing the emoji JSON file on the app starting and we want to avoid it const emojisTrie = require('./EmojiTrie').default; @@ -345,9 +349,9 @@ function replaceEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEF // Set the cursor to the end of the last replaced Emoji. Note that we position after // the extra space, if we added one. - cursorPosition = newText.indexOf(emoji) + emojiReplacement.length; + cursorPosition = newText.indexOf(emoji) + (emojiReplacement?.length ?? 0); - newText = newText.replace(emoji, emojiReplacement); + newText = newText.replace(emoji, emojiReplacement ?? ''); } } @@ -369,7 +373,7 @@ function replaceEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEF /** * Find all emojis in a text and replace them with their code. */ -function replaceAndExtractEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji { +function replaceAndExtractEmojis(text: string, preferredSkinTone: OnyxEntry = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji { const {text: convertedText = '', emojis = [], cursorPosition} = replaceEmojis(text, preferredSkinTone, lang); return { diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 20313ee8912d..784d339a4a0d 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -180,3 +180,5 @@ export { getMicroSecondOnyxErrorObject, isReceiptError, }; + +export type {OnyxDataWithErrors}; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index fc89b53fbefd..6f5dcdf9cda9 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -63,6 +63,7 @@ const loadConciergePage = () => require('../../../pages/ConciergePage').default const loadProfileAvatar = () => require('../../../pages/settings/Profile/ProfileAvatar').default as React.ComponentType; const loadWorkspaceAvatar = () => require('../../../pages/workspace/WorkspaceAvatar').default as React.ComponentType; const loadReportAvatar = () => require('../../../pages/ReportAvatar').default as React.ComponentType; +const loadWorkspaceJoinUser = () => require('@pages/workspace/WorkspaceJoinUserPage').default as React.ComponentType; let timezone: Timezone | null; let currentAccountID = -1; @@ -356,6 +357,14 @@ function AuthScreens({session, lastOpenedPublicRoomID, isUsingMemoryOnlyKeys = f options={screenOptions.fullScreen} component={DesktopSignInRedirectPage} /> + ); diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 545641957c9a..978e338796ea 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -251,6 +251,9 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/WorkspaceProfileCurrencyPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORY_SETTINGS]: () => require('../../../pages/workspace/categories/CategorySettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require('../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.MEMBER_DETAILS]: () => require('../../../pages/workspace/members/WorkspaceMemberDetailsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: () => require('../../../pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage').default as React.ComponentType, + [SCREENS.WORKSPACE.CATEGORY_CREATE]: () => require('../../../pages/workspace/categories/CreateCategoryPage').default as React.ComponentType, [SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType, [SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType, [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default as React.ComponentType, diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index f38ec213a466..58d9efb43df5 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -11,6 +11,7 @@ import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as Session from '@libs/actions/Session'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import getTopmostBottomTabRoute from '@libs/Navigation/getTopmostBottomTabRoute'; import Navigation from '@libs/Navigation/Navigation'; @@ -47,7 +48,8 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps // When we are redirected to the Settings tab from the OldDot, we don't want to call the Welcome.show() method. // To prevent this, the value of the bottomTabRoute?.name is checked here bottomTabRoute?.name === SCREENS.WORKSPACE.INITIAL || - (currentRoute && currentRoute.name !== NAVIGATORS.BOTTOM_TAB_NAVIGATOR && currentRoute.name !== NAVIGATORS.CENTRAL_PANE_NAVIGATOR) + Boolean(currentRoute && currentRoute.name !== NAVIGATORS.BOTTOM_TAB_NAVIGATOR && currentRoute.name !== NAVIGATORS.CENTRAL_PANE_NAVIGATOR) || + Session.isAnonymousUser() ) { return; } diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 20c426a74c71..2ca4c5178a5e 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -45,7 +45,7 @@ function parseAndLogRoute(state: NavigationState) { const focusedRoute = findFocusedRoute(state); - if (focusedRoute?.name !== SCREENS.NOT_FOUND) { + if (focusedRoute?.name !== SCREENS.NOT_FOUND && focusedRoute?.name !== SCREENS.SAML_SIGN_IN) { updateLastVisitedPath(currentPath); } diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 7959999ee813..5bc7d52230a8 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -4,9 +4,9 @@ import SCREENS from '@src/SCREENS'; const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = { [SCREENS.WORKSPACE.PROFILE]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.CURRENCY, SCREENS.WORKSPACE.DESCRIPTION, SCREENS.WORKSPACE.SHARE], [SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT, SCREENS.WORKSPACE.RATE_AND_UNIT_RATE, SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT], - [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE], + [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE, SCREENS.WORKSPACE.MEMBER_DETAILS, SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION], [SCREENS.WORKSPACE.WORKFLOWS]: [SCREENS.WORKSPACE.WORKFLOWS_APPROVER, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET], - [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], + [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], }; export default CENTRAL_PANE_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 3ceb3c1ac7df..8a24dc177a80 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -22,6 +22,7 @@ const config: LinkingOptions['config'] = { [SCREENS.PROFILE_AVATAR]: ROUTES.PROFILE_AVATAR.route, [SCREENS.WORKSPACE_AVATAR]: ROUTES.WORKSPACE_AVATAR.route, [SCREENS.REPORT_AVATAR]: ROUTES.REPORT_AVATAR.route, + [SCREENS.WORKSPACE_JOIN_USER]: ROUTES.WORKSPACE_JOIN_USER.route, // Sidebar [NAVIGATORS.BOTTOM_TAB_NAVIGATOR]: { @@ -280,6 +281,15 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: { path: ROUTES.WORKSPACE_CATEGORIES_SETTINGS.route, }, + [SCREENS.WORKSPACE.MEMBER_DETAILS]: { + path: ROUTES.WORKSPACE_MEMBER_DETAILS.route, + }, + [SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: { + path: ROUTES.WORKSPACE_MEMBER_ROLE_SELECTION.route, + }, + [SCREENS.WORKSPACE.CATEGORY_CREATE]: { + path: ROUTES.WORKSPACE_CATEGORY_CREATE.route, + }, [SCREENS.REIMBURSEMENT_ACCOUNT]: { path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.route, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 6790dd5f8f10..decb905ac52f 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -93,6 +93,7 @@ type CentralPaneNavigatorParamList = { }; [SCREENS.WORKSPACE.TAGS]: { policyID: string; + categoryName: string; }; }; @@ -197,6 +198,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.INVITE_MESSAGE]: { policyID: string; }; + [SCREENS.WORKSPACE.CATEGORY_CREATE]: { + policyID: string; + }; [SCREENS.WORKSPACE.CATEGORY_SETTINGS]: { policyID: string; categoryName: string; @@ -204,6 +208,16 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: { policyID: string; }; + [SCREENS.WORKSPACE.MEMBER_DETAILS]: { + policyID: string; + accountID: string; + backTo: Routes; + }; + [SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: { + policyID: string; + accountID: string; + backTo: Routes; + }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; @@ -566,6 +580,10 @@ type AuthScreensParamList = SharedScreensParamList & { [SCREENS.WORKSPACE_AVATAR]: { policyID: string; }; + [SCREENS.WORKSPACE_JOIN_USER]: { + policyID: string; + email: string; + }; [SCREENS.REPORT_AVATAR]: { reportID: string; }; diff --git a/src/libs/Notification/PushNotification/NotificationType.ts b/src/libs/Notification/PushNotification/NotificationType.ts index d6ec246eddf7..40778f38c0d4 100644 --- a/src/libs/Notification/PushNotification/NotificationType.ts +++ b/src/libs/Notification/PushNotification/NotificationType.ts @@ -18,6 +18,8 @@ type ReportCommentNotificationData = { shouldScrollToLastUnread?: boolean; roomName?: string; onyxData?: OnyxServerUpdate[]; + lastUpdateID?: number; + previousUpdateID?: number; }; /** diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts index 813e0aecbd5c..7f86d3ddb9ac 100644 --- a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts +++ b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import * as OnyxUpdates from '@libs/actions/OnyxUpdates'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import getPolicyMemberAccountIDs from '@libs/PolicyMembersUtils'; @@ -6,8 +7,10 @@ import {extractPolicyIDFromPath} from '@libs/PolicyUtils'; import {doesReportBelongToWorkspace, getReport} from '@libs/ReportUtils'; import Visibility from '@libs/Visibility'; import * as Modal from '@userActions/Modal'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {OnyxUpdatesFromServer} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import backgroundRefresh from './backgroundRefresh'; import PushNotification from './index'; @@ -27,9 +30,28 @@ Onyx.connect({ * Setup reportComment push notification callbacks. */ export default function subscribeToReportCommentPushNotifications() { - PushNotification.onReceived(PushNotification.TYPE.REPORT_COMMENT, ({reportID, reportActionID, onyxData}) => { + PushNotification.onReceived(PushNotification.TYPE.REPORT_COMMENT, ({reportID, reportActionID, onyxData, lastUpdateID, previousUpdateID}) => { Log.info(`[PushNotification] received report comment notification in the ${Visibility.isVisible() ? 'foreground' : 'background'}`, false, {reportID, reportActionID}); - Onyx.update(onyxData ?? []); + + if (onyxData && lastUpdateID && previousUpdateID) { + Log.info('[PushNotification] reliable onyx update received', false, {lastUpdateID, previousUpdateID, onyxDataCount: onyxData?.length ?? 0}); + + const updates: OnyxUpdatesFromServer = { + type: CONST.ONYX_UPDATE_TYPES.AIRSHIP, + lastUpdateID, + previousUpdateID, + updates: [ + { + eventType: 'eventType', + data: onyxData, + }, + ], + }; + OnyxUpdates.applyOnyxUpdatesReliably(updates); + } else { + Log.hmmm("[PushNotification] Didn't apply onyx updates because some data is missing", {lastUpdateID, previousUpdateID, onyxDataCount: onyxData?.length ?? 0}); + } + backgroundRefresh(); }); diff --git a/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts b/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts index 9c7e6402d69b..82410b120df2 100644 --- a/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts +++ b/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts @@ -1,6 +1,7 @@ -import type {OnyxValue} from '@src/ONYXKEYS'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {Report} from '@src/types/onyx'; -export default function reportWithoutHasDraftSelector(report: OnyxValue<'report_'>) { +export default function reportWithoutHasDraftSelector(report: OnyxEntry) { if (!report) { return report; } diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 07f0df962455..fd803a508b4a 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -107,7 +107,7 @@ type Hierarchy = Record; selectedOptions?: Option[]; maxRecentReportsToShow?: number; excludeLogins?: string[]; @@ -156,7 +156,6 @@ type SectionForSearchTerm = { section: CategorySection; newIndexOffset: number; }; - type GetOptions = { recentReports: ReportUtils.OptionData[]; personalDetails: ReportUtils.OptionData[]; @@ -533,7 +532,6 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails // some types of actions are filtered out for lastReportAction, in some cases we need to check the actual last action const lastOriginalReportAction = lastReportActions[report?.reportID ?? ''] ?? null; let lastMessageTextFromReport = ''; - const lastActionName = lastReportAction?.actionName ?? ''; if (ReportUtils.isArchivedRoom(report)) { const archiveReason = @@ -585,12 +583,8 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails } else if (ReportActionUtils.isModifiedExpenseAction(lastReportAction)) { const properSchemaForModifiedExpenseMessage = ModifiedExpenseMessage.getForReportAction(report?.reportID, lastReportAction); lastMessageTextFromReport = ReportUtils.formatReportLastMessageText(properSchemaForModifiedExpenseMessage, true); - } else if ( - lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || - lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED || - lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED - ) { - lastMessageTextFromReport = lastReportAction?.message?.[0].text ?? ''; + } else if (ReportActionUtils.isTaskAction(lastReportAction)) { + lastMessageTextFromReport = TaskUtils.getTaskReportActionMessage(lastReportAction).text; } else if (ReportActionUtils.isCreatedTaskReportAction(lastReportAction)) { lastMessageTextFromReport = TaskUtils.getTaskCreatedMessage(lastReportAction); } else if (ReportActionUtils.isApprovedOrSubmittedReportAction(lastReportAction)) { @@ -1441,7 +1435,8 @@ function getOptions( const {parentReportID, parentReportActionID} = report ?? {}; const canGetParentReport = parentReportID && parentReportActionID && allReportActions; const parentReportAction = canGetParentReport ? allReportActions[parentReportID]?.[parentReportActionID] ?? null : null; - const doesReportHaveViolations = betas.includes(CONST.BETAS.VIOLATIONS) && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); + const doesReportHaveViolations = + (betas?.includes(CONST.BETAS.VIOLATIONS) && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction)) ?? false; return ReportUtils.shouldReportBeInOptionList({ report, @@ -1805,7 +1800,7 @@ function getIOUConfirmationOptionsFromParticipants(participants: Participant[], function getFilteredOptions( reports: OnyxCollection, personalDetails: OnyxEntry, - betas: Beta[] = [], + betas: OnyxEntry = [], searchValue = '', selectedOptions: Array> = [], excludeLogins: string[] = [], @@ -1852,9 +1847,9 @@ function getFilteredOptions( */ function getShareDestinationOptions( - reports: Record, + reports: Record, personalDetails: OnyxEntry, - betas: Beta[] = [], + betas: OnyxEntry = [], searchValue = '', selectedOptions: Array> = [], excludeLogins: string[] = [], diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index c9f386f5bd7a..26df03134fd5 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -26,6 +26,10 @@ function canUseViolations(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.VIOLATIONS) || canUseAllBetas(betas); } +function canUseP2PDistanceRequests(betas: OnyxEntry): boolean { + return !!betas?.includes(CONST.BETAS.P2P_DISTANCE_REQUESTS) || canUseAllBetas(betas); +} + function canUseWorkflowsDelayedSubmission(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.WORKFLOWS_DELAYED_SUBMISSION) || canUseAllBetas(betas); } @@ -44,5 +48,6 @@ export default { canUseLinkPreviews, canUseViolations, canUseReportFields, + canUseP2PDistanceRequests, canUseWorkflowsDelayedSubmission, }; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 5b916148c6ee..f6534e075773 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -91,7 +91,9 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry, policyMemb */ function shouldShowPolicy(policy: OnyxEntry, isOffline: boolean): boolean { return ( - !!policy && policy?.isPolicyExpenseChatEnabled && (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) + !!policy && + (policy?.isPolicyExpenseChatEnabled || Boolean(policy?.isJoinRequestPending)) && + (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) ); } @@ -227,7 +229,7 @@ function isPaidGroupPolicy(policy: OnyxEntry | EmptyObject): boolean { * Checks if policy's scheduled submit / auto reporting frequency is "instant". * Note: Free policies have "instant" submit always enabled. */ -function isInstantSubmitEnabled(policy: OnyxEntry): boolean { +function isInstantSubmitEnabled(policy: OnyxEntry | EmptyObject): boolean { return policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT || policy?.type === CONST.POLICY.TYPE.FREE; } @@ -242,6 +244,13 @@ function extractPolicyIDFromPath(path: string) { return path.match(CONST.REGEX.POLICY_ID_FROM_PATH)?.[1]; } +/** + * Whether the policy has active accounting integration connections + */ +function hasAccountingConnections(policy: OnyxEntry) { + return Boolean(policy?.connections); +} + function getPathWithoutPolicyID(path: string) { return path.replace(CONST.REGEX.PATH_WITHOUT_POLICY_ID, '/'); } @@ -263,6 +272,7 @@ function goBackFromInvalidPolicy() { export { getActivePolicies, + hasAccountingConnections, hasPolicyMemberError, hasPolicyError, hasPolicyErrorFields, diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts index bc48111eadc5..3cb15c0f3fc3 100644 --- a/src/libs/Pusher/pusher.ts +++ b/src/libs/Pusher/pusher.ts @@ -5,7 +5,7 @@ import type {LiteralUnion, ValueOf} from 'type-fest'; import Log from '@libs/Log'; import type CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxUpdateEvent, OnyxUpdatesFromServer, ReportUserIsTyping} from '@src/types/onyx'; +import type {OnyxUpdatesFromServer, ReportUserIsTyping} from '@src/types/onyx'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import TYPE from './EventType'; import Pusher from './library'; @@ -22,8 +22,6 @@ type Args = { authEndpoint: string; }; -type PushJSON = OnyxUpdateEvent[] | OnyxUpdatesFromServer; - type UserIsTypingEvent = ReportUserIsTyping & { userLogin?: string; }; @@ -37,7 +35,7 @@ type PusherEventMap = { [TYPE.USER_IS_LEAVING_ROOM]: UserIsLeavingRoomEvent; }; -type EventData = EventName extends keyof PusherEventMap ? PusherEventMap[EventName] : PushJSON; +type EventData = EventName extends keyof PusherEventMap ? PusherEventMap[EventName] : OnyxUpdatesFromServer; type EventCallbackError = {type: ValueOf; data: {code: number}}; @@ -413,4 +411,4 @@ export { getPusherSocketID, }; -export type {EventCallbackError, States, PushJSON, UserIsTypingEvent, UserIsLeavingRoomEvent}; +export type {EventCallbackError, States, UserIsTypingEvent, UserIsLeavingRoomEvent}; diff --git a/src/libs/PusherUtils.ts b/src/libs/PusherUtils.ts index 1ee75eb9c2f6..2bd79adef516 100644 --- a/src/libs/PusherUtils.ts +++ b/src/libs/PusherUtils.ts @@ -1,10 +1,10 @@ import type {OnyxUpdate} from 'react-native-onyx'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; +import type {OnyxUpdatesFromServer} from '@src/types/onyx'; import Log from './Log'; import NetworkConnection from './NetworkConnection'; import * as Pusher from './Pusher/pusher'; -import type {PushJSON} from './Pusher/pusher'; type Callback = (data: OnyxUpdate[]) => Promise; @@ -25,10 +25,10 @@ function triggerMultiEventHandler(eventType: string, data: OnyxUpdate[]): Promis /** * Abstraction around subscribing to private user channel events. Handles all logs and errors automatically. */ -function subscribeToPrivateUserChannelEvent(eventName: string, accountID: string, onEvent: (pushJSON: PushJSON) => void) { +function subscribeToPrivateUserChannelEvent(eventName: string, accountID: string, onEvent: (pushJSON: OnyxUpdatesFromServer) => void) { const pusherChannelName = `${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${accountID}${CONFIG.PUSHER.SUFFIX}` as const; - function logPusherEvent(pushJSON: PushJSON) { + function logPusherEvent(pushJSON: OnyxUpdatesFromServer) { Log.info(`[Report] Handled ${eventName} event sent by Pusher`, false, pushJSON); } @@ -36,7 +36,7 @@ function subscribeToPrivateUserChannelEvent(eventName: string, accountID: string NetworkConnection.triggerReconnectionCallbacks('Pusher re-subscribed to private user channel'); } - function onEventPush(pushJSON: PushJSON) { + function onEventPush(pushJSON: OnyxUpdatesFromServer) { logPusherEvent(pushJSON); onEvent(pushJSON); } diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 628d88fdc76f..3b1ecf45adb4 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -6,7 +6,15 @@ import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ActionName, ChangeLog, IOUMessage, OriginalMessageActionableMentionWhisper, OriginalMessageIOU, OriginalMessageReimbursementDequeued} from '@src/types/onyx/OriginalMessage'; +import type { + ActionName, + ChangeLog, + IOUMessage, + OriginalMessageActionableMentionWhisper, + OriginalMessageIOU, + OriginalMessageJoinPolicyChangeLog, + OriginalMessageReimbursementDequeued, +} from '@src/types/onyx/OriginalMessage'; import type Report from '@src/types/onyx/Report'; import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; @@ -205,7 +213,7 @@ function isTransactionThread(parentReportAction: OnyxEntry): boole ); } -function getOneTransactionThreadReportID(reportActions: ReportActions): string { +function getOneTransactionThreadReportID(reportActions: OnyxEntry): string { const reportActionsArray = Object.values(reportActions ?? {}); if (!reportActionsArray.length) { @@ -407,10 +415,6 @@ function shouldReportActionBeVisible(reportAction: OnyxEntry, key: return false; } - if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.TASKEDITED) { - return false; - } - // Filter out any unsupported reportAction types if (!supportedActionTypes.includes(reportAction.actionName)) { return false; @@ -715,7 +719,8 @@ function isTaskAction(reportAction: OnyxEntry): boolean { return ( reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED || - reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED + reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED || + reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKEDITED ); } @@ -872,7 +877,7 @@ function hasRequestFromCurrentAccount(reportID: string, currentAccountID: number * Checks if a given report action corresponds to an actionable mention whisper. * @param reportAction */ -function isActionableMentionWhisper(reportAction: OnyxEntry): boolean { +function isActionableMentionWhisper(reportAction: OnyxEntry): reportAction is ReportActionBase & OriginalMessageActionableMentionWhisper { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLEMENTIONWHISPER; } @@ -924,6 +929,26 @@ function isCurrentActionUnread(report: Report | EmptyObject, reportAction: Repor return isReportActionUnread(reportAction, lastReadTime) && (!prevReportAction || !isReportActionUnread(prevReportAction, lastReadTime)); } +/** + * Checks if a given report action corresponds to a join request action. + * @param reportAction + */ +function isActionableJoinRequest(reportAction: OnyxEntry): boolean { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLEJOINREQUEST; +} + +/** + * Checks if any report actions correspond to a join request action that is still pending. + * @param reportID + */ +function isActionableJoinRequestPending(reportID: string): boolean { + const sortedReportActions = getSortedReportActions(Object.values(getAllReportActions(reportID))); + const findPendingRequest = sortedReportActions.find( + (reportActionItem) => isActionableJoinRequest(reportActionItem) && (reportActionItem as OriginalMessageJoinPolicyChangeLog)?.originalMessage?.choice === '', + ); + return !!findPendingRequest; +} + function isApprovedOrSubmittedReportAction(action: OnyxEntry | EmptyObject) { return [CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED].some((type) => type === action?.actionName); } @@ -991,6 +1016,8 @@ export { isActionableMentionWhisper, getActionableMentionWhisperMessage, isCurrentActionUnread, + isActionableJoinRequest, + isActionableJoinRequestPending, }; export type {LastVisibleMessage}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 393dfec86f18..29b401c92477 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -28,6 +28,7 @@ import type { ReportAction, ReportMetadata, Session, + Task, Transaction, TransactionViolation, } from '@src/types/onyx'; @@ -515,6 +516,14 @@ Onyx.connect({ }, }); +function getCurrentUserAvatarOrDefault(): UserUtils.AvatarSource { + return currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID); +} + +function getCurrentUserDisplayNameOrEmail(): string | undefined { + return currentUserPersonalDetails?.displayName ?? currentUserEmail; +} + function getChatType(report: OnyxEntry | Participant | EmptyObject): ValueOf | undefined { return report?.chatType; } @@ -959,14 +968,6 @@ function isProcessingReport(report: OnyxEntry | EmptyObject): boolean { return report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS_NUM.SUBMITTED; } -/** - * Returns true if the policy has `instant` reporting frequency and if the report is still being processed (i.e. submitted state) - */ -function isExpenseReportWithInstantSubmittedState(report: OnyxEntry | EmptyObject): boolean { - const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`] ?? null; - return isExpenseReport(report) && isProcessingReport(report) && PolicyUtils.isInstantSubmitEnabled(policy); -} - /** * Check if the report is a single chat report that isn't a thread * and personal detail of participant is optimistic data @@ -1075,6 +1076,20 @@ function findLastAccessedReport( return adminReport ?? sortedReports.at(-1) ?? null; } +/** + * Whether the provided report has expenses + */ +function hasExpenses(reportID?: string): boolean { + return !!Object.values(allTransactions ?? {}).find((transaction) => `${transaction?.reportID}` === `${reportID}`); +} + +/** + * Whether the provided report is a closed expense report with no expenses + */ +function isClosedExpenseReportWithNoExpenses(report: OnyxEntry): boolean { + return report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED && isExpenseReport(report) && !hasExpenses(report.reportID); +} + /** * Whether the provided report is an archived room */ @@ -1082,6 +1097,16 @@ function isArchivedRoom(report: OnyxEntry | EmptyObject): boolean { return report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED && report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED; } +/** + * Whether the provided report is the admin's room + */ +function isJoinRequestInAdminRoom(report: OnyxEntry): boolean { + if (!report) { + return false; + } + return ReportActionsUtils.isActionableJoinRequestPending(report.reportID); +} + /** * Checks if the current user is allowed to comment on the given report. */ @@ -1296,6 +1321,29 @@ function getChildReportNotificationPreference(reportAction: OnyxEntry): boolean { + if (!isMoneyRequestReport(moneyRequestReport)) { + return false; + } + + if (isReportApproved(moneyRequestReport) || isSettled(moneyRequestReport?.reportID)) { + return false; + } + + if (isGroupPolicy(moneyRequestReport) && isProcessingReport(moneyRequestReport) && !PolicyUtils.isInstantSubmitEnabled(getPolicy(moneyRequestReport?.policyID))) { + return false; + } + + return true; +} + /** * Can only delete if the author is this user and the action is an ADDCOMMENT action or an IOU action in an unsettled report, or if the user is a * policy admin @@ -1310,14 +1358,13 @@ function canDeleteReportAction(reportAction: OnyxEntry, reportID: // For now, users cannot delete split actions const isSplitAction = reportAction?.originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; - if (isSplitAction || isSettled(String(reportAction?.originalMessage?.IOUReportID)) || (!isEmptyObject(report) && isReportApproved(report))) { + if (isSplitAction) { return false; } if (isActionOwner) { - if (!isEmptyObject(report) && isPaidGroupPolicyExpenseReport(report)) { - // If it's a paid policy expense report, only allow deleting the request if it's a draft or is instantly submitted or the user is the policy admin - return isDraftExpenseReport(report) || isExpenseReportWithInstantSubmittedState(report) || PolicyUtils.isPolicyAdmin(policy); + if (!isEmptyObject(report) && isMoneyRequestReport(report)) { + return canAddOrDeleteTransactions(report); } return true; } @@ -1842,7 +1889,7 @@ function buildOptimisticCancelPaymentReportAction(expenseReportID: string, amoun person: [ { style: 'strong', - text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), type: 'TEXT', }, ], @@ -1909,6 +1956,10 @@ function requiresAttentionFromCurrentUser(optionOrReport: OnyxEntry | Op return false; } + if (isJoinRequestInAdminRoom(optionOrReport)) { + return true; + } + if (isArchivedRoom(optionOrReport) || isArchivedRoom(getReport(optionOrReport.parentReportID))) { return false; } @@ -2605,6 +2656,10 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu return parentReportActionMessage; } + if (isClosedExpenseReportWithNoExpenses(report)) { + return Localize.translateLocal('parentReportAction.deletedReport'); + } + if (isTaskReport(report) && isCanceledTaskReport(report, parentReportAction)) { return Localize.translateLocal('parentReportAction.deletedTask'); } @@ -3005,11 +3060,10 @@ function buildOptimisticExpenseReport(chatReportID: string, policyID: string, pa const formattedTotal = CurrencyUtils.convertToDisplayString(storedTotal, currency); const policy = getPolicy(policyID); - const isFree = policy?.type === CONST.POLICY.TYPE.FREE; + const isInstantSubmitEnabled = PolicyUtils.isInstantSubmitEnabled(policy); - // Define the state and status of the report based on whether the policy is free or paid - const stateNum = isFree ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.OPEN; - const statusNum = isFree ? CONST.REPORT.STATUS_NUM.SUBMITTED : CONST.REPORT.STATUS_NUM.OPEN; + const stateNum = isInstantSubmitEnabled ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.OPEN; + const statusNum = isInstantSubmitEnabled ? CONST.REPORT.STATUS_NUM.SUBMITTED : CONST.REPORT.STATUS_NUM.OPEN; const expenseReport: OptimisticExpenseReport = { reportID: generateReportID(), @@ -3180,14 +3234,14 @@ function buildOptimisticIOUReportAction( actionName: CONST.REPORT.ACTIONS.TYPE.IOU, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), isAttachment: false, originalMessage, message: getIOUReportActionMessage(iouReportID, type, amount, comment, currency, paymentType, isSettlingUp), person: [ { style: 'strong', - text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), type: 'TEXT', }, ], @@ -3213,14 +3267,14 @@ function buildOptimisticApprovedReportAction(amount: number, currency: string, e actionName: CONST.REPORT.ACTIONS.TYPE.APPROVED, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), isAttachment: false, originalMessage, message: getIOUReportActionMessage(expenseReportID, CONST.REPORT.ACTIONS.TYPE.APPROVED, Math.abs(amount), '', currency), person: [ { style: 'strong', - text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), type: 'TEXT', }, ], @@ -3255,14 +3309,14 @@ function buildOptimisticMovedReportAction(fromPolicyID: string, toPolicyID: stri actionName: CONST.REPORT.ACTIONS.TYPE.MOVED, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), isAttachment: false, originalMessage, message: movedActionMessage, person: [ { style: 'strong', - text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), type: 'TEXT', }, ], @@ -3288,14 +3342,14 @@ function buildOptimisticSubmittedReportAction(amount: number, currency: string, actionName: CONST.REPORT.ACTIONS.TYPE.SUBMITTED, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatar(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), isAttachment: false, originalMessage, message: getIOUReportActionMessage(expenseReportID, CONST.REPORT.ACTIONS.TYPE.SUBMITTED, Math.abs(amount), '', currency), person: [ { style: 'strong', - text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), type: 'TEXT', }, ], @@ -3361,7 +3415,7 @@ function buildOptimisticModifiedExpenseReportAction( actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), created: DateUtils.getDBTime(), isAttachment: false, message: [ @@ -3444,7 +3498,7 @@ function buildOptimisticTaskReportAction(taskReportID: string, actionName: Origi actionName, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), isAttachment: false, originalMessage, message: [ @@ -3520,10 +3574,6 @@ function buildOptimisticChatReport( }; } -function getCurrentUserAvatarOrDefault(): UserUtils.AvatarSource { - return allPersonalDetails?.[currentUserAccountID ?? '']?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID); -} - /** * Returns the necessary reportAction onyx data to indicate that the chat has been created optimistically * @param [created] - Action created time @@ -3550,7 +3600,7 @@ function buildOptimisticCreatedReportAction(emailCreatingAction: string, created { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], automatic: false, @@ -3586,7 +3636,7 @@ function buildOptimisticRenamedRoomReportAction(newName: string, oldName: string { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], originalMessage: { @@ -3627,11 +3677,11 @@ function buildOptimisticHoldReportAction(comment: string, created = DateUtils.ge { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], automatic: false, - avatar: allPersonalDetails?.[currentUserAccountID ?? '']?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), created, shouldShow: true, }; @@ -3658,42 +3708,79 @@ function buildOptimisticUnHoldReportAction(created = DateUtils.getDBTime()): Opt { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'normal', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], automatic: false, - avatar: allPersonalDetails?.[currentUserAccountID ?? '']?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), created, shouldShow: true, }; } -/** - * Returns the necessary reportAction onyx data to indicate that a task report has been edited - */ -function buildOptimisticEditedTaskReportAction(emailEditingTask: string): OptimisticEditedTaskReportAction { +function buildOptimisticEditedTaskFieldReportAction({title, description}: Task): OptimisticEditedTaskReportAction { + // We do not modify title & description in one request, so we need to create a different optimistic action for each field modification + let field = ''; + let value = ''; + if (title !== undefined) { + field = 'task title'; + value = title; + } else if (description !== undefined) { + field = 'description'; + value = description; + } + + let changelog = 'edited this task'; + if (field && value) { + changelog = `updated the ${field} to ${value}`; + } else if (field) { + changelog = `removed the ${field}`; + } + return { reportActionID: NumberUtils.rand64(), actionName: CONST.REPORT.ACTIONS.TYPE.TASKEDITED, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, actorAccountID: currentUserAccountID, message: [ + { + type: CONST.REPORT.MESSAGE.TYPE.COMMENT, + text: changelog, + html: changelog, + }, + ], + person: [ { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: emailEditingTask, + text: getCurrentUserDisplayNameOrEmail(), }, + ], + automatic: false, + avatar: getCurrentUserAvatarOrDefault(), + created: DateUtils.getDBTime(), + shouldShow: false, + }; +} + +function buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID: number): OptimisticEditedTaskReportAction { + return { + reportActionID: NumberUtils.rand64(), + actionName: CONST.REPORT.ACTIONS.TYPE.TASKEDITED, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + actorAccountID: currentUserAccountID, + message: [ { - type: CONST.REPORT.MESSAGE.TYPE.TEXT, - style: 'normal', - text: ' edited this task', + type: CONST.REPORT.MESSAGE.TYPE.COMMENT, + text: `assigned to ${getDisplayNameForParticipant(assigneeAccountID)}`, + html: `assigned to `, }, ], person: [ { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], automatic: false, @@ -3736,7 +3823,7 @@ function buildOptimisticClosedReportAction(emailClosingReport: string, policyNam { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], reportActionID: NumberUtils.rand64(), @@ -4001,7 +4088,7 @@ function shouldReportBeInOptionList({ report: OnyxEntry; currentReportId: string; isInGSDMode: boolean; - betas: Beta[]; + betas: OnyxEntry; policies: OnyxCollection; excludeEmptyChats: boolean; doesReportHaveViolations: boolean; @@ -4165,7 +4252,13 @@ function chatIncludesChronos(report: OnyxEntry | EmptyObject): boolean { * - It's an ADDCOMMENT that is not an attachment */ function canFlagReportAction(reportAction: OnyxEntry, reportID: string | undefined): boolean { - const report = getReport(reportID); + let report = getReport(reportID); + + // If the childReportID exists in reportAction and is equal to the reportID, + // the report action being evaluated is the parent report action in a thread, and we should get the parent report to evaluate instead. + if (reportAction?.childReportID?.toString() === reportID?.toString()) { + report = getReport(report?.parentReportID); + } const isCurrentUserAction = reportAction?.actorAccountID === currentUserAccountID; const isOriginalMessageHaveHtml = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT || @@ -4340,7 +4433,6 @@ function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, o return false; } - // In case of expense reports, we have to look at the parent workspace chat to get the isOwnPolicyExpenseChat property let isOwnPolicyExpenseChat = report?.isOwnPolicyExpenseChat ?? false; if (isExpenseReport(report) && getParentReport(report)) { isOwnPolicyExpenseChat = Boolean(getParentReport(report)?.isOwnPolicyExpenseChat); @@ -4354,12 +4446,8 @@ function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, o // User can request money in any IOU report, unless paid, but user can only request money in an expense report // which is tied to their workspace chat. if (isMoneyRequestReport(report)) { - const isOwnExpenseReport = isExpenseReport(report) && isOwnPolicyExpenseChat; - if (isOwnExpenseReport && PolicyUtils.isPaidGroupPolicy(policy)) { - return isDraftExpenseReport(report) || isExpenseReportWithInstantSubmittedState(report); - } - - return (isOwnExpenseReport || isIOUReport(report)) && !isReportApproved(report) && !isSettled(report?.reportID); + const canAddTransactions = canAddOrDeleteTransactions(report); + return isGroupPolicy(report) ? isOwnPolicyExpenseChat && canAddTransactions : canAddTransactions; } // In case of policy expense chat, users can only request money from their own policy expense chat @@ -5119,6 +5207,17 @@ function canBeAutoReimbursed(report: OnyxEntry, policy: OnyxEntry | undefined | null, chatReport: OnyxEntry | null): boolean { + return !existingIOUReport || hasIOUWaitingOnCurrentUserBankAccount(chatReport) || !canAddOrDeleteTransactions(existingIOUReport); +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -5142,6 +5241,7 @@ export { getPolicyName, getPolicyType, isArchivedRoom, + isClosedExpenseReportWithNoExpenses, isExpensifyOnlyParticipantInReport, canCreateTaskInReport, isPolicyExpenseChatAdmin, @@ -5150,7 +5250,6 @@ export { isPublicAnnounceRoom, isConciergeChatReport, isProcessingReport, - isExpenseReportWithInstantSubmittedState, isCurrentUserTheOnlyParticipant, hasAutomatedExpensifyAccountIDs, hasExpensifyGuidesEmails, @@ -5190,7 +5289,8 @@ export { buildOptimisticClosedReportAction, buildOptimisticCreatedReportAction, buildOptimisticRenamedRoomReportAction, - buildOptimisticEditedTaskReportAction, + buildOptimisticEditedTaskFieldReportAction, + buildOptimisticChangedTaskAssigneeReportAction, buildOptimisticIOUReport, buildOptimisticApprovedReportAction, buildOptimisticMovedReportAction, @@ -5324,6 +5424,9 @@ export { canEditRoomVisibility, canEditPolicyDescription, getPolicyDescriptionText, + isJoinRequestInAdminRoom, + canAddOrDeleteTransactions, + shouldCreateNewMoneyRequestReport, }; export type { diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 8d53e992cb2d..3aa4cb63df9a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -328,7 +328,7 @@ function getOptionData({ const newName = lastAction?.originalMessage?.newName ?? ''; result.alternateText = Localize.translate(preferredLocale, 'newRoomPage.roomRenamedTo', {newName}); } else if (ReportActionsUtils.isTaskAction(lastAction)) { - result.alternateText = TaskUtils.getTaskReportActionMessage(lastAction.actionName); + result.alternateText = TaskUtils.getTaskReportActionMessage(lastAction).text; } else if ( lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM || @@ -386,6 +386,12 @@ function getOptionData({ result.isIOUReportOwner = ReportUtils.isIOUOwnedByCurrentUser(result as Report); + if (ReportActionsUtils.isActionableJoinRequestPending(report.reportID)) { + result.isPinned = true; + result.isUnread = true; + result.brickRoadIndicator = CONST.BRICK_ROAD_INDICATOR_STATUS.INFO; + } + if (!hasMultipleParticipants) { result.accountID = personalDetail?.accountID; result.login = personalDetail?.login; diff --git a/src/libs/TaskUtils.ts b/src/libs/TaskUtils.ts index 623d449db885..81a079003d0e 100644 --- a/src/libs/TaskUtils.ts +++ b/src/libs/TaskUtils.ts @@ -3,6 +3,7 @@ import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report} from '@src/types/onyx'; +import type {Message} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import * as CollectionUtils from './CollectionUtils'; import * as Localize from './Localize'; @@ -22,16 +23,21 @@ Onyx.connect({ /** * Given the Task reportAction name, return the appropriate message to be displayed and copied to clipboard. */ -function getTaskReportActionMessage(actionName: string): string { - switch (actionName) { +function getTaskReportActionMessage(action: OnyxEntry): Pick { + switch (action?.actionName) { case CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED: - return Localize.translateLocal('task.messages.completed'); + return {text: Localize.translateLocal('task.messages.completed')}; case CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED: - return Localize.translateLocal('task.messages.canceled'); + return {text: Localize.translateLocal('task.messages.canceled')}; case CONST.REPORT.ACTIONS.TYPE.TASKREOPENED: - return Localize.translateLocal('task.messages.reopened'); + return {text: Localize.translateLocal('task.messages.reopened')}; + case CONST.REPORT.ACTIONS.TYPE.TASKEDITED: + return { + text: action?.message?.[0].text ?? '', + html: action?.message?.[0].html, + }; default: - return Localize.translateLocal('task.task'); + return {text: Localize.translateLocal('task.task')}; } } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5f9657755b02..cb3aa20ab6a7 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -27,6 +27,7 @@ import type { import {WRITE_COMMANDS} from '@libs/API/types'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as FileUtils from '@libs/fileDownload/FileUtils'; import * as IOUUtils from '@libs/IOUUtils'; @@ -222,12 +223,22 @@ Onyx.connect({ }, }); +let lastSelectedDistanceRates: OnyxEntry = {}; +Onyx.connect({ + key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + callback: (value) => { + lastSelectedDistanceRates = value; + }, +}); + /** * Initialize money request info * @param reportID to attach the transaction to + * @param policy + * @param isFromGlobalCreate * @param iouRequestType one of manual/scan/distance */ -function initMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) { +function initMoneyRequest(reportID: string, policy: OnyxEntry, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) { // Generate a brand new transactionID const newTransactionID = CONST.IOU.OPTIMISTIC_TRANSACTION_ID; // Disabling this line since currentDate can be an empty string @@ -241,6 +252,12 @@ function initMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequ waypoint0: {}, waypoint1: {}, }; + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; + if (ReportUtils.isPolicyExpenseChat(report)) { + customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; + } + comment.customUnit = {customUnitRateID}; } // Store the transaction in Onyx and mark it as not saved so it can be cleaned up later @@ -828,37 +845,26 @@ function getMoneyRequestInformation( // STEP 2: Get the money request report. If the moneyRequestReportID has been provided, we want to add the transaction to this specific report. // If no such reportID has been provided, let's use the chatReport.iouReportID property. In case that is not present, build a new optimistic money request report. let iouReport: OnyxEntry = null; - const shouldCreateNewMoneyRequestReport = !moneyRequestReportID && (!chatReport.iouReportID || ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(chatReport)); if (moneyRequestReportID) { iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReportID}`] ?? null; - } else if (!shouldCreateNewMoneyRequestReport) { + } else { iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`] ?? null; } - let isFromPaidPolicy = false; - if (isPolicyExpenseChat) { - isFromPaidPolicy = PolicyUtils.isPaidGroupPolicy(policy ?? null); - - // If the linked expense report on paid policy is not draft and not instantly submitted, we need to create a new draft expense report - if (iouReport && isFromPaidPolicy && !ReportUtils.isDraftExpenseReport(iouReport) && !ReportUtils.isExpenseReportWithInstantSubmittedState(iouReport)) { - iouReport = null; - } - } + const shouldCreateNewMoneyRequestReport = ReportUtils.shouldCreateNewMoneyRequestReport(iouReport, chatReport); - if (iouReport) { - if (isPolicyExpenseChat) { - iouReport = {...iouReport}; - if (iouReport?.currency === currency && typeof iouReport.total === 'number') { - // Because of the Expense reports are stored as negative values, we subtract the total from the amount - iouReport.total -= amount; - } - } else { - iouReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, payeeAccountID, amount, currency); - } - } else { + if (!iouReport || shouldCreateNewMoneyRequestReport) { iouReport = isPolicyExpenseChat ? ReportUtils.buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID ?? '', payeeAccountID, amount, currency) : ReportUtils.buildOptimisticIOUReport(payeeAccountID, payerAccountID, amount, chatReport.reportID, currency); + } else if (isPolicyExpenseChat) { + iouReport = {...iouReport}; + if (iouReport?.currency === currency && typeof iouReport.total === 'number') { + // Because of the Expense reports are stored as negative values, we subtract the total from the amount + iouReport.total -= amount; + } + } else { + iouReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, payeeAccountID, amount, currency); } // STEP 3: Build optimistic receipt and transaction @@ -1843,10 +1849,8 @@ function createSplitsAndOnyxData( } // STEP 2: Get existing IOU/Expense report and update its total OR build a new optimistic one - // For Control policy expense chats, if the report is already approved, create a new expense report let oneOnOneIOUReport: OneOnOneIOUReport = oneOnOneChatReport.iouReportID ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`] : null; - const shouldCreateNewOneOnOneIOUReport = - !oneOnOneIOUReport || (isOwnPolicyExpenseChat && ReportUtils.isControlPolicyExpenseReport(oneOnOneIOUReport) && ReportUtils.isReportApproved(oneOnOneIOUReport)); + const shouldCreateNewOneOnOneIOUReport = ReportUtils.shouldCreateNewMoneyRequestReport(oneOnOneIOUReport, oneOnOneChatReport); if (!oneOnOneIOUReport || shouldCreateNewOneOnOneIOUReport) { oneOnOneIOUReport = isOwnPolicyExpenseChat @@ -2484,8 +2488,7 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA } let oneOnOneIOUReport: OneOnOneIOUReport = oneOnOneChatReport?.iouReportID ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`] : null; - const shouldCreateNewOneOnOneIOUReport = - !oneOnOneIOUReport || (isPolicyExpenseChat && ReportUtils.isControlPolicyExpenseReport(oneOnOneIOUReport) && ReportUtils.isReportApproved(oneOnOneIOUReport)); + const shouldCreateNewOneOnOneIOUReport = ReportUtils.shouldCreateNewMoneyRequestReport(oneOnOneIOUReport, oneOnOneChatReport); if (!oneOnOneIOUReport || shouldCreateNewOneOnOneIOUReport) { oneOnOneIOUReport = isPolicyExpenseChat @@ -3638,6 +3641,7 @@ function getPayMoneyRequestParams(chatReport: OnyxTypes.Report, iouReport: OnyxT chatReportID: chatReport.reportID, reportActionID: optimisticIOUReportAction.reportActionID, paymentMethodType, + amount: Math.abs(total), }, optimisticData, successData, diff --git a/src/libs/actions/OnyxUpdateManager.ts b/src/libs/actions/OnyxUpdateManager.ts index ab0dea960b27..b4554f9461ce 100644 --- a/src/libs/actions/OnyxUpdateManager.ts +++ b/src/libs/actions/OnyxUpdateManager.ts @@ -41,7 +41,8 @@ export default () => { if ( !(typeof value === 'object' && !!value) || !('type' in value) || - (!(value.type === CONST.ONYX_UPDATE_TYPES.HTTPS && value.request && value.response) && !(value.type === CONST.ONYX_UPDATE_TYPES.PUSHER && value.updates)) + (!(value.type === CONST.ONYX_UPDATE_TYPES.HTTPS && value.request && value.response) && + !((value.type === CONST.ONYX_UPDATE_TYPES.PUSHER || value.type === CONST.ONYX_UPDATE_TYPES.AIRSHIP) && value.updates)) ) { console.debug('[OnyxUpdateManager] Invalid format found for updates, cleaning and unpausing the queue'); Onyx.set(ONYXKEYS.ONYX_UPDATES_FROM_SERVER, null); diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index cfb4735f0638..ab26ad330b6f 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -2,6 +2,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {Merge} from 'type-fest'; import Log from '@libs/Log'; +import * as SequentialQueue from '@libs/Network/SequentialQueue'; import PusherUtils from '@libs/PusherUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -107,7 +108,7 @@ function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFrom if (type === CONST.ONYX_UPDATE_TYPES.HTTPS && request && response) { return applyHTTPSOnyxUpdates(request, response); } - if (type === CONST.ONYX_UPDATE_TYPES.PUSHER && updates) { + if ((type === CONST.ONYX_UPDATE_TYPES.PUSHER || type === CONST.ONYX_UPDATE_TYPES.AIRSHIP) && updates) { return applyPusherOnyxUpdates(updates); } } @@ -141,5 +142,17 @@ function doesClientNeedToBeUpdated(previousUpdateID = 0): boolean { return lastUpdateIDAppliedToClient < previousUpdateID; } +function applyOnyxUpdatesReliably(updates: OnyxUpdatesFromServer) { + const previousUpdateID = Number(updates.previousUpdateID) || 0; + if (!doesClientNeedToBeUpdated(previousUpdateID)) { + apply(updates); + return; + } + + // If we reached this point, we need to pause the queue while we prepare to fetch older OnyxUpdates. + SequentialQueue.pause(); + saveUpdateInformation(updates); +} + // eslint-disable-next-line import/prefer-default-export -export {saveUpdateInformation, doesClientNeedToBeUpdated, apply}; +export {saveUpdateInformation, doesClientNeedToBeUpdated, apply, applyOnyxUpdatesReliably}; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index aa64611b210f..f6a1ec3ec340 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -57,7 +57,8 @@ import type { ReportAction, Transaction, } from '@src/types/onyx'; -import type {Errors} from '@src/types/onyx/OnyxCommon'; +import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon'; +import type {OriginalMessageJoinPolicyChangeLog} from '@src/types/onyx/OriginalMessage'; import type {Attributes, CustomUnit, Rate, Unit} from '@src/types/onyx/Policy'; import type {OnyxData} from '@src/types/onyx/Request'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; @@ -749,9 +750,9 @@ function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newR onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, value: { - ...memberRoles.reduce((member: Record, current) => { + ...memberRoles.reduce((member: Record, current) => { // eslint-disable-next-line no-param-reassign - member[current.accountID] = {role: current?.role}; + member[current.accountID] = {role: current?.role, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}; return member; }, {}), errors: null, @@ -764,6 +765,11 @@ function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newR onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`, value: { + ...memberRoles.reduce((member: Record, current) => { + // eslint-disable-next-line no-param-reassign + member[current.accountID] = {role: current?.role, pendingAction: null}; + return member; + }, {}), errors: null, }, }, @@ -964,6 +970,37 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount API.write(WRITE_COMMANDS.ADD_MEMBERS_TO_WORKSPACE, params, {optimisticData, successData, failureData}); } +/** + * Invite member to the specified policyID + * Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details + */ +function inviteMemberToWorkspace(policyID: string, inviterEmail: string) { + const memberJoinKey = `${ONYXKEYS.COLLECTION.POLICY_JOIN_MEMBER}${policyID}` as const; + + const optimisticMembersState = {policyID, inviterEmail}; + const failureMembersState = {policyID, inviterEmail}; + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: memberJoinKey, + value: optimisticMembersState, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: memberJoinKey, + value: {...failureMembersState, errors: ErrorUtils.getMicroSecondOnyxError('common.genericEditFailureMessage')}, + }, + ]; + + const params = {policyID, inviterEmail}; + + API.write(WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK, params, {optimisticData, failureData}); +} + /** * Updates a workspace avatar image */ @@ -2435,6 +2472,56 @@ function setWorkspaceCategoryEnabled(policyID: string, categoriesToUpdate: Recor API.write('SetWorkspaceCategoriesEnabled', parameters, onyxData); } +function createPolicyCategory(policyID: string, categoryName: string) { + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + value: { + [categoryName]: { + name: categoryName, + enabled: true, + errors: null, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + value: { + [categoryName]: { + errors: null, + pendingAction: null, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + value: { + [categoryName]: { + errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.genericFailureMessage'), + pendingAction: null, + }, + }, + }, + ], + }; + + const parameters = { + policyID, + categories: JSON.stringify([{name: categoryName}]), + }; + + API.write(WRITE_COMMANDS.CREATE_WORKSPACE_CATEGORIES, parameters, onyxData); +} + function setWorkspaceRequiresCategory(policyID: string, requiresCategory: boolean) { const onyxData: OnyxData = { optimisticData: [ @@ -2503,6 +2590,123 @@ function clearCategoryErrors(policyID: string, categoryName: string) { }); } +/** + * Accept user join request to a workspace + */ +function acceptJoinRequest(reportID: string, reportAction: OnyxEntry) { + const choice = CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT; + if (!reportAction) { + return; + } + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [reportAction.reportActionID]: { + originalMessage: {choice}, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [reportAction.reportActionID]: { + originalMessage: {choice}, + pendingAction: null, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [reportAction.reportActionID]: { + originalMessage: {choice: ''}, + pendingAction: null, + }, + }, + }, + ]; + + const parameters = { + requests: JSON.stringify({ + [(reportAction.originalMessage as OriginalMessageJoinPolicyChangeLog['originalMessage']).policyID]: { + requests: [{accountID: reportAction?.actorAccountID, adminsRoomMessageReportActionID: reportAction.reportActionID}], + }, + }), + }; + + API.write(WRITE_COMMANDS.ACCEPT_JOIN_REQUEST, parameters, {optimisticData, failureData, successData}); +} + +/** + * Decline user join request to a workspace + */ +function declineJoinRequest(reportID: string, reportAction: OnyxEntry) { + if (!reportAction) { + return; + } + const choice = CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE; + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [reportAction.reportActionID]: { + originalMessage: {choice}, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [reportAction.reportActionID]: { + originalMessage: {choice}, + pendingAction: null, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [reportAction.reportActionID]: { + originalMessage: {choice: ''}, + pendingAction: null, + }, + }, + }, + ]; + + const parameters = { + requests: JSON.stringify({ + [(reportAction.originalMessage as OriginalMessageJoinPolicyChangeLog['originalMessage']).policyID]: { + requests: [{accountID: reportAction?.actorAccountID, adminsRoomMessageReportActionID: reportAction.reportActionID}], + }, + }), + }; + + API.write(WRITE_COMMANDS.DECLINE_JOIN_REQUEST, parameters, {optimisticData, failureData, successData}); +} + export { removeMembers, updateWorkspaceMembersRole, @@ -2552,5 +2756,9 @@ export { updateWorkspaceDescription, setWorkspaceCategoryEnabled, setWorkspaceRequiresCategory, + inviteMemberToWorkspace, + acceptJoinRequest, + declineJoinRequest, + createPolicyCategory, clearCategoryErrors, }; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7ad12cf3e1ed..94fe324d306a 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -113,15 +113,31 @@ Onyx.connect({ }, }); +// map of reportID to all reportActions for that report const allReportActions: OnyxCollection = {}; + +// map of reportID to the ID of the oldest reportAction for that report +const oldestReportActions: Record = {}; + +// map of report to the ID of the newest action for that report +const newestReportActions: Record = {}; + Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - callback: (action, key) => { - if (!key || !action) { + callback: (actions, key) => { + if (!key || !actions) { return; } const reportID = CollectionUtils.extractCollectionItemID(key); - allReportActions[reportID] = action; + allReportActions[reportID] = actions; + const sortedActions = ReportActionsUtils.getSortedReportActions(Object.values(actions)); + + if (sortedActions.length === 0) { + return; + } + + oldestReportActions[reportID] = sortedActions[0].reportActionID; + newestReportActions[reportID] = sortedActions[sortedActions.length - 1].reportActionID; }, }); @@ -879,7 +895,7 @@ function reconnect(reportID: string) { * Gets the older actions that have not been read yet. * Normally happens when you scroll up on a chat, and the actions have not been read yet. */ -function getOlderActions(reportID: string, reportActionID: string) { +function getOlderActions(reportID: string) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -912,7 +928,7 @@ function getOlderActions(reportID: string, reportActionID: string) { const parameters: GetOlderActionsParams = { reportID, - reportActionID, + reportActionID: oldestReportActions[reportID], }; API.read(READ_COMMANDS.GET_OLDER_ACTIONS, parameters, {optimisticData, successData, failureData}); @@ -922,7 +938,7 @@ function getOlderActions(reportID: string, reportActionID: string) { * Gets the newer actions that have not been read yet. * Normally happens when you are not located at the bottom of the list and scroll down on a chat. */ -function getNewerActions(reportID: string, reportActionID: string) { +function getNewerActions(reportID: string) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -955,7 +971,7 @@ function getNewerActions(reportID: string, reportActionID: string) { const parameters: GetNewerActionsParams = { reportID, - reportActionID, + reportActionID: newestReportActions[reportID], }; API.read(READ_COMMANDS.GET_NEWER_ACTIONS, parameters, {optimisticData, successData, failureData}); diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index e328460c37eb..27c7f3e36fd4 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -399,7 +399,7 @@ function reopenTask(taskReport: OnyxEntry) { function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); + const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskFieldReportAction({title, description}); // Sometimes title or description is undefined, so we need to check for that, and we provide it to multiple functions const reportName = (title ?? report?.reportName)?.trim(); @@ -429,6 +429,11 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task ]; const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, + value: {[editTaskReportAction.reportActionID]: {pendingAction: null}}, + }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, @@ -467,16 +472,22 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task API.write(WRITE_COMMANDS.EDIT_TASK, parameters, {optimisticData, successData, failureData}); } -function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assigneeEmail: string, assigneeAccountID = 0, assigneeChatReport: OnyxEntry = null) { +function editTaskAssignee( + report: OnyxTypes.Report, + ownerAccountID: number, + assigneeEmail: string, + assigneeAccountID: number | null = 0, + assigneeChatReport: OnyxEntry = null, +) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); + const editTaskReportAction = ReportUtils.buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID ?? 0); const reportName = report.reportName?.trim(); let assigneeChatReportOnyxData; const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : '0'; const optimisticReport: OptimisticReport = { reportName, - managerID: assigneeAccountID || report.managerID, + managerID: assigneeAccountID ?? report.managerID, pendingFields: { ...(assigneeAccountID && {managerID: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }, @@ -499,6 +510,11 @@ function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assi ]; const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, + value: {[editTaskReportAction.reportActionID]: {pendingAction: null}}, + }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 7b146f7447bb..fdd657f801f2 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -578,35 +578,15 @@ function subscribeToUserEvents() { // Handles the mega multipleEvents from Pusher which contains an array of single events. // Each single event is passed to PusherUtils in order to trigger the callbacks for that event PusherUtils.subscribeToPrivateUserChannelEvent(Pusher.TYPE.MULTIPLE_EVENTS, currentUserAccountID.toString(), (pushJSON) => { - // The data for this push event comes in two different formats: - // 1. Original format - this is what was sent before the RELIABLE_UPDATES project and will go away once RELIABLE_UPDATES is fully complete - // - The data is an array of objects, where each object is an onyx update - // Example: [{onyxMethod: 'whatever', key: 'foo', value: 'bar'}] - // 1. Reliable updates format - this is what was sent with the RELIABLE_UPDATES project and will be the format from now on - // - The data is an object, containing updateIDs from the server and an array of onyx updates (this array is the same format as the original format above) - // Example: {lastUpdateID: 1, previousUpdateID: 0, updates: [{onyxMethod: 'whatever', key: 'foo', value: 'bar'}]} - if (Array.isArray(pushJSON)) { - Log.warn('Received pusher event with array format'); - pushJSON.forEach((multipleEvent) => { - PusherUtils.triggerMultiEventHandler(multipleEvent.eventType, multipleEvent.data); - }); - return; - } - + // The data for the update is an object, containing updateIDs from the server and an array of onyx updates (this array is the same format as the original format above) + // Example: {lastUpdateID: 1, previousUpdateID: 0, updates: [{onyxMethod: 'whatever', key: 'foo', value: 'bar'}]} const updates = { type: CONST.ONYX_UPDATE_TYPES.PUSHER, lastUpdateID: Number(pushJSON.lastUpdateID || 0), updates: pushJSON.updates ?? [], previousUpdateID: Number(pushJSON.previousUpdateID || 0), }; - if (!OnyxUpdates.doesClientNeedToBeUpdated(Number(pushJSON.previousUpdateID || 0))) { - OnyxUpdates.apply(updates); - return; - } - - // If we reached this point, we need to pause the queue while we prepare to fetch older OnyxUpdates. - SequentialQueue.pause(); - OnyxUpdates.saveUpdateInformation(updates); + OnyxUpdates.applyOnyxUpdatesReliably(updates); }); // Handles Onyx updates coming from Pusher through the mega multipleEvents. diff --git a/src/libs/calculateAnchorPosition.ts b/src/libs/calculateAnchorPosition.ts index 3dc5924d023a..9fe6e8f018d8 100644 --- a/src/libs/calculateAnchorPosition.ts +++ b/src/libs/calculateAnchorPosition.ts @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-imports */ -import type {Text as RNText, View} from 'react-native'; import type {ValueOf} from 'type-fest'; +import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; import type {AnchorPosition} from '@src/styles'; @@ -13,9 +13,9 @@ type AnchorOrigin = { /** * Gets the x,y position of the passed in component for the purpose of anchoring another component to it. */ -export default function calculateAnchorPosition(anchorComponent: View | RNText, anchorOrigin?: AnchorOrigin): Promise { +export default function calculateAnchorPosition(anchorComponent: ContextMenuAnchor, anchorOrigin?: AnchorOrigin): Promise { return new Promise((resolve) => { - if (!anchorComponent) { + if (!anchorComponent || !('measureInWindow' in anchorComponent)) { resolve({horizontal: 0, vertical: 0}); return; } diff --git a/src/libs/focusTextInputAfterAnimation/index.android.ts b/src/libs/focusTextInputAfterAnimation/index.android.ts index 31c748f5daa4..cca8a6588103 100644 --- a/src/libs/focusTextInputAfterAnimation/index.android.ts +++ b/src/libs/focusTextInputAfterAnimation/index.android.ts @@ -19,7 +19,7 @@ import type FocusTextInputAfterAnimation from './types'; */ const focusTextInputAfterAnimation: FocusTextInputAfterAnimation = (inputRef, animationLength = 0) => { setTimeout(() => { - inputRef.focus(); + inputRef?.focus(); }, animationLength); }; diff --git a/src/libs/focusTextInputAfterAnimation/index.ts b/src/libs/focusTextInputAfterAnimation/index.ts index 3f7c6555b5ce..66d0c35c1a63 100644 --- a/src/libs/focusTextInputAfterAnimation/index.ts +++ b/src/libs/focusTextInputAfterAnimation/index.ts @@ -4,7 +4,7 @@ import type FocusTextInputAfterAnimation from './types'; * This library is a no-op for all platforms except for Android and iOS and will immediately focus the given input without any delays. */ const focusTextInputAfterAnimation: FocusTextInputAfterAnimation = (inputRef) => { - inputRef.focus(); + inputRef?.focus(); }; export default focusTextInputAfterAnimation; diff --git a/src/libs/focusTextInputAfterAnimation/types.ts b/src/libs/focusTextInputAfterAnimation/types.ts index a6a14165598b..bfe29317c1ef 100644 --- a/src/libs/focusTextInputAfterAnimation/types.ts +++ b/src/libs/focusTextInputAfterAnimation/types.ts @@ -1,5 +1,5 @@ import type {TextInput} from 'react-native'; -type FocusTextInputAfterAnimation = (inputRef: TextInput | HTMLInputElement, animationLength: number) => void; +type FocusTextInputAfterAnimation = (inputRef: TextInput | HTMLInputElement | undefined, animationLength: number) => void; export default FocusTextInputAfterAnimation; diff --git a/src/libs/getClickedTargetLocation/types.ts b/src/libs/getClickedTargetLocation/types.ts index 7b1e85e63b17..eed10238be2d 100644 --- a/src/libs/getClickedTargetLocation/types.ts +++ b/src/libs/getClickedTargetLocation/types.ts @@ -1,5 +1,5 @@ type DOMRectProperties = 'top' | 'bottom' | 'left' | 'right' | 'height' | 'x' | 'y'; -type GetClickedTargetLocation = (target: Element) => Pick; +type GetClickedTargetLocation = (target: HTMLDivElement) => Pick; export default GetClickedTargetLocation; diff --git a/src/libs/isReportMessageAttachment.ts b/src/libs/isReportMessageAttachment.ts index fd03adcffd93..330ba4470097 100644 --- a/src/libs/isReportMessageAttachment.ts +++ b/src/libs/isReportMessageAttachment.ts @@ -8,15 +8,15 @@ import type {Message} from '@src/types/onyx/ReportAction'; * * @param reportActionMessage report action's message as text, html and translationKey */ -export default function isReportMessageAttachment({text, html, translationKey}: Message): boolean { - if (!text || !html) { +export default function isReportMessageAttachment(message: Message | undefined): boolean { + if (!message?.text || !message.html) { return false; } - if (translationKey && text === CONST.ATTACHMENT_MESSAGE_TEXT) { - return translationKey === CONST.TRANSLATION_KEYS.ATTACHMENT; + if (message.translationKey && message.text === CONST.ATTACHMENT_MESSAGE_TEXT) { + return message?.translationKey === CONST.TRANSLATION_KEYS.ATTACHMENT; } const regex = new RegExp(` ${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}="(.*)"`, 'i'); - return (text === CONST.ATTACHMENT_MESSAGE_TEXT || !!Str.isVideo(text)) && (!!html.match(regex) || html === CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML); + return (message.text === CONST.ATTACHMENT_MESSAGE_TEXT || !!Str.isVideo(message.text)) && (!!message.html.match(regex) || message.html === CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML); } diff --git a/src/libs/migrations/KeyReportActionsDraftByReportActionID.ts b/src/libs/migrations/KeyReportActionsDraftByReportActionID.ts index dbf2829a6c28..c8ef72ca15e7 100644 --- a/src/libs/migrations/KeyReportActionsDraftByReportActionID.ts +++ b/src/libs/migrations/KeyReportActionsDraftByReportActionID.ts @@ -47,6 +47,7 @@ export default function () { // If newReportActionsDrafts[newOnyxKey] isn't set, fall back on the migrated draft if there is one const currentActionsDrafts = newReportActionsDrafts[newOnyxKey] ?? allReportActionsDrafts[newOnyxKey]; + newReportActionsDrafts[newOnyxKey] = { ...currentActionsDrafts, [reportActionID]: reportActionDraft, diff --git a/src/libs/navigateAfterJoinRequest/index.desktop.ts b/src/libs/navigateAfterJoinRequest/index.desktop.ts new file mode 100644 index 000000000000..47180c6a1368 --- /dev/null +++ b/src/libs/navigateAfterJoinRequest/index.desktop.ts @@ -0,0 +1,8 @@ +import Navigation from '@navigation/Navigation'; +import ROUTES from '@src/ROUTES'; + +const navigateAfterJoinRequest = () => { + Navigation.goBack(undefined, false, true); + Navigation.navigate(ROUTES.SETTINGS_WORKSPACES); +}; +export default navigateAfterJoinRequest; diff --git a/src/libs/navigateAfterJoinRequest/index.ts b/src/libs/navigateAfterJoinRequest/index.ts new file mode 100644 index 000000000000..b9e533208ec2 --- /dev/null +++ b/src/libs/navigateAfterJoinRequest/index.ts @@ -0,0 +1,8 @@ +import Navigation from '@navigation/Navigation'; +import ROUTES from '@src/ROUTES'; + +const navigateAfterJoinRequest = () => { + Navigation.goBack(undefined, false, true); + Navigation.navigate(ROUTES.ALL_SETTINGS); +}; +export default navigateAfterJoinRequest; diff --git a/src/libs/navigateAfterJoinRequest/index.web.ts b/src/libs/navigateAfterJoinRequest/index.web.ts new file mode 100644 index 000000000000..47180c6a1368 --- /dev/null +++ b/src/libs/navigateAfterJoinRequest/index.web.ts @@ -0,0 +1,8 @@ +import Navigation from '@navigation/Navigation'; +import ROUTES from '@src/ROUTES'; + +const navigateAfterJoinRequest = () => { + Navigation.goBack(undefined, false, true); + Navigation.navigate(ROUTES.SETTINGS_WORKSPACES); +}; +export default navigateAfterJoinRequest; diff --git a/src/pages/DetailsPage.tsx b/src/pages/DetailsPage.tsx index a9adb5310e58..b3b0f0782ba0 100755 --- a/src/pages/DetailsPage.tsx +++ b/src/pages/DetailsPage.tsx @@ -1,7 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; import Str from 'expensify-common/lib/str'; import React from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import AttachmentModal from '@components/AttachmentModal'; @@ -15,6 +15,7 @@ import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/EnablePayments/TermsStep.js b/src/pages/EnablePayments/TermsStep.js index 9fa3a4becea3..a55816d207be 100644 --- a/src/pages/EnablePayments/TermsStep.js +++ b/src/pages/EnablePayments/TermsStep.js @@ -1,9 +1,9 @@ import React, {useEffect, useState} from 'react'; -import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; diff --git a/src/pages/FlagCommentPage.tsx b/src/pages/FlagCommentPage.tsx index 00c38dabc4ec..216196c17d55 100644 --- a/src/pages/FlagCommentPage.tsx +++ b/src/pages/FlagCommentPage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {SvgProps} from 'react-native-svg'; import type {ValueOf} from 'type-fest'; @@ -9,6 +9,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/pages/GetAssistancePage.tsx b/src/pages/GetAssistancePage.tsx index 948e0c239de9..b543524fc68e 100644 --- a/src/pages/GetAssistancePage.tsx +++ b/src/pages/GetAssistancePage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -8,6 +8,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import type {MenuItemWithLink} from '@components/MenuItemList'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/KeyboardShortcutsPage.tsx b/src/pages/KeyboardShortcutsPage.tsx index 9b70defbf8af..d68643e74a5a 100644 --- a/src/pages/KeyboardShortcutsPage.tsx +++ b/src/pages/KeyboardShortcutsPage.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItem from '@components/MenuItem'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/pages/OnboardEngagement/ManageTeamsExpensesPage.tsx b/src/pages/OnboardEngagement/ManageTeamsExpensesPage.tsx index f27c821abd8c..559da335cf13 100644 --- a/src/pages/OnboardEngagement/ManageTeamsExpensesPage.tsx +++ b/src/pages/OnboardEngagement/ManageTeamsExpensesPage.tsx @@ -1,5 +1,5 @@ import React, {useMemo} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -7,6 +7,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import type {MenuItemProps} from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/pages/OnboardEngagement/PurposeForUsingExpensifyPage.tsx b/src/pages/OnboardEngagement/PurposeForUsingExpensifyPage.tsx index 747b23e943ca..3c7520b850b4 100644 --- a/src/pages/OnboardEngagement/PurposeForUsingExpensifyPage.tsx +++ b/src/pages/OnboardEngagement/PurposeForUsingExpensifyPage.tsx @@ -1,5 +1,5 @@ import React, {useCallback, useMemo} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -8,6 +8,7 @@ import LottieAnimations from '@components/LottieAnimations'; import type {MenuItemProps} from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.tsx b/src/pages/PrivateNotes/PrivateNotesListPage.tsx index 0a6a2659ffb6..1893f81da2fe 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesListPage.tsx @@ -1,11 +1,11 @@ import React, {useMemo} from 'react'; -import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index a4c740250908..cc533dbc3a08 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -2,7 +2,7 @@ import Str from 'expensify-common/lib/str'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useEffect} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import AutoUpdateTime from '@components/AutoUpdateTime'; @@ -17,6 +17,7 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 75ae02587486..e18155ea6139 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -1,7 +1,7 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -11,6 +11,7 @@ import * as Illustrations from '@components/Icon/Illustrations'; import MenuItem from '@components/MenuItem'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; diff --git a/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.tsx b/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.tsx index b128d6dc75e8..af4b251952de 100644 --- a/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.tsx +++ b/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.tsx @@ -1,10 +1,11 @@ import React, {useMemo} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/ConfirmationUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/ConfirmationUBO.tsx index 2b742ad65699..4228b1da9d12 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/ConfirmationUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/ConfirmationUBO.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/CompanyOwnersListUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/CompanyOwnersListUBO.tsx index 25ce2d7b81da..42bf43d78910 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/CompanyOwnersListUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/CompanyOwnersListUBO.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx index 6a94a7b456f3..a5b839118edc 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx @@ -1,6 +1,5 @@ import type {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import React, {useMemo} from 'react'; -import {ScrollView} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; @@ -8,6 +7,7 @@ import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/ReimbursementAccount/ConnectBankAccount/components/FinishChatCard.tsx b/src/pages/ReimbursementAccount/ConnectBankAccount/components/FinishChatCard.tsx index 2bf76d714cf5..65f7f14d6c91 100644 --- a/src/pages/ReimbursementAccount/ConnectBankAccount/components/FinishChatCard.tsx +++ b/src/pages/ReimbursementAccount/ConnectBankAccount/components/FinishChatCard.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import {ScrollView} from 'react-native'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import MenuItem from '@components/MenuItem'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js b/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js index d1ac0989ae38..9c28fe928d33 100644 --- a/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js +++ b/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js @@ -1,7 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; -import {ScrollView} from 'react-native'; import _ from 'underscore'; import Button from '@components/Button'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -10,6 +9,7 @@ import * as Illustrations from '@components/Icon/Illustrations'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; diff --git a/src/pages/ReimbursementAccount/EnableBankAccount/EnableBankAccount.tsx b/src/pages/ReimbursementAccount/EnableBankAccount/EnableBankAccount.tsx index fd2f05493098..4c4bd9a20b71 100644 --- a/src/pages/ReimbursementAccount/EnableBankAccount/EnableBankAccount.tsx +++ b/src/pages/ReimbursementAccount/EnableBankAccount/EnableBankAccount.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import {ScrollView} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -10,6 +9,7 @@ import * as Illustrations from '@components/Icon/Illustrations'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/Confirmation.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/Confirmation.tsx index b4272f094071..f05bb70bcd5a 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/Confirmation.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/Confirmation.tsx @@ -1,10 +1,11 @@ import React, {useMemo} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/ReimbursementAccount/RequestorOnfidoStep.js b/src/pages/ReimbursementAccount/RequestorOnfidoStep.js index 8cca56779059..fac405090de7 100644 --- a/src/pages/ReimbursementAccount/RequestorOnfidoStep.js +++ b/src/pages/ReimbursementAccount/RequestorOnfidoStep.js @@ -1,12 +1,12 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; -import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Onfido from '@components/Onfido'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Growl from '@libs/Growl'; diff --git a/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx b/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx index d17166365a39..cb9763b5cc25 100644 --- a/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx +++ b/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx @@ -1,5 +1,5 @@ import React, {useCallback} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; @@ -8,6 +8,7 @@ import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; // @ts-expect-error TODO: Remove this once Onfido (https://github.com/Expensify/App/issues/25136) is migrated to TypeScript. import Onfido from '@components/Onfido'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Growl from '@libs/Growl'; diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index e94c0cc80952..d96e01c1a4d3 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -1,7 +1,7 @@ import {useRoute} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; import React, {useEffect, useMemo} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -17,6 +17,7 @@ import ParentNavigationSubtitle from '@components/ParentNavigationSubtitle'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import RoomHeaderAvatars from '@components/RoomHeaderAvatars'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index f2bba4b17a9a..4f1bac01b556 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -1,5 +1,5 @@ import React, {useMemo, useRef} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {ImageSourcePropType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png'; @@ -10,6 +10,7 @@ import MenuItem from '@components/MenuItem'; import QRShareWithDownload from '@components/QRShare/QRShareWithDownload'; import type QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/home/ReportScreenContext.ts b/src/pages/home/ReportScreenContext.ts index e9440ab932d6..6f177098c2c4 100644 --- a/src/pages/home/ReportScreenContext.ts +++ b/src/pages/home/ReportScreenContext.ts @@ -1,8 +1,9 @@ import type {RefObject, SyntheticEvent} from 'react'; import {createContext} from 'react'; -import type {FlatList, GestureResponderEvent, View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {FlatList, GestureResponderEvent, Text, View} from 'react-native'; -type ReactionListAnchor = View | HTMLDivElement | null; +type ReactionListAnchor = View | Text | HTMLDivElement | null; type ReactionListEvent = GestureResponderEvent | MouseEvent | SyntheticEvent; diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx index 4f6e0548eb72..974a8824f5ff 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -22,7 +22,7 @@ import type {Beta, ReportAction, ReportActions} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {ContextMenuAction, ContextMenuActionPayload} from './ContextMenuActions'; import ContextMenuActions from './ContextMenuActions'; -import type {ContextMenuType} from './ReportActionContextMenu'; +import type {ContextMenuAnchor, ContextMenuType} from './ReportActionContextMenu'; import {hideContextMenu, showContextMenu} from './ReportActionContextMenu'; type BaseReportActionContextMenuOnyxProps = { @@ -64,7 +64,7 @@ type BaseReportActionContextMenuProps = BaseReportActionContextMenuOnyxProps & { type?: ContextMenuType; /** Target node which is the target of ContentMenu */ - anchor?: MutableRefObject; + anchor?: MutableRefObject; /** Flag to check if the chat participant is Chronos */ isChronosReport?: boolean; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 831b32def2bb..ffdbcab577b7 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -1,7 +1,8 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import type {MutableRefObject} from 'react'; import React from 'react'; -import type {GestureResponderEvent} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {GestureResponderEvent, Text, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -13,6 +14,7 @@ import EmailUtils from '@libs/EmailUtils'; import * as Environment from '@libs/Environment/Environment'; import fileDownload from '@libs/fileDownload'; import getAttachmentDetails from '@libs/fileDownload/getAttachmentDetails'; +import * as Localize from '@libs/Localize'; import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; @@ -28,6 +30,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; import type {Beta, ReportAction, ReportActionReactions, Report as ReportType} from '@src/types/onyx'; import type IconAsset from '@src/types/utils/IconAsset'; +import type {ContextMenuAnchor} from './ReportActionContextMenu'; import {hideContextMenu, showDeleteModal} from './ReportActionContextMenu'; /** Gets the HTML version of the message in an action */ @@ -52,7 +55,7 @@ type ShouldShow = ( reportAction: OnyxEntry, isArchivedRoom: boolean, betas: OnyxEntry, - menuTarget: MutableRefObject | undefined, + menuTarget: MutableRefObject | undefined, isChronosReport: boolean, reportID: string, isPinnedChat: boolean, @@ -69,6 +72,8 @@ type ContextMenuActionPayload = { close: () => void; openContextMenu: () => void; interceptAnonymousUser: (callback: () => void, isAnonymousAction?: boolean) => void; + anchor?: MutableRefObject; + checkIfContextMenuActive?: () => void; openOverflowMenu: (event: GestureResponderEvent | MouseEvent) => void; event?: GestureResponderEvent | MouseEvent | KeyboardEvent; setIsEmojiPickerActive?: (state: boolean) => void; @@ -342,9 +347,8 @@ const ContextMenuActions: ContextMenuAction[] = [ // `ContextMenuItem` with `successText` and `successIcon` which will fall back to // the `text` and `icon` onPress: (closePopover, {reportAction, selection, reportID}) => { - const isTaskAction = ReportActionsUtils.isTaskAction(reportAction); const isReportPreviewAction = ReportActionsUtils.isReportPreviewAction(reportAction); - const messageHtml = isTaskAction ? TaskUtils.getTaskReportActionMessage(reportAction?.actionName) : getActionHtml(reportAction); + const messageHtml = getActionHtml(reportAction); const messageText = ReportActionsUtils.getReportActionMessageText(reportAction); const isAttachment = ReportActionsUtils.isReportActionAttachment(reportAction); @@ -354,6 +358,9 @@ const ContextMenuActions: ContextMenuAction[] = [ const iouReport = ReportUtils.getReport(ReportActionsUtils.getIOUReportIDFromReportActionPreview(reportAction)); const displayMessage = ReportUtils.getReportPreviewMessage(iouReport, reportAction); Clipboard.setString(displayMessage); + } else if (ReportActionsUtils.isTaskAction(reportAction)) { + const displayMessage = TaskUtils.getTaskReportActionMessage(reportAction).text; + Clipboard.setString(displayMessage); } else if (ReportActionsUtils.isModifiedExpenseAction(reportAction)) { const modifyExpenseMessage = ModifiedExpenseMessage.getForReportAction(reportID, reportAction); Clipboard.setString(modifyExpenseMessage); @@ -376,6 +383,10 @@ const ContextMenuActions: ContextMenuAction[] = [ } else if (ReportActionsUtils.isActionableMentionWhisper(reportAction)) { const mentionWhisperMessage = ReportActionsUtils.getActionableMentionWhisperMessage(reportAction); setClipboardMessage(mentionWhisperMessage); + } else if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD) { + Clipboard.setString(Localize.translateLocal('iou.heldRequest', {comment: reportAction.message?.[1]?.text ?? ''})); + } else if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.UNHOLD) { + Clipboard.setString(Localize.translateLocal('iou.unheldRequest')); } else if (content) { setClipboardMessage(content); } else if (messageText) { @@ -399,7 +410,7 @@ const ContextMenuActions: ContextMenuAction[] = [ const isAttachment = ReportActionsUtils.isReportActionAttachment(reportAction); // Only hide the copylink menu item when context menu is opened over img element. - const isAttachmentTarget = menuTarget?.current?.tagName === 'IMG' && isAttachment; + const isAttachmentTarget = menuTarget?.current && 'tagName' in menuTarget.current && menuTarget?.current.tagName === 'IMG' && isAttachment; return Permissions.canUseCommentLinking(betas) && type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && !isAttachmentTarget && !ReportActionsUtils.isMessageDeleted(reportAction); }, onPress: (closePopover, {reportAction, reportID}) => { diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/types.ts b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/types.ts index 98b38dcb6968..b7c3d6214094 100644 --- a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/types.ts +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/types.ts @@ -1,6 +1,6 @@ import type {BaseReportActionContextMenuProps} from '@pages/home/report/ContextMenu/BaseReportActionContextMenu'; -type MiniReportActionContextMenuProps = Omit & { +type MiniReportActionContextMenuProps = Omit & { /** Should the reportAction this menu is attached to have the appearance of being grouped with the previous reportAction? */ displayAsGroup?: boolean; }; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 862d5f01c2fc..931b87704ce5 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -67,8 +67,8 @@ function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef(null); const anchorRef = useRef(null); const dimensionsEventListener = useRef(null); - const contextMenuAnchorRef = useRef(null); - const contextMenuTargetNode = useRef(null); + const contextMenuAnchorRef = useRef(null); + const contextMenuTargetNode = useRef(null); const onPopoverShow = useRef(() => {}); const onPopoverHide = useRef(() => {}); @@ -83,7 +83,7 @@ function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef new Promise((resolve) => { - if (contextMenuAnchorRef.current && typeof contextMenuAnchorRef.current.measureInWindow === 'function') { + if (contextMenuAnchorRef.current && 'measureInWindow' in contextMenuAnchorRef.current && typeof contextMenuAnchorRef.current.measureInWindow === 'function') { contextMenuAnchorRef.current.measureInWindow((x, y) => resolve({x, y})); } else { resolve({x: 0, y: 0}); @@ -169,7 +169,7 @@ function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef { const {pageX = 0, pageY = 0} = extractPointerEvent(event); contextMenuAnchorRef.current = contextMenuAnchor; - contextMenuTargetNode.current = event.target as HTMLElement; + contextMenuTargetNode.current = event.target as HTMLDivElement; if (shouldCloseOnTarget) { anchorRef.current = event.target as HTMLDivElement; } else { diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts index f2537c56a5af..21c1eea18e03 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts @@ -16,7 +16,7 @@ type OnCancel = () => void; type ContextMenuType = ValueOf; -type ContextMenuAnchor = View | RNText | null | undefined; +type ContextMenuAnchor = View | RNText | HTMLDivElement | null | undefined; type ShowContextMenu = ( type: ContextMenuType, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.tsx similarity index 51% rename from src/pages/home/report/ReportActionItem.js rename to src/pages/home/report/ReportActionItem.tsx index fb4a1f52b51a..744f0afb857b 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.tsx @@ -1,9 +1,11 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import lodashIsEqual from 'lodash/isEqual'; +import lodashIsEmpty from 'lodash/isEmpty'; import React, {memo, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import type {GestureResponderEvent, TextInput} from 'react-native'; import {InteractionManager, View} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {Emoji} from '@assets/emojis/types'; import Button from '@components/Button'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; @@ -12,11 +14,11 @@ import * as Expensicons from '@components/Icon/Expensicons'; import InlineSystemMessage from '@components/InlineSystemMessage'; import KYCWall from '@components/KYCWall'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {usePersonalDetails, withBlockedFromConcierge, withNetwork, withReportActionsDrafts} from '@components/OnyxProvider'; +import {useBlockedFromConcierge, usePersonalDetails, useReportActionsDrafts} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; -import EmojiReactionsPropTypes from '@components/Reactions/EmojiReactionsPropTypes'; import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions'; import RenderHTML from '@components/RenderHTML'; +import type {ActionableItem} from '@components/ReportActionItem/ActionableItemButtons'; import ActionableItemButtons from '@components/ReportActionItem/ActionableItemButtons'; import ChronosOOOListActions from '@components/ReportActionItem/ChronosOOOListActions'; import MoneyReportView from '@components/ReportActionItem/MoneyReportView'; @@ -30,14 +32,13 @@ import TaskView from '@components/ReportActionItem/TaskView'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import UnreadActionIndicator from '@components/UnreadActionIndicator'; -import withLocalize from '@components/withLocalize'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; +import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useReportScrollManager from '@hooks/useReportScrollManager'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import ControlSelection from '@libs/ControlSelection'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ErrorUtils from '@libs/ErrorUtils'; @@ -49,11 +50,10 @@ import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import SelectionScraper from '@libs/SelectionScraper'; -import userWalletPropTypes from '@pages/EnablePayments/userWalletPropTypes'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; -import reportPropTypes from '@pages/reportPropTypes'; import * as BankAccounts from '@userActions/BankAccounts'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; +import * as Policy from '@userActions/Policy'; import * as store from '@userActions/ReimbursementAccount/store'; import * as Report from '@userActions/Report'; import * as ReportActions from '@userActions/ReportActions'; @@ -62,6 +62,9 @@ import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {OriginalMessageActionableMentionWhisper, OriginalMessageJoinPolicyChangeLog} from '@src/types/onyx/OriginalMessage'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; @@ -75,233 +78,258 @@ import ReportActionItemMessage from './ReportActionItemMessage'; import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; -import reportActionPropTypes from './reportActionPropTypes'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import transactionPropTypes from '@components/transactionPropTypes'; -const propTypes = { - ...windowDimensionsPropTypes, +const getDraftMessage = (drafts: OnyxCollection, reportID: string, action: OnyxTypes.ReportAction): string | undefined => { + const originalReportID = ReportUtils.getOriginalReportID(reportID, action); + const draftKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`; + const draftMessage = drafts?.[draftKey]?.[action.reportActionID]; + return typeof draftMessage === 'string' ? draftMessage : draftMessage?.message; +}; - /** Report for this action */ - report: reportPropTypes.isRequired, +type ReportActionItemOnyxProps = { + /** Stores user's preferred skin tone */ + preferredSkinTone: OnyxEntry; - /** All the data of the action item */ - action: PropTypes.shape(reportActionPropTypes).isRequired, + /** All reports shared with the user */ + reports: OnyxCollection; - /** Should the comment have the appearance of being grouped with the previous comment? */ - displayAsGroup: PropTypes.bool.isRequired, + /** IOU report for this action, if any */ + iouReport: OnyxEntry; - /** Is this the most recent IOU Action? */ - isMostRecentIOUReportAction: PropTypes.bool.isRequired, + emojiReactions: OnyxEntry; - /** Should we display the new marker on top of the comment? */ - shouldDisplayNewMarker: PropTypes.bool.isRequired, + /** The user's wallet account */ + userWallet: OnyxEntry; - /** Determines if the avatar is displayed as a subscript (positioned lower than normal) */ - shouldShowSubscriptAvatar: PropTypes.bool, + /** All the report actions belonging to the report's parent */ + parentReportActions: OnyxEntry; - /** Position index of the report action in the overall report FlatList view */ - index: PropTypes.number.isRequired, + /** All policy report fields */ + policyReportFields: OnyxEntry; - /** Draft message - if this is set the comment is in 'edit' mode */ - draftMessage: PropTypes.string, + /** The policy which the user has access to and which the report is tied to */ + policy: OnyxEntry; - /** Stores user's preferred skin tone */ - preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + /** Array of report actions for this report */ + reportActions: OnyxEntry; - emojiReactions: EmojiReactionsPropTypes, + /** All the transactions shared with the user */ + transactions: OnyxCollection; +}; - /** All reports shared with the user */ - reports: PropTypes.objectOf(reportPropTypes), +type ReportActionItemProps = { + /** Report for this action */ + report: OnyxTypes.Report; - /** IOU report for this action, if any */ - iouReport: reportPropTypes, + /** All the data of the action item */ + action: OnyxTypes.ReportAction; - /** Flag to show, hide the thread divider line */ - shouldHideThreadDividerLine: PropTypes.bool, + /** Should the comment have the appearance of being grouped with the previous comment? */ + displayAsGroup: boolean; - /** The user's wallet account */ - userWallet: userWalletPropTypes, + /** Is this the most recent IOU Action? */ + isMostRecentIOUReportAction: boolean; - /** All the report actions belonging to the report's parent */ - parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + /** Should we display the new marker on top of the comment? */ + shouldDisplayNewMarker: boolean; - /** All the report actions belonging to the current report */ - reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + /** Determines if the avatar is displayed as a subscript (positioned lower than normal) */ + shouldShowSubscriptAvatar?: boolean; - /** All the transactions shared wit hthe user */ - transactions: PropTypes.objectOf(PropTypes.shape(transactionPropTypes)), + /** Position index of the report action in the overall report FlatList view */ + index: number; - /** Callback to be called on onPress */ - onPress: PropTypes.func, -}; + /** Flag to show, hide the thread divider line */ + shouldHideThreadDividerLine?: boolean; -const defaultProps = { - draftMessage: undefined, - preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, - emojiReactions: {}, - shouldShowSubscriptAvatar: false, - reports: {}, - iouReport: undefined, - shouldHideThreadDividerLine: false, - userWallet: {}, - parentReportActions: {}, - reportActions: {}, - transactions: {}, - onPress: undefined, -}; + linkedReportActionID?: string; -function ReportActionItem(props) { + /** Callback to be called on onPress */ + onPress?: () => void; +} & ReportActionItemOnyxProps; + +const isIOUReport = (actionObj: OnyxEntry): actionObj is OnyxTypes.ReportActionBase & OnyxTypes.OriginalMessageIOU => + actionObj?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; + +function ReportActionItem({ + action, + report, + reports, + linkedReportActionID, + displayAsGroup, + emojiReactions, + index, + iouReport, + isMostRecentIOUReportAction, + parentReportActions, + preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, + shouldDisplayNewMarker, + userWallet, + shouldHideThreadDividerLine = false, + shouldShowSubscriptAvatar = false, + policyReportFields, + policy, + reportActions, + transactions, + onPress = undefined, +}: ReportActionItemProps) { + const {translate} = useLocalize(); + const {isSmallScreenWidth} = useWindowDimensions(); + const blockedFromConcierge = useBlockedFromConcierge(); + const reportActionDrafts = useReportActionsDrafts(); + const draftMessage = useMemo(() => getDraftMessage(reportActionDrafts, report.reportID, action), [action, report.reportID, reportActionDrafts]); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; - const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)); - const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); + const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); + const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); const [isHidden, setIsHidden] = useState(false); - const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); + const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); const reactionListRef = useContext(ReactionListContext); const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); - const textInputRef = useRef(); - const popoverAnchorRef = useRef(); - const downloadedPreviews = useRef([]); - const prevDraftMessage = usePrevious(props.draftMessage); - const originalReportID = ReportUtils.getOriginalReportID(props.report.reportID, props.action); - const originalReport = props.report.reportID === originalReportID ? props.report : ReportUtils.getReport(originalReportID); - const isReportActionLinked = props.linkedReportActionID && props.action.reportActionID && props.linkedReportActionID === props.action.reportActionID; - const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(props.reportActions); - let transaction = {}; + const textInputRef = useRef(); + const popoverAnchorRef = useRef(null); + const downloadedPreviews = useRef([]); + const prevDraftMessage = usePrevious(draftMessage); + const originalReportID = ReportUtils.getOriginalReportID(report.reportID, action); + const originalReport = report.reportID === originalReportID ? report : ReportUtils.getReport(originalReportID); + const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; + const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(reportActions); const transactionThreadReport = useMemo(() => { - if (transactionThreadReportID === '0') { - return {}; - } - const report = props.reports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? {}; + return reports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`]; + }, [reports, transactionThreadReportID]); - // Get the transaction associated with the report - const transactionID = props.reportActions?.[report.parentReportActionID ?? '']?.originalMessage?.IOUTransactionID ?? 0; - transaction = props.transactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; - return report; - }, [props.reports, transactionThreadReportID, props.reportActions, props.transactions]); + // Get the transaction associated with the report + const transaction = useMemo(() => { + const reportAction = reportActions?.[transactionThreadReport?.parentReportActionID ?? '']; + const transactionID = reportAction?.originalMessage?.IOUReportID ?? 0; + return transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + }, [transactionThreadReport, reportActions, transactions]); - const transactionCurrency = !_.isEmpty(transaction) ? (transaction.modifiedCurrency ?? transaction.currency) : props.report.currency; + const transactionCurrency = !lodashIsEmpty(transaction) ? (transaction?.modifiedCurrency ?? transaction?.currency) : report.currency; const reportScrollManager = useReportScrollManager(); const highlightedBackgroundColorIfNeeded = useMemo( () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.hoverComponentBG) : {}), [StyleUtils, isReportActionLinked, theme.hoverComponentBG], ); - const originalMessage = lodashGet(props.action, 'originalMessage', {}); - const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(props.action); - const prevActionResolution = usePrevious(lodashGet(props.action, 'originalMessage.resolution', null)); + + const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); + const prevActionResolution = usePrevious(ReportActionsUtils.isActionableMentionWhisper(action) ? action.originalMessage.resolution : null); // IOUDetails only exists when we are sending money - const isSendingMoney = originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && _.has(originalMessage, 'IOUDetails'); + const isSendingMoney = isIOUReport(action) && action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && action.originalMessage.IOUDetails; const updateHiddenState = useCallback( - (isHiddenValue) => { + (isHiddenValue: boolean) => { setIsHidden(isHiddenValue); - const isAttachment = ReportUtils.isReportMessageAttachment(_.last(props.action.message)); + const isAttachment = ReportUtils.isReportMessageAttachment(action.message?.at(-1)); if (!isAttachment) { return; } - updateHiddenAttachments(props.action.reportActionID, isHiddenValue); + updateHiddenAttachments(action.reportActionID, isHiddenValue); }, - [props.action.reportActionID, props.action.message, updateHiddenAttachments], + [action.reportActionID, action.message, updateHiddenAttachments], ); useEffect( () => () => { // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components, // we should also hide them when the current component is destroyed - if (ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)) { + if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { ReportActionContextMenu.hideContextMenu(); ReportActionContextMenu.hideDeleteModal(); } - if (EmojiPickerAction.isActive(props.action.reportActionID)) { + if (EmojiPickerAction.isActive(action.reportActionID)) { EmojiPickerAction.hideEmojiPicker(true); } - if (reactionListRef.current && reactionListRef.current.isActiveReportAction(props.action.reportActionID)) { - reactionListRef.current.hideReactionList(); + if (reactionListRef?.current?.isActiveReportAction(action.reportActionID)) { + reactionListRef?.current?.hideReactionList(); } }, - [props.action.reportActionID, reactionListRef], + [action.reportActionID, reactionListRef], ); useEffect(() => { // We need to hide EmojiPicker when this is a deleted parent action - if (!isDeletedParentAction || !EmojiPickerAction.isActive(props.action.reportActionID)) { + if (!isDeletedParentAction || !EmojiPickerAction.isActive(action.reportActionID)) { return; } EmojiPickerAction.hideEmojiPicker(true); - }, [isDeletedParentAction, props.action.reportActionID]); + }, [isDeletedParentAction, action.reportActionID]); useEffect(() => { - if (!_.isUndefined(prevDraftMessage) || _.isUndefined(props.draftMessage)) { + if (prevDraftMessage !== undefined || draftMessage === undefined) { return; } focusTextInputAfterAnimation(textInputRef.current, 100); - }, [prevDraftMessage, props.draftMessage]); + }, [prevDraftMessage, draftMessage]); useEffect(() => { if (!Permissions.canUseLinkPreviews()) { return; } - const urls = ReportActionsUtils.extractLinksFromMessageHtml(props.action); - if (_.isEqual(downloadedPreviews.current, urls) || props.action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { + const urls = ReportActionsUtils.extractLinksFromMessageHtml(action); + if (lodashIsEqual(downloadedPreviews.current, urls) || action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { return; } downloadedPreviews.current = urls; - Report.expandURLPreview(props.report.reportID, props.action.reportActionID); - }, [props.action, props.report.reportID]); + Report.expandURLPreview(report.reportID, action.reportActionID); + }, [action, report.reportID]); useEffect(() => { - if (_.isUndefined(props.draftMessage) || !ReportActionsUtils.isDeletedAction(props.action)) { + if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { return; } - Report.deleteReportActionDraft(props.report.reportID, props.action); - }, [props.draftMessage, props.action, props.report.reportID]); + Report.deleteReportActionDraft(report.reportID, action); + }, [draftMessage, action, report.reportID]); // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator // Removed messages should not be shown anyway and should not need this flow - const latestDecision = lodashGet(props, ['action', 'message', 0, 'moderationDecision', 'decision'], ''); + const latestDecision = action.message?.[0].moderationDecision?.decision ?? ''; useEffect(() => { - if (props.action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT) { + if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT) { return; } // Hide reveal message button and show the message if latestDecision is changed to empty - if (_.isEmpty(latestDecision)) { + if (!latestDecision) { setModerationDecision(CONST.MODERATION.MODERATOR_DECISION_APPROVED); setIsHidden(false); return; } setModerationDecision(latestDecision); - if (!_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], latestDecision) && !ReportActionsUtils.isPendingRemove(props.action)) { + if ( + ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === latestDecision) && + !ReportActionsUtils.isPendingRemove(action) + ) { setIsHidden(true); return; } setIsHidden(false); - }, [latestDecision, props.action]); + }, [latestDecision, action]); const toggleContextMenuFromActiveReportAction = useCallback(() => { - setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)); - }, [props.action.reportActionID]); + setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); + }, [action.reportActionID]); /** * Show the ReportActionContextMenu modal popover. * - * @param {Object} [event] - A press event. + * @param [event] - A press event. */ const showPopover = useCallback( - (event) => { + (event: GestureResponderEvent | MouseEvent) => { // Block menu on the message being Edited or if the report action item has errors - if (!_.isUndefined(props.draftMessage) || !_.isEmpty(props.action.errors)) { + if (draftMessage !== undefined || !isEmptyObject(action.errors)) { return; } @@ -311,11 +339,11 @@ function ReportActionItem(props) { CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, event, selection, - popoverAnchorRef, - props.report.reportID, - props.action.reportActionID, + popoverAnchorRef.current, + report.reportID, + action.reportActionID, originalReportID, - props.draftMessage, + draftMessage ?? '', () => setIsContextMenuActive(true), toggleContextMenuFromActiveReportAction, ReportUtils.isArchivedRoom(originalReport), @@ -324,168 +352,184 @@ function ReportActionItem(props) { false, [], false, - setIsEmojiPickerActive, + setIsEmojiPickerActive as () => void, ); }, - [props.draftMessage, props.action, props.report.reportID, toggleContextMenuFromActiveReportAction, originalReport, originalReportID], + [draftMessage, action, report.reportID, toggleContextMenuFromActiveReportAction, originalReport, originalReportID], ); // Handles manual scrolling to the bottom of the chat when the last message is an actionable mention whisper and it's resolved. // This fixes an issue where InvertedFlatList fails to auto scroll down and results in an empty space at the bottom of the chat in IOS. useEffect(() => { - if (props.index !== 0 || !ReportActionsUtils.isActionableMentionWhisper(props.action)) { + if (index !== 0 || !ReportActionsUtils.isActionableMentionWhisper(action)) { return; } - if (prevActionResolution !== lodashGet(props.action, 'originalMessage.resolution', null)) { - reportScrollManager.scrollToIndex(props.index); + if (ReportActionsUtils.isActionableMentionWhisper(action) && prevActionResolution !== (action.originalMessage.resolution ?? null)) { + reportScrollManager.scrollToIndex(index); } - }, [props.index, props.action, prevActionResolution, reportScrollManager]); + }, [index, action, prevActionResolution, reportScrollManager]); const toggleReaction = useCallback( - (emoji) => { - Report.toggleEmojiReaction(props.report.reportID, props.action, emoji, props.emojiReactions); + (emoji: Emoji) => { + Report.toggleEmojiReaction(report.reportID, action, emoji, emojiReactions); }, - [props.report, props.action, props.emojiReactions], + [report, action, emojiReactions], ); const contextValue = useMemo( () => ({ - anchor: popoverAnchorRef, - report: props.report, - action: props.action, + anchor: popoverAnchorRef.current, + report, + action, checkIfContextMenuActive: toggleContextMenuFromActiveReportAction, }), - [props.report, props.action, toggleContextMenuFromActiveReportAction], + [report, action, toggleContextMenuFromActiveReportAction], ); - const actionableItemButtons = useMemo(() => { - if (!(ReportActionsUtils.isActionableMentionWhisper(props.action) && !lodashGet(props.action, 'originalMessage.resolution', null))) { + const actionableItemButtons: ActionableItem[] = useMemo(() => { + const isWhisperResolution = (action?.originalMessage as OriginalMessageActionableMentionWhisper['originalMessage'])?.resolution !== null; + const isJoinChoice = (action?.originalMessage as OriginalMessageJoinPolicyChangeLog['originalMessage'])?.choice === ''; + + if (!((ReportActionsUtils.isActionableMentionWhisper(action) && isWhisperResolution) || (ReportActionsUtils.isActionableJoinRequest(action) && isJoinChoice))) { return []; } + + if (ReportActionsUtils.isActionableJoinRequest(action)) { + return [ + { + text: 'actionableMentionJoinWorkspaceOptions.accept', + key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, + onPress: () => Policy.acceptJoinRequest(report.reportID, action), + isPrimary: true, + }, + { + text: 'actionableMentionJoinWorkspaceOptions.decline', + key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, + onPress: () => Policy.declineJoinRequest(report.reportID, action), + }, + ]; + } return [ { text: 'actionableMentionWhisperOptions.invite', - key: `${props.action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, - onPress: () => Report.resolveActionableMentionWhisper(props.report.reportID, props.action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), + key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, + onPress: () => Report.resolveActionableMentionWhisper(report.reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), isPrimary: true, }, { text: 'actionableMentionWhisperOptions.nothing', - key: `${props.action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => Report.resolveActionableMentionWhisper(props.report.reportID, props.action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), + key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, + onPress: () => Report.resolveActionableMentionWhisper(report.reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), }, ]; - }, [props.action, props.report.reportID]); + }, [action, report.reportID]); /** * Get the content of ReportActionItem - * @param {Boolean} hovered whether the ReportActionItem is hovered - * @param {Boolean} isWhisper whether the report action is a whisper - * @param {Boolean} hasErrors whether the report action has any errors - * @returns {Object} child component(s) + * @param hovered whether the ReportActionItem is hovered + * @param isWhisper whether the report action is a whisper + * @param hasErrors whether the report action has any errors + * @returns child component(s) */ - const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false) => { + const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false): React.JSX.Element => { let children; // Show the MoneyRequestPreview for when request was created, bill was split or money was sent if ( - props.action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && - originalMessage && + isIOUReport(action) && + action.originalMessage && // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message - (originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || isSendingMoney) + (action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || isSendingMoney) ) { // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID - const iouReportID = originalMessage.IOUReportID ? originalMessage.IOUReportID.toString() : '0'; + const iouReportID = action.originalMessage.IOUReportID ? action.originalMessage.IOUReportID.toString() : '0'; children = ( ); - } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW) { - children = ( + } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW) { + children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? ( + ${translate('parentReportAction.deletedReport')}`} /> + ) : ( ); - } else if ( - props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || - props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED || - props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED - ) { - children = ; - } else if (ReportActionsUtils.isCreatedTaskReportAction(props.action)) { + } else if (ReportActionsUtils.isTaskAction(action)) { + children = ; + } else if (ReportActionsUtils.isCreatedTaskReportAction(action)) { children = ( ); - } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(lodashGet(personalDetails, props.report.ownerAccountID)); - const paymentType = lodashGet(props.action, 'originalMessage.paymentType', ''); + } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED) { + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[report.ownerAccountID ?? -1]); + const paymentType = action.originalMessage.paymentType ?? ''; - const isSubmitterOfUnsettledReport = ReportUtils.isCurrentUserSubmitter(props.report.reportID) && !ReportUtils.isSettled(props.report.reportID); + const isSubmitterOfUnsettledReport = ReportUtils.isCurrentUserSubmitter(report.reportID) && !ReportUtils.isSettled(report.reportID); const shouldShowAddCreditBankAccountButton = isSubmitterOfUnsettledReport && !store.hasCreditBankAccount() && paymentType !== CONST.IOU.PAYMENT_TYPE.EXPENSIFY; const shouldShowEnableWalletButton = - isSubmitterOfUnsettledReport && - (_.isEmpty(props.userWallet) || props.userWallet.tierName === CONST.WALLET.TIER_NAME.SILVER) && - paymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY; + isSubmitterOfUnsettledReport && (isEmptyObject(userWallet) || userWallet?.tierName === CONST.WALLET.TIER_NAME.SILVER) && paymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY; children = ( <> {shouldShowAddCreditBankAccountButton && ( )} @@ -535,49 +579,46 @@ function ReportActionItem(props) { for example: Invite a user mentioned but not a member of the room https://github.com/Expensify/App/issues/32741 */} - {actionableItemButtons.length > 0 && ( - - )} + {actionableItemButtons.length > 0 && } ) : ( )} ); } - const numberOfThreadReplies = _.get(props, ['action', 'childVisibleActionCount'], 0); + const numberOfThreadReplies = action.childVisibleActionCount ?? 0; - const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(props.action, props.report.reportID); - const oldestFourAccountIDs = _.map(lodashGet(props.action, 'childOldestFourAccountIDs', '').split(','), (accountID) => Number(accountID)); - const draftMessageRightAlign = !_.isUndefined(props.draftMessage) ? styles.chatItemReactionsDraftRight : {}; + const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(action, report.reportID); + const oldestFourAccountIDs = + action.childOldestFourAccountIDs + ?.split(',') + .map((accountID) => Number(accountID)) + .filter((accountID): accountID is number => typeof accountID === 'number') ?? []; + const draftMessageRightAlign = draftMessage !== undefined ? styles.chatItemReactionsDraftRight : {}; return ( <> {children} - {Permissions.canUseLinkPreviews() && !isHidden && !_.isEmpty(props.action.linkMetadata) && ( - - !_.isEmpty(item))} /> + {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( + + !isEmptyObject(item))} /> )} - {!ReportActionsUtils.isMessageDeleted(props.action) && ( + {!ReportActionsUtils.isMessageDeleted(action) && ( { if (Session.isAnonymousUser()) { @@ -598,9 +639,9 @@ function ReportActionItem(props) { {shouldDisplayThreadReplies && ( { + const renderReportActionItem = (hovered: boolean, isWhisper: boolean, hasErrors: boolean): React.JSX.Element => { const content = renderItemContent(hovered || isContextMenuActive || isEmojiPickerActive, isWhisper, hasErrors); - if (!_.isUndefined(props.draftMessage)) { + if (draftMessage !== undefined) { return {content}; } - if (!props.displayAsGroup) { + if (!displayAsGroup) { return ( item === moderationDecision) && + !ReportActionsUtils.isPendingRemove(action) } > {content} @@ -648,23 +689,23 @@ function ReportActionItem(props) { return {content}; }; - if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { - const parentReportAction = props.parentReportActions[props.report.parentReportActionID]; + if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { + const parentReportAction = parentReportActions?.[report.parentReportActionID ?? ''] ?? null; if (ReportActionsUtils.isTransactionThread(parentReportAction)) { const isReversedTransaction = ReportActionsUtils.isReversedTransaction(parentReportAction); if (ReportActionsUtils.isDeletedParentAction(parentReportAction) || isReversedTransaction) { return ( - + - - + + ${props.translate(isReversedTransaction ? 'parentReportAction.reversedTransaction' : 'parentReportAction.deletedRequest')}`} + html={`${translate(isReversedTransaction ? 'parentReportAction.reversedTransaction' : 'parentReportAction.deletedRequest')}`} /> @@ -676,26 +717,26 @@ function ReportActionItem(props) { return ( ); } - if (ReportUtils.isTaskReport(props.report)) { - if (ReportUtils.isCanceledTaskReport(props.report, parentReportAction)) { + if (ReportUtils.isTaskReport(report)) { + if (ReportUtils.isCanceledTaskReport(report, parentReportAction)) { return ( - + - - + + - ${props.translate('parentReportAction.deletedTask')}`} /> + ${translate('parentReportAction.deletedTask')}`} /> @@ -704,44 +745,44 @@ function ReportActionItem(props) { ); } return ( - + - + ); } - if (ReportUtils.isExpenseReport(props.report) || ReportUtils.isIOUReport(props.report)) { + if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report)) { return ( - - {transactionThreadReport && !_.isEmpty(transactionThreadReport) ? ( + + {transactionThreadReport && !lodashIsEmpty(transactionThreadReport) ? ( <> - {transactionCurrency !== props.report.currency && ( + {transactionCurrency !== report.currency && ( )} ) : ( )} @@ -750,96 +791,94 @@ function ReportActionItem(props) { return ( ); } - if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED) { - return ; + if (action.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED) { + return ; } - if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.CHRONOSOOOLIST) { + if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CHRONOSOOOLIST) { return ( ); } // For the `pay` IOU action on non-send money flow, we don't want to render anything if `isWaitingOnBankAccount` is true // Otherwise, we will see two system messages informing the payee needs to add a bank account or wallet - if ( - props.action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && - lodashGet(props.report, 'isWaitingOnBankAccount', false) && - originalMessage && - originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - !isSendingMoney - ) { + if (isIOUReport(action) && !!report?.isWaitingOnBankAccount && action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && !isSendingMoney) { return null; } // if action is actionable mention whisper and resolved by user, then we don't want to render anything - if (ReportActionsUtils.isActionableMentionWhisper(props.action) && lodashGet(props.action, 'originalMessage.resolution', null)) { + if (ReportActionsUtils.isActionableMentionWhisper(action) && (action.originalMessage.resolution ?? null)) { return null; } // We currently send whispers to all report participants and hide them in the UI for users that shouldn't see them. // This is a temporary solution needed for comment-linking. // The long term solution will leverage end-to-end encryption and only targeted users will be able to decrypt. - if (ReportActionsUtils.isWhisperActionTargetedToOthers(props.action)) { + if (ReportActionsUtils.isWhisperActionTargetedToOthers(action)) { return null; } - const hasErrors = !_.isEmpty(props.action.errors); - const whisperedToAccountIDs = props.action.whisperedToAccountIDs || []; + const hasErrors = !isEmptyObject(action.errors); + const whisperedToAccountIDs = action.whisperedToAccountIDs ?? []; const isWhisper = whisperedToAccountIDs.length > 0; const isMultipleParticipant = whisperedToAccountIDs.length > 1; const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedToAccountIDs); - const whisperedToPersonalDetails = isWhisper ? _.filter(personalDetails, (details) => _.includes(whisperedToAccountIDs, details.accountID)) : []; + const whisperedToPersonalDetails = isWhisper + ? (Object.values(personalDetails ?? {}).filter((details) => whisperedToAccountIDs.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) + : []; const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; return ( props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPress={onPress} + style={[action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? styles.pointerEventsNone : styles.pointerEventsAuto]} + onPressIn={() => isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onSecondaryInteraction={showPopover} - preventDefaultContextMenu={_.isUndefined(props.draftMessage) && !hasErrors} + preventDefaultContextMenu={draftMessage === undefined && !hasErrors} withoutFocusOnSecondaryInteraction - accessibilityLabel={props.translate('accessibilityHints.chatMessage')} + accessibilityLabel={translate('accessibilityHints.chatMessage')} + accessible > {(hovered) => ( - {props.shouldDisplayNewMarker && } + {shouldDisplayNewMarker && } - + ReportActions.clearReportActionErrors(props.report.reportID, props.action)} + onClose={() => ReportActions.clearReportActionErrors(report.reportID, action)} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing pendingAction={ - !_.isUndefined(props.draftMessage) ? null : props.action.pendingAction || (props.action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : '') + draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) } - shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(props.action, props.report.reportID)} - errors={ErrorUtils.getLatestErrorMessageField(props.action)} + shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, report.reportID)} + errors={ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} errorRowStyles={[styles.ml10, styles.mr2]} - needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(props.action)} + needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} shouldDisableStrikeThrough > {isWhisper && ( @@ -852,11 +891,11 @@ function ReportActionItem(props) { /> - {props.translate('reportActionContextMenu.onlyVisible')} + {translate('reportActionContextMenu.onlyVisible')}   )} - {renderReportActionItem(hovered || isReportActionLinked, isWhisper, hasErrors)} + {renderReportActionItem(!!hovered || !!isReportActionLinked, isWhisper, hasErrors)} )} - + {/* @ts-expect-error TODO check if there is a field on the reportAction object */} + ); } -ReportActionItem.propTypes = propTypes; -ReportActionItem.defaultProps = defaultProps; - -export default compose( - withWindowDimensions, - withLocalize, - withNetwork(), - withBlockedFromConcierge({propName: 'blockedFromConcierge'}), - withReportActionsDrafts({ - propName: 'draftMessage', - transformValue: (drafts, props) => { - const originalReportID = ReportUtils.getOriginalReportID(props.report.reportID, props.action); - const draftKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`; - return lodashGet(drafts, [draftKey, props.action.reportActionID, 'message']); - }, - }), - withOnyx({ - preferredSkinTone: { - key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, - initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE, - }, - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - iouReport: { - key: ({action}) => { - const iouReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action); - return iouReportID ? `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}` : undefined; - }, - initialValue: {}, - }, - policyReportFields: { - key: ({report}) => (report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID}` : undefined), - initialValue: [], +export default withOnyx({ + preferredSkinTone: { + key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE, + }, + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + iouReport: { + key: ({action}) => { + const iouReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action); + return `${ONYXKEYS.COLLECTION.REPORT}${iouReportID ?? ''}`; }, - policy: { - key: ({report}) => (report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}` : undefined), - initialValue: {}, - }, - emojiReactions: { - key: ({action}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`, - initialValue: {}, - }, - userWallet: { - key: ONYXKEYS.USER_WALLET, - }, - parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID || 0}`, - canEvict: false, - }, - reportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID || 0}`, - canEvict: false, - }, - transactions: { - key: ONYXKEYS.COLLECTION.TRANSACTION, - }, - }), -)( + initialValue: {} as OnyxTypes.Report, + }, + policyReportFields: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID ?? ''}`, + initialValue: {}, + }, + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID ?? ''}`, + initialValue: {} as OnyxTypes.Policy, + }, + emojiReactions: { + key: ({action}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`, + initialValue: {}, + }, + userWallet: { + key: ONYXKEYS.USER_WALLET, + }, + parentReportActions: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID ?? 0}`, + canEvict: false, + }, + reportActions: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID || 0}`, + canEvict: false, + }, + transactions: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + }, +})( memo(ReportActionItem, (prevProps, nextProps) => { - const prevParentReportAction = prevProps.parentReportActions[prevProps.report.parentReportActionID]; - const nextParentReportAction = nextProps.parentReportActions[nextProps.report.parentReportActionID]; + const prevParentReportAction = prevProps.parentReportActions?.[prevProps.report.parentReportActionID ?? '']; + const nextParentReportAction = nextProps.parentReportActions?.[nextProps.report.parentReportActionID ?? '']; return ( prevProps.displayAsGroup === nextProps.displayAsGroup && - prevProps.draftMessage === nextProps.draftMessage && prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && - _.isEqual(prevProps.reports, nextProps.reports) && - _.isEqual(prevProps.emojiReactions, nextProps.emojiReactions) && - _.isEqual(prevProps.action, nextProps.action) && - _.isEqual(prevProps.iouReport, nextProps.iouReport) && - _.isEqual(prevProps.report.pendingFields, nextProps.report.pendingFields) && - _.isEqual(prevProps.report.isDeletedParentAction, nextProps.report.isDeletedParentAction) && - _.isEqual(prevProps.report.errorFields, nextProps.report.errorFields) && - lodashGet(prevProps.report, 'statusNum') === lodashGet(nextProps.report, 'statusNum') && - lodashGet(prevProps.report, 'stateNum') === lodashGet(nextProps.report, 'stateNum') && - lodashGet(prevProps.report, 'parentReportID') === lodashGet(nextProps.report, 'parentReportID') && - lodashGet(prevProps.report, 'parentReportActionID') === lodashGet(nextProps.report, 'parentReportActionID') && - prevProps.translate === nextProps.translate && + lodashIsEqual(prevProps.reports, nextProps.reports) && + lodashIsEqual(prevProps.emojiReactions, nextProps.emojiReactions) && + lodashIsEqual(prevProps.action, nextProps.action) && + lodashIsEqual(prevProps.iouReport, nextProps.iouReport) && + lodashIsEqual(prevProps.report.pendingFields, nextProps.report.pendingFields) && + lodashIsEqual(prevProps.report.isDeletedParentAction, nextProps.report.isDeletedParentAction) && + lodashIsEqual(prevProps.report.errorFields, nextProps.report.errorFields) && + prevProps.report?.statusNum === nextProps.report?.statusNum && + prevProps.report?.stateNum === nextProps.report?.stateNum && + prevProps.report?.parentReportID === nextProps.report?.parentReportID && + prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID && // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) && prevProps.action.actionName === nextProps.action.actionName && @@ -965,15 +986,15 @@ export default compose( ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) && prevProps.report.managerID === nextProps.report.managerID && prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && - lodashGet(prevProps.report, 'total', 0) === lodashGet(nextProps.report, 'total', 0) && - lodashGet(prevProps.report, 'nonReimbursableTotal', 0) === lodashGet(nextProps.report, 'nonReimbursableTotal', 0) && + prevProps.report?.total === nextProps.report?.total && + prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal && prevProps.linkedReportActionID === nextProps.linkedReportActionID && - _.isEqual(prevProps.policyReportFields, nextProps.policyReportFields) && - _.isEqual(prevProps.report.reportFields, nextProps.report.reportFields) && - _.isEqual(prevProps.policy, nextProps.policy) && - _.isEqual(prevParentReportAction, nextParentReportAction) && - _.isEqual(prevProps.reportActions, nextProps.reportActions) && - _.isEqual(prevProps.transactions, nextProps.transactions) + lodashIsEqual(prevProps.policyReportFields, nextProps.policyReportFields) && + lodashIsEqual(prevProps.report.reportFields, nextProps.report.reportFields) && + lodashIsEqual(prevProps.policy, nextProps.policy) && + lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && + lodashIsEqual(prevProps.transactions, nextProps.transactions) && + lodashIsEqual(prevParentReportAction, nextParentReportAction) ); }), ); diff --git a/src/pages/home/report/ReportActionItemBasicMessage.tsx b/src/pages/home/report/ReportActionItemBasicMessage.tsx index 35141a42b726..a28f2af24448 100644 --- a/src/pages/home/report/ReportActionItemBasicMessage.tsx +++ b/src/pages/home/report/ReportActionItemBasicMessage.tsx @@ -5,7 +5,7 @@ import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -type ReportActionItemBasicMessageProps = ChildrenProps & { +type ReportActionItemBasicMessageProps = Partial & { message: string; }; diff --git a/src/pages/home/report/ReportActionItemCreated.tsx b/src/pages/home/report/ReportActionItemCreated.tsx index 95578c10e816..4fe52f6adf41 100644 --- a/src/pages/home/report/ReportActionItemCreated.tsx +++ b/src/pages/home/report/ReportActionItemCreated.tsx @@ -35,7 +35,7 @@ type ReportActionItemCreatedProps = ReportActionItemCreatedOnyxProps & { /** The id of the policy */ // eslint-disable-next-line react/no-unused-prop-types - policyID: string; + policyID: string | undefined; }; function ReportActionItemCreated(props: ReportActionItemCreatedProps) { const styles = useThemeStyles(); diff --git a/src/pages/home/report/ReportActionItemFragment.tsx b/src/pages/home/report/ReportActionItemFragment.tsx index e16d94eb7db7..04391bb19cd5 100644 --- a/src/pages/home/report/ReportActionItemFragment.tsx +++ b/src/pages/home/report/ReportActionItemFragment.tsx @@ -70,6 +70,7 @@ const MUTED_ACTIONS = [ CONST.REPORT.ACTIONS.TYPE.IOU, CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.MOVED, + CONST.REPORT.ACTIONS.TYPE.ACTIONABLEJOINREQUEST, ] as ActionName[]; function ReportActionItemFragment({ diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 2c9a4cbd21e8..fbf2da69aa31 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -5,6 +5,7 @@ import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {Keyboard, View} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import Composer from '@components/Composer'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; @@ -58,7 +59,7 @@ type ReportActionItemMessageEditProps = { shouldDisableEmojiPicker?: boolean; /** Stores user's preferred skin tone */ - preferredSkinTone?: number; + preferredSkinTone?: OnyxEntry; }; // native ids @@ -69,7 +70,7 @@ const isMobileSafari = Browser.isMobileSafari(); function ReportActionItemMessageEdit( {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE}: ReportActionItemMessageEditProps, - forwardedRef: ForwardedRef, + forwardedRef: ForwardedRef<(TextInput & HTMLTextAreaElement) | undefined>, ) { const theme = useTheme(); const styles = useThemeStyles(); diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index af1c4e85104e..4a041fc495c0 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -83,7 +83,6 @@ function ReportActionItemParentAction({report, index = 0, shouldHideThreadDivide onClose={() => Report.navigateToConciergeChatAndDeleteReport(ancestor.report.reportID)} > Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID))} report={ancestor.report} action={ancestor.reportAction} diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 741422cc7e82..696cd7a7d850 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -1,6 +1,7 @@ import React, {useCallback, useMemo} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import Avatar from '@components/Avatar'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -29,7 +30,7 @@ import ReportActionItemFragment from './ReportActionItemFragment'; type ReportActionItemSingleProps = Partial & { /** All the data of the action */ - action: ReportAction; + action: OnyxEntry; /** Styles for the outermost View */ wrapperStyle?: StyleProp; @@ -38,7 +39,7 @@ type ReportActionItemSingleProps = Partial & { report: Report; /** IOU Report for this action, if any */ - iouReport?: Report; + iouReport?: OnyxEntry; /** Show header for action */ showHeader?: boolean; @@ -77,12 +78,12 @@ function ReportActionItemSingle({ const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT; - const actorAccountID = action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && iouReport ? iouReport.managerID : action.actorAccountID; + const actorAccountID = action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && iouReport ? iouReport.managerID : action?.actorAccountID; let displayName = ReportUtils.getDisplayNameForParticipant(actorAccountID); const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID ?? -1] ?? {}; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing let actorHint = (login || (displayName ?? '')).replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); - const displayAllActors = useMemo(() => action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && iouReport, [action.actionName, iouReport]); + const displayAllActors = useMemo(() => action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && iouReport, [action?.actionName, iouReport]); const isWorkspaceActor = ReportUtils.isPolicyExpenseChat(report) && (!actorAccountID || displayAllActors); let avatarSource = UserUtils.getAvatar(avatar ?? '', actorAccountID); @@ -90,7 +91,7 @@ function ReportActionItemSingle({ displayName = ReportUtils.getPolicyName(report); actorHint = displayName; avatarSource = ReportUtils.getWorkspaceAvatar(report); - } else if (action.delegateAccountID && personalDetails[action.delegateAccountID]) { + } else if (action?.delegateAccountID && personalDetails[action?.delegateAccountID]) { // We replace the actor's email, name, and avatar with the Copilot manually for now. And only if we have their // details. This will be improved upon when the Copilot feature is implemented. const delegateDetails = personalDetails[action.delegateAccountID]; @@ -141,7 +142,7 @@ function ReportActionItemSingle({ text: displayName, }, ] - : action.person; + : action?.person; const reportID = report?.reportID; const iouReportID = iouReport?.reportID; @@ -155,14 +156,14 @@ function ReportActionItemSingle({ Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(iouReportID)); return; } - showUserDetails(action.delegateAccountID ? String(action.delegateAccountID) : String(actorAccountID)); + showUserDetails(action?.delegateAccountID ? String(action.delegateAccountID) : String(actorAccountID)); } - }, [isWorkspaceActor, reportID, actorAccountID, action.delegateAccountID, iouReportID, displayAllActors]); + }, [isWorkspaceActor, reportID, actorAccountID, action?.delegateAccountID, iouReportID, displayAllActors]); const shouldDisableDetailPage = useMemo( () => CONST.RESTRICTED_ACCOUNT_IDS.includes(actorAccountID ?? 0) || - (!isWorkspaceActor && ReportUtils.isOptimisticPersonalDetail(action.delegateAccountID ? Number(action.delegateAccountID) : actorAccountID ?? -1)), + (!isWorkspaceActor && ReportUtils.isOptimisticPersonalDetail(action?.delegateAccountID ? Number(action.delegateAccountID) : actorAccountID ?? -1)), [action, isWorkspaceActor, actorAccountID], ); @@ -189,7 +190,7 @@ function ReportActionItemSingle({ return ( @@ -237,13 +238,13 @@ function ReportActionItemSingle({ {personArray?.map((fragment, index) => ( ))} @@ -255,7 +256,7 @@ function ReportActionItemSingle({ >{`${status?.emojiCode}`} )} - +
) : null} {children} diff --git a/src/pages/home/report/ReportActionItemThread.tsx b/src/pages/home/report/ReportActionItemThread.tsx index f7c7e5fcf91d..c0dbe2a3825d 100644 --- a/src/pages/home/report/ReportActionItemThread.tsx +++ b/src/pages/home/report/ReportActionItemThread.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type {GestureResponderEvent} from 'react-native'; import {View} from 'react-native'; import MultipleAvatars from '@components/MultipleAvatars'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; @@ -26,7 +27,7 @@ type ReportActionItemThreadProps = { isHovered: boolean; /** The function that should be called when the thread is LongPressed or right-clicked */ - onSecondaryInteraction: () => void; + onSecondaryInteraction: (event: GestureResponderEvent | MouseEvent) => void; }; function ReportActionItemThread({numberOfReplies, icons, mostRecentReply, childReportID, isHovered, onSecondaryInteraction}: ReportActionItemThreadProps) { diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 5e9d863dd62d..ca3ee7d2ab6a 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -194,7 +194,7 @@ function ReportActionsView(props) { return; } // Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments - Report.getOlderActions(reportID, oldestReportAction.reportActionID); + Report.getOlderActions(reportID); }, [props.isLoadingOlderReportActions, props.network.isOffline, oldestReportAction, reportID]); /** @@ -223,10 +223,9 @@ function ReportActionsView(props) { return; } - const newestReportAction = _.first(props.reportActions); - Report.getNewerActions(reportID, newestReportAction.reportActionID); + Report.getNewerActions(reportID); }, 500), - [props.isLoadingNewerReportActions, props.isLoadingInitialReportActions, props.reportActions, reportID, hasNewestReportAction], + [props.isLoadingNewerReportActions, props.isLoadingInitialReportActions, reportID, hasNewestReportAction], ); /** diff --git a/src/pages/home/sidebar/AllSettingsScreen.tsx b/src/pages/home/sidebar/AllSettingsScreen.tsx index a9e284329421..7151cc84e735 100644 --- a/src/pages/home/sidebar/AllSettingsScreen.tsx +++ b/src/pages/home/sidebar/AllSettingsScreen.tsx @@ -1,11 +1,11 @@ import React, {useMemo} from 'react'; -import {ScrollView} from 'react-native'; import type {OnyxCollection} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Breadcrumbs from '@components/Breadcrumbs'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemList from '@components/MenuItemList'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 8d7272df63e9..62b1adf1fb8c 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -10,6 +10,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TabSelector from '@components/TabSelector/TabSelector'; import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; @@ -62,6 +63,7 @@ const defaultProps = { function MoneyRequestSelectorPage(props) { const styles = useThemeStyles(); const [isDraggingOver, setIsDraggingOver] = useState(false); + const {canUseP2PDistanceRequests} = usePermissions(); const iouType = lodashGet(props.route, 'params.iouType', ''); const reportID = lodashGet(props.route, 'params.reportID', ''); @@ -75,7 +77,7 @@ function MoneyRequestSelectorPage(props) { const isFromGlobalCreate = !reportID; const isExpenseChat = ReportUtils.isPolicyExpenseChat(props.report); const isExpenseReport = ReportUtils.isExpenseReport(props.report); - const shouldDisplayDistanceRequest = isExpenseChat || isExpenseReport || isFromGlobalCreate; + const shouldDisplayDistanceRequest = canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate; const resetMoneyRequestInfo = () => { const moneyRequestID = `${iouType}${reportID}`; diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index 8e50577ede1f..b1ae257b792f 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -13,6 +13,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import TabSelector from '@components/TabSelector/TabSelector'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -80,6 +81,7 @@ function IOURequestStartPage({ }; const transactionRequestType = useRef(TransactionUtils.getRequestType(transaction)); const previousIOURequestType = usePrevious(transactionRequestType.current); + const {canUseP2PDistanceRequests} = usePermissions(); const isFromGlobalCreate = _.isEmpty(report.reportID); useFocusEffect( @@ -102,12 +104,12 @@ function IOURequestStartPage({ if (transaction.reportID === reportID) { return; } - IOU.initMoneyRequest(reportID, isFromGlobalCreate, transactionRequestType.current); - }, [transaction, reportID, iouType, isFromGlobalCreate]); + IOU.initMoneyRequest(reportID, policy, isFromGlobalCreate, transactionRequestType.current); + }, [transaction, policy, reportID, iouType, isFromGlobalCreate]); const isExpenseChat = ReportUtils.isPolicyExpenseChat(report); const isExpenseReport = ReportUtils.isExpenseReport(report); - const shouldDisplayDistanceRequest = isExpenseChat || isExpenseReport || isFromGlobalCreate; + const shouldDisplayDistanceRequest = canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate; // Allow the user to create the request if we are creating the request in global menu or the report can create the request const isAllowedToCreateRequest = _.isEmpty(report.reportID) || ReportUtils.canCreateRequest(report, policy, iouType); @@ -124,10 +126,10 @@ function IOURequestStartPage({ if (iouType === CONST.IOU.TYPE.SPLIT && transaction.isFromGlobalCreate) { IOU.updateMoneyRequestTypeParams(navigation.getState().routes, CONST.IOU.TYPE.REQUEST, newIouType); } - IOU.initMoneyRequest(reportID, isFromGlobalCreate, newIouType); + IOU.initMoneyRequest(reportID, policy, isFromGlobalCreate, newIouType); transactionRequestType.current = newIouType; }, - [previousIOURequestType, reportID, isFromGlobalCreate, iouType, navigation, transaction.isFromGlobalCreate], + [policy, previousIOURequestType, reportID, isFromGlobalCreate, iouType, navigation, transaction.isFromGlobalCreate], ); if (!transaction.transactionID) { diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 95dda131eab7..fb3a4d9457d5 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -14,6 +14,7 @@ import SelectionList from '@components/SelectionList'; import UserListItem from '@components/SelectionList/UserListItem'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import usePermissions from '@hooks/usePermissions'; import useSearchTermAndSearch from '@hooks/useSearchTermAndSearch'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -90,6 +91,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST; const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); + const {canUseP2PDistanceRequests} = usePermissions(); const offlineMessage = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''; @@ -120,18 +122,14 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // sees the option to request money from their admin on their own Workspace Chat. iouType === CONST.IOU.TYPE.REQUEST, - // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + canUseP2PDistanceRequests || iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, false, {}, [], false, {}, [], - - // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. - // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + canUseP2PDistanceRequests || iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, false, ); @@ -182,7 +180,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ } return [newSections, chatOptions]; - }, [didScreenTransitionEnd, reports, personalDetails, betas, searchTerm, participants, iouType, iouRequestType, maxParticipantsReached, translate]); + }, [didScreenTransitionEnd, reports, personalDetails, betas, searchTerm, participants, iouType, iouRequestType, maxParticipantsReached, canUseP2PDistanceRequests, translate]); /** * Adds a single participant to the request @@ -257,7 +255,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; - const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE; + const isAllowedToSplit = canUseP2PDistanceRequests || iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE; const handleConfirmSelection = useCallback(() => { if (shouldShowSplitBillErrorMessage) { diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index af1de64f8930..1b53dab12fa3 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import categoryPropTypes from '@components/categoryPropTypes'; import TagPicker from '@components/TagPicker'; @@ -11,7 +11,9 @@ import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import {canEditMoneyRequest} from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; @@ -78,10 +80,12 @@ function IOURequestStepTag({ const tag = TransactionUtils.getTag(transaction, tagIndex); const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); const parentReportAction = parentReportActions[report.parentReportActionID]; + const shouldShowTag = ReportUtils.isGroupPolicy(report) && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists)); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = isEditing && !canEditMoneyRequest(parentReportAction); + const shouldShowNotFoundPage = !shouldShowTag || (isEditing && !canEditMoneyRequest(parentReportAction)); const navigateBack = () => { Navigation.goBack(backTo); diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index cb1f73ae2207..55bf77e9ae88 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -1,11 +1,12 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native'; import type {ValueOf} from 'type-fest'; import BigNumberPad from '@components/BigNumberPad'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; +import ScrollView from '@components/ScrollView'; import TextInputWithCurrencySymbol from '@components/TextInputWithCurrencySymbol'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 3fde970327d7..1ad6488aeee9 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -14,6 +14,7 @@ import SelectionList from '@components/SelectionList'; import UserListItem from '@components/SelectionList/UserListItem'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import usePermissions from '@hooks/usePermissions'; import useSearchTermAndSearch from '@hooks/useSearchTermAndSearch'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -94,6 +95,7 @@ function MoneyRequestParticipantsSelector({ const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST; const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); + const {canUseP2PDistanceRequests} = usePermissions(); const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached); @@ -113,8 +115,7 @@ function MoneyRequestParticipantsSelector({ // sees the option to request money from their admin on their own Workspace Chat. iouType === CONST.IOU.TYPE.REQUEST, - // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - !isDistanceRequest, + canUseP2PDistanceRequests || !isDistanceRequest, false, {}, [], @@ -123,7 +124,7 @@ function MoneyRequestParticipantsSelector({ [], // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - !isDistanceRequest, + canUseP2PDistanceRequests || !isDistanceRequest, true, ); return { @@ -131,7 +132,7 @@ function MoneyRequestParticipantsSelector({ personalDetails: chatOptions.personalDetails, userToInvite: chatOptions.userToInvite, }; - }, [betas, reports, participants, personalDetails, searchTerm, iouType, isDistanceRequest]); + }, [betas, reports, participants, personalDetails, searchTerm, iouType, isDistanceRequest, canUseP2PDistanceRequests]); /** * Returns the sections needed for the OptionsSelector @@ -272,7 +273,7 @@ function MoneyRequestParticipantsSelector({ // the app from crashing on native when you try to do this, we'll going to show error message if you have a workspace and other participants const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; - const isAllowedToSplit = !isDistanceRequest && iouType !== CONST.IOU.TYPE.SEND; + const isAllowedToSplit = (canUseP2PDistanceRequests || !isDistanceRequest) && iouType !== CONST.IOU.TYPE.SEND; const handleConfirmSelection = useCallback(() => { if (shouldShowSplitBillErrorMessage) { diff --git a/src/pages/settings/AboutPage/AboutPage.tsx b/src/pages/settings/AboutPage/AboutPage.tsx index 3346b044ceca..0c087b2c93d6 100644 --- a/src/pages/settings/AboutPage/AboutPage.tsx +++ b/src/pages/settings/AboutPage/AboutPage.tsx @@ -1,5 +1,5 @@ import React, {useCallback, useMemo, useRef} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; // eslint-disable-next-line no-restricted-imports import type {GestureResponderEvent, Text as RNText, StyleProp, ViewStyle} from 'react-native'; import DeviceInfo from 'react-native-device-info'; @@ -9,6 +9,7 @@ import * as Illustrations from '@components/Icon/Illustrations'; import LottieAnimations from '@components/LottieAnimations'; import MenuItemList from '@components/MenuItemList'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; diff --git a/src/pages/settings/AppDownloadLinks.tsx b/src/pages/settings/AppDownloadLinks.tsx index 352b3772923a..e4165178ff2f 100644 --- a/src/pages/settings/AppDownloadLinks.tsx +++ b/src/pages/settings/AppDownloadLinks.tsx @@ -1,11 +1,11 @@ import React, {useRef} from 'react'; -import {ScrollView} from 'react-native'; import type {View} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import type {MenuItemProps} from '@components/MenuItem'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; diff --git a/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx b/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx index 7459819afd99..14739c4ffc52 100644 --- a/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx +++ b/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect} from 'react'; -import {View} from 'react-native'; +import {NativeModules, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import Icon from '@components//Icon'; @@ -84,6 +84,12 @@ function ExitSurveyConfirmPage({exitReason, isLoading, route, navigation}: ExitS text={translate('exitSurvey.goToExpensifyClassic')} onPress={() => { ExitSurvey.switchToOldDot(); + + if (NativeModules.HybridAppModule) { + NativeModules.HybridAppModule.closeReactNativeApp(); + return; + } + Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX); }} isLoading={isLoading ?? false} diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index b29fd600ae16..2f2343027cf0 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -1,7 +1,7 @@ import {useNavigationState} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; -import {NativeModules, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -174,23 +174,6 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa ], }; - if (NativeModules.HybridAppModule) { - const hybridAppMenuItems: MenuData[] = [ - { - translationKey: 'initialSettingsPage.returnToClassic' as const, - icon: Expensicons.RotateLeft, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - action: () => { - NativeModules.HybridAppModule.closeReactNativeApp(); - }, - }, - ...defaultMenu.items, - ].filter((item) => item.translationKey !== 'initialSettingsPage.signOut' && item.translationKey !== 'exitSurvey.goToExpensifyClassic'); - - return {sectionStyle: styles.accountSettingsSectionContainer, sectionTranslationKey: 'initialSettingsPage.account', items: hybridAppMenuItems}; - } - return defaultMenu; }, [loginList, fundList, styles.accountSettingsSectionContainer, bankAccountList, userWallet?.errors, walletTerms?.errors, signOut]); diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js index 0fd6121fe512..36a26ccffaa2 100755 --- a/src/pages/settings/Preferences/PreferencesPage.js +++ b/src/pages/settings/Preferences/PreferencesPage.js @@ -1,13 +1,14 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Illustrations from '@components/Icon/Illustrations'; import LottieAnimations from '@components/LottieAnimations'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Switch from '@components/Switch'; import Text from '@components/Text'; diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 18589beb6353..2ba4fc33580b 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -1,7 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; import Str from 'expensify-common/lib/str'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {InteractionManager, Keyboard, ScrollView, View} from 'react-native'; +import {InteractionManager, Keyboard, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -13,6 +13,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx index 5d150e782c44..3851ef7153fb 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx @@ -1,7 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; import Str from 'expensify-common/lib/str'; import React, {useCallback} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -11,6 +11,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 968d9e502806..2fa133f41616 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -1,7 +1,7 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useEffect} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; @@ -10,6 +10,7 @@ import * as Illustrations from '@components/Icon/Illustrations'; import MenuItemGroup from '@components/MenuItemGroup'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; diff --git a/src/pages/settings/Report/ReportSettingsPage.tsx b/src/pages/settings/Report/ReportSettingsPage.tsx index 54057f7c05bb..383cbbcb0833 100644 --- a/src/pages/settings/Report/ReportSettingsPage.tsx +++ b/src/pages/settings/Report/ReportSettingsPage.tsx @@ -1,12 +1,13 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useMemo} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import DisplayNames from '@components/DisplayNames'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 8600c9e08471..01563e586792 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -1,11 +1,12 @@ import React, {useMemo} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import LottieAnimations from '@components/LottieAnimations'; import MenuItemList from '@components/MenuItemList'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx index d6c7a1abcd4f..b4c1bc249c81 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useState} from 'react'; -import {ActivityIndicator, ScrollView, View} from 'react-native'; +import {ActivityIndicator, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; @@ -7,6 +7,7 @@ import FormHelpMessage from '@components/FormHelpMessage'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import PressableWithDelayToggle from '@components/Pressable/PressableWithDelayToggle'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.tsx index 59c145f9e348..ad9a4060af45 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.tsx @@ -1,8 +1,9 @@ import React, {useState} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import ConfirmModal from '@components/ConfirmModal'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx index d9998c777f3b..58e7d98d69de 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useRef} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png'; import Button from '@components/Button'; @@ -8,6 +8,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import {useSession} from '@components/OnyxProvider'; import PressableWithDelayToggle from '@components/Pressable/PressableWithDelayToggle'; import QRCode from '@components/QRCode'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage.tsx index a8b676f6c379..097b2cf28ed0 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.tsx +++ b/src/pages/settings/Wallet/ExpensifyCardPage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useEffect, useMemo, useState} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -11,6 +11,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/pages/settings/Wallet/TransferBalancePage.tsx b/src/pages/settings/Wallet/TransferBalancePage.tsx index 93ead17e9523..85b7bef0550c 100644 --- a/src/pages/settings/Wallet/TransferBalancePage.tsx +++ b/src/pages/settings/Wallet/TransferBalancePage.tsx @@ -1,5 +1,5 @@ import React, {useEffect} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -10,6 +10,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx index b9f49049d51a..88236e06f9a9 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -2,7 +2,7 @@ import _ from 'lodash'; import type {ForwardedRef, RefObject} from 'react'; import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'; import type {GestureResponderEvent} from 'react-native'; -import {ActivityIndicator, Dimensions, ScrollView, View} from 'react-native'; +import {ActivityIndicator, Dimensions, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; import Button from '@components/Button'; @@ -18,6 +18,7 @@ import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Popover from '@components/Popover'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; @@ -74,7 +75,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi }); const addPaymentMethodAnchorRef = useRef(null); - const paymentMethodButtonRef = useRef(null); + const paymentMethodButtonRef = useRef(null); const [anchorPosition, setAnchorPosition] = useState({ anchorPositionHorizontal: 0, anchorPositionVertical: 0, @@ -163,7 +164,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi setShouldShowDefaultDeleteMenu(false); return; } - paymentMethodButtonRef.current = nativeEvent?.currentTarget as HTMLElement; + paymentMethodButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement; // The delete/default menu if (accountType) { diff --git a/src/pages/signin/SAMLSignInPage/index.tsx b/src/pages/signin/SAMLSignInPage/index.tsx index 701c2917bea6..1ff9d02672be 100644 --- a/src/pages/signin/SAMLSignInPage/index.tsx +++ b/src/pages/signin/SAMLSignInPage/index.tsx @@ -7,7 +7,7 @@ import type {SAMLSignInPageOnyxProps, SAMLSignInPageProps} from './types'; function SAMLSignInPage({credentials}: SAMLSignInPageProps) { useEffect(() => { - window.open(`${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials?.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`, '_self'); + window.location.replace(`${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials?.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`); }, [credentials?.login]); return ; diff --git a/src/pages/signin/SignInPageLayout/index.tsx b/src/pages/signin/SignInPageLayout/index.tsx index b65da7eba0a5..3532c17181db 100644 --- a/src/pages/signin/SignInPageLayout/index.tsx +++ b/src/pages/signin/SignInPageLayout/index.tsx @@ -1,8 +1,11 @@ import type {ForwardedRef} from 'react'; import React, {forwardRef, useEffect, useImperativeHandle, useMemo, useRef} from 'react'; -import {ScrollView, View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {ScrollView as RNScrollView} from 'react-native'; +import {View} from 'react-native'; import SignInGradient from '@assets/images/home-fade-gradient.svg'; import ImageSVG from '@components/ImageSVG'; +import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -38,7 +41,7 @@ function SignInPageLayout( const StyleUtils = useStyleUtils(); const {preferredLocale} = useLocalize(); const {top: topInsets, bottom: bottomInsets} = useSafeAreaInsets(); - const scrollViewRef = useRef(null); + const scrollViewRef = useRef(null); const prevPreferredLocale = usePrevious(preferredLocale); const {windowHeight, isMediumScreenWidth, isLargeScreenWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); diff --git a/src/pages/tasks/NewTaskPage.js b/src/pages/tasks/NewTaskPage.js index f77285190e62..352c08115114 100644 --- a/src/pages/tasks/NewTaskPage.js +++ b/src/pages/tasks/NewTaskPage.js @@ -1,7 +1,7 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useEffect, useMemo, useState} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -10,6 +10,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; @@ -155,14 +156,7 @@ function NewTaskPage(props) { Navigation.goBack(ROUTES.NEW_TASK_DETAILS); }} /> - + ; /** Grab the Share destination of the Task */ - task: PropTypes.shape({ - /** Share destination of the Task */ - shareDestination: PropTypes.string, - - /** The task report if it's currently being edited */ - report: reportPropTypes, - }), - - /** The policy of root parent report */ - rootParentReportPolicy: PropTypes.shape({ - /** The role of current user */ - role: PropTypes.string, - }), + task: OnyxEntry; }; -const defaultProps = { - reports: {}, - task: {}, - rootParentReportPolicy: {}, +type UseOptions = { + reports: OnyxCollection; }; -function useOptions({reports}) { +type TaskAssigneeSelectorModalProps = TaskAssigneeSelectorModalOnyxProps & WithCurrentUserPersonalDetailsProps; + +function useOptions({reports}: UseOptions) { const allPersonalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const betas = useBetas(); const [isLoading, setIsLoading] = useState(true); @@ -78,7 +69,7 @@ function useOptions({reports}) { ); const headerMessage = OptionsListUtils.getHeaderMessage( - (recentReports.length || 0 + personalDetails.length || 0) !== 0 || currentUserOption, + (recentReports?.length || 0) + (personalDetails?.length || 0) !== 0 || Boolean(currentUserOption), Boolean(userToInvite), debouncedSearchValue, ); @@ -99,20 +90,20 @@ function useOptions({reports}) { return {...options, isLoading, searchValue, debouncedSearchValue, setSearchValue}; } -function TaskAssigneeSelectorModal({reports, task, rootParentReportPolicy}) { +function TaskAssigneeSelectorModal({reports, task}: TaskAssigneeSelectorModalProps) { const styles = useThemeStyles(); - const route = useRoute(); + const route = useRoute>(); const {translate} = useLocalize(); const session = useSession(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const {userToInvite, recentReports, personalDetails, currentUserOption, isLoading, searchValue, setSearchValue, headerMessage} = useOptions({reports, task}); + const {userToInvite, recentReports, personalDetails, currentUserOption, isLoading, searchValue, setSearchValue, headerMessage} = useOptions({reports}); const onChangeText = (newSearchTerm = '') => { setSearchValue(newSearchTerm); }; - const report = useMemo(() => { - if (!route.params || !route.params.reportID) { + const report: OnyxEntry = useMemo(() => { + if (!route.params?.reportID) { return null; } if (report && !ReportUtils.isTaskReport(report)) { @@ -120,7 +111,7 @@ function TaskAssigneeSelectorModal({reports, task, rootParentReportPolicy}) { Navigation.dismissModal(report.reportID); }); } - return reports[`${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`]; + return reports?.[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`] ?? null; }, [reports, route]); const sections = useMemo(() => { @@ -155,17 +146,29 @@ function TaskAssigneeSelectorModal({reports, task, rootParentReportPolicy}) { if (userToInvite) { sectionsList.push({ + title: '', data: [userToInvite], shouldShow: true, indexOffset, }); } - return sectionsList; - }, [currentUserOption, personalDetails, recentReports, userToInvite, translate]); + return sectionsList.map((section) => ({ + ...section, + data: section.data.map((option) => ({ + ...option, + text: option.text ?? '', + alternateText: option.alternateText ?? undefined, + keyForList: option.keyForList ?? '', + isDisabled: option.isDisabled ?? undefined, + login: option.login ?? undefined, + shouldShowSubscript: option.shouldShowSubscript ?? undefined, + })), + })); + }, [currentUserOption, personalDetails, recentReports, translate, userToInvite]); const selectReport = useCallback( - (option) => { + (option: ListItem) => { if (!option) { return; } @@ -173,25 +176,35 @@ function TaskAssigneeSelectorModal({reports, task, rootParentReportPolicy}) { // Check to see if we're editing a task and if so, update the assignee if (report) { if (option.accountID !== report.managerID) { - const assigneeChatReport = Task.setAssigneeValue(option.login, option.accountID, report.reportID, OptionsListUtils.isCurrentUser(option)); + const assigneeChatReport = TaskActions.setAssigneeValue( + option?.login ?? '', + option?.accountID ?? -1, + report.reportID, + OptionsListUtils.isCurrentUser({...option, accountID: option?.accountID ?? -1, login: option?.login ?? ''}), + ); // Pass through the selected assignee - Task.editTaskAssignee(report, session.accountID, option.login, option.accountID, assigneeChatReport); + TaskActions.editTaskAssignee(report, session?.accountID ?? 0, option?.login ?? '', option?.accountID, assigneeChatReport); } Navigation.dismissModal(report.reportID); // If there's no report, we're creating a new task } else if (option.accountID) { - Task.setAssigneeValue(option.login, option.accountID, task.shareDestination, OptionsListUtils.isCurrentUser(option)); + TaskActions.setAssigneeValue( + option?.login ?? '', + option.accountID, + task?.shareDestination ?? '', + OptionsListUtils.isCurrentUser({...option, accountID: option?.accountID ?? -1, login: option?.login ?? undefined}), + ); Navigation.goBack(ROUTES.NEW_TASK); } }, - [session.accountID, task.shareDestination, report], + [session?.accountID, task?.shareDestination, report], ); - const handleBackButtonPress = useCallback(() => (lodashGet(route.params, 'reportID') ? Navigation.dismissModal() : Navigation.goBack(ROUTES.NEW_TASK)), [route.params]); + const handleBackButtonPress = useCallback(() => (route.params?.reportID ? Navigation.dismissModal() : Navigation.goBack(ROUTES.NEW_TASK)), [route.params]); const isOpen = ReportUtils.isOpenTaskReport(report); - const canModifyTask = Task.canModifyTask(report, currentUserPersonalDetails.accountID, lodashGet(rootParentReportPolicy, 'role', '')); + const canModifyTask = TaskActions.canModifyTask(report, currentUserPersonalDetails.accountID); const isTaskNonEditable = ReportUtils.isTaskReport(report) && (!canModifyTask || !isOpen); return ( @@ -199,7 +212,7 @@ function TaskAssigneeSelectorModal({reports, task, rootParentReportPolicy}) { includeSafeAreaPaddingBottom={false} testID={TaskAssigneeSelectorModal.displayName} > - {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( + {({didScreenTransitionEnd}) => ( @@ -225,26 +237,14 @@ function TaskAssigneeSelectorModal({reports, task, rootParentReportPolicy}) { } TaskAssigneeSelectorModal.displayName = 'TaskAssigneeSelectorModal'; -TaskAssigneeSelectorModal.propTypes = propTypes; -TaskAssigneeSelectorModal.defaultProps = defaultProps; -export default compose( - withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - task: { - key: ONYXKEYS.TASK, - }, - }), - withOnyx({ - rootParentReportPolicy: { - key: ({reports, route}) => { - const report = reports[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID || '0'}`]; - const rootParentReport = ReportUtils.getRootParentReport(report); - return `${ONYXKEYS.COLLECTION.POLICY}${rootParentReport ? rootParentReport.policyID : '0'}`; - }, - selector: (policy) => lodashPick(policy, ['role']), - }, - }), -)(TaskAssigneeSelectorModal); +const TaskAssigneeSelectorModalWithOnyx = withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + task: { + key: ONYXKEYS.TASK, + }, +})(TaskAssigneeSelectorModal); + +export default withCurrentUserPersonalDetails(TaskAssigneeSelectorModalWithOnyx); diff --git a/src/pages/tasks/TaskDescriptionPage.js b/src/pages/tasks/TaskDescriptionPage.tsx similarity index 61% rename from src/pages/tasks/TaskDescriptionPage.js rename to src/pages/tasks/TaskDescriptionPage.tsx index b8b48abd09ff..e08d6380bb18 100644 --- a/src/pages/tasks/TaskDescriptionPage.js +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -2,53 +2,43 @@ import {useFocusEffect} from '@react-navigation/native'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import React, {useCallback, useRef} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import withReportOrNotFound from '@pages/home/report/withReportOrNotFound'; -import reportPropTypes from '@pages/reportPropTypes'; +import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/EditTaskForm'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; -const propTypes = { - /** The report currently being looked at */ - report: reportPropTypes, - - /* Onyx Props */ - ...withLocalizePropTypes, -}; - -const defaultProps = { - report: {}, -}; +type TaskDescriptionPageProps = WithReportOrNotFoundProps & WithCurrentUserPersonalDetailsProps; const parser = new ExpensiMark(); -function TaskDescriptionPage(props) { + +function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescriptionPageProps) { const styles = useThemeStyles(); + const {translate} = useLocalize(); - /** - * @param {Object} values - form input values passed by the Form component - * @returns {Boolean} - */ - const validate = useCallback((values) => { + const validate = useCallback((values: FormOnyxValues): FormInputErrors => { const errors = {}; - if (values.description.length > CONST.DESCRIPTION_LIMIT) { + if (values?.description && values.description?.length > CONST.DESCRIPTION_LIMIT) { ErrorUtils.addErrorMessage(errors, 'description', ['common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT}]); } @@ -56,30 +46,30 @@ function TaskDescriptionPage(props) { }, []); const submit = useCallback( - (values) => { - // props.report.description might contain CRLF from the server - if (StringUtils.normalizeCRLF(values.description) !== StringUtils.normalizeCRLF(props.report.description)) { + (values: FormOnyxValues) => { + // report.description might contain CRLF from the server + if (StringUtils.normalizeCRLF(values.description) !== StringUtils.normalizeCRLF(report?.description) && !isEmptyObject(report)) { // Set the description of the report in the store and then call EditTask API // to update the description of the report on the server - Task.editTask(props.report, {description: values.description}); + Task.editTask(report, {description: values.description}); } - Navigation.dismissModal(props.report.reportID); + Navigation.dismissModal(report?.reportID); }, - [props], + [report], ); - if (!ReportUtils.isTaskReport(props.report)) { + if (!ReportUtils.isTaskReport(report)) { Navigation.isNavigationReady().then(() => { - Navigation.dismissModal(props.report.reportID); + Navigation.dismissModal(report?.reportID); }); } - const inputRef = useRef(null); - const focusTimeoutRef = useRef(null); + const inputRef = useRef(null); + const focusTimeoutRef = useRef(null); - const isOpen = ReportUtils.isOpenTaskReport(props.report); - const canModifyTask = Task.canModifyTask(props.report, props.currentUserPersonalDetails.accountID); - const isTaskNonEditable = ReportUtils.isTaskReport(props.report) && (!canModifyTask || !isOpen); + const isOpen = ReportUtils.isOpenTaskReport(report); + const canModifyTask = Task.canModifyTask(report, currentUserPersonalDetails.accountID); + const isTaskNonEditable = ReportUtils.isTaskReport(report) && (!canModifyTask || !isOpen); useFocusEffect( useCallback(() => { @@ -104,13 +94,13 @@ function TaskDescriptionPage(props) { testID={TaskDescriptionPage.displayName} > - + @@ -119,14 +109,14 @@ function TaskDescriptionPage(props) { role={CONST.ROLE.PRESENTATION} inputID={INPUT_IDS.DESCRIPTION} name={INPUT_IDS.DESCRIPTION} - label={props.translate('newTaskPage.descriptionOptional')} - accessibilityLabel={props.translate('newTaskPage.descriptionOptional')} - defaultValue={parser.htmlToMarkdown((props.report && parser.replace(props.report.description)) || '')} - ref={(el) => { - if (!el) { + label={translate('newTaskPage.descriptionOptional')} + accessibilityLabel={translate('newTaskPage.descriptionOptional')} + defaultValue={parser.htmlToMarkdown((report && parser.replace(report?.description ?? '')) || '')} + ref={(element: AnimatedTextInputRef) => { + if (!element) { return; } - inputRef.current = el; + inputRef.current = element; updateMultilineInputRange(inputRef.current); }} autoGrowHeight @@ -140,17 +130,8 @@ function TaskDescriptionPage(props) { ); } -TaskDescriptionPage.propTypes = propTypes; -TaskDescriptionPage.defaultProps = defaultProps; TaskDescriptionPage.displayName = 'TaskDescriptionPage'; -export default compose( - withLocalize, - withCurrentUserPersonalDetails, - withReportOrNotFound(), - withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, - }, - }), -)(TaskDescriptionPage); +const ComponentWithCurrentUserPersonalDetails = withCurrentUserPersonalDetails(TaskDescriptionPage); + +export default withReportOrNotFound()(ComponentWithCurrentUserPersonalDetails); diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx similarity index 62% rename from src/pages/tasks/TaskShareDestinationSelectorModal.js rename to src/pages/tasks/TaskShareDestinationSelectorModal.tsx index b62440b22967..5b56e58752ac 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx @@ -1,9 +1,7 @@ -import keys from 'lodash/keys'; -import reduce from 'lodash/reduce'; -import PropTypes from 'prop-types'; import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {usePersonalDetails} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -13,51 +11,45 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as Report from '@libs/actions/Report'; +import * as ReportActions from '@libs/actions/Report'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import reportPropTypes from '@pages/reportPropTypes'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Report} from '@src/types/onyx'; -const propTypes = { - /** All reports shared with the user */ - reports: PropTypes.objectOf(reportPropTypes), - /** Whether or not we are searching for reports on the server */ - isSearchingForReports: PropTypes.bool, -}; +type TaskShareDestinationSelectorModalOnyxProps = { + reports: OnyxCollection; -const defaultProps = { - reports: {}, - isSearchingForReports: false, + isSearchingForReports: OnyxEntry; }; -const selectReportHandler = (option) => { - if (!option || !option.reportID) { +type TaskShareDestinationSelectorModalProps = TaskShareDestinationSelectorModalOnyxProps; + +const selectReportHandler = (option: unknown) => { + const optionItem = option as ReportUtils.OptionData; + + if (!optionItem || !optionItem?.reportID) { return; } - Task.setShareDestinationValue(option.reportID); + Task.setShareDestinationValue(optionItem?.reportID); Navigation.goBack(ROUTES.NEW_TASK); }; -const reportFilter = (reports) => - reduce( - keys(reports), - (filtered, reportKey) => { - const report = reports[reportKey]; - if (ReportUtils.canUserPerformWriteAction(report) && ReportUtils.canCreateTaskInReport(report) && !ReportUtils.isCanceledTaskReport(report)) { - return {...filtered, [reportKey]: report}; - } - return filtered; - }, - {}, - ); +const reportFilter = (reports: OnyxCollection) => + Object.keys(reports ?? {}).reduce((filtered, reportKey) => { + const report: OnyxEntry = reports?.[reportKey] ?? null; + if (ReportUtils.canUserPerformWriteAction(report) && ReportUtils.canCreateTaskInReport(report) && !ReportUtils.isCanceledTaskReport(report)) { + return {...filtered, [reportKey]: report}; + } + return filtered; + }, {}); -function TaskShareDestinationSelectorModal({reports, isSearchingForReports}) { +function TaskShareDestinationSelectorModal({reports, isSearchingForReports}: TaskShareDestinationSelectorModalProps) { const styles = useThemeStyles(); const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); const {translate} = useLocalize(); @@ -73,13 +65,29 @@ function TaskShareDestinationSelectorModal({reports, isSearchingForReports}) { const headerMessage = OptionsListUtils.getHeaderMessage(recentReports && recentReports.length !== 0, false, debouncedSearchValue); - const sections = recentReports && recentReports.length > 0 ? [{data: recentReports, shouldShow: true}] : []; + const sections = + recentReports && recentReports.length > 0 + ? [ + { + data: recentReports.map((option) => ({ + ...option, + text: option.text ?? '', + alternateText: option.alternateText ?? undefined, + keyForList: option.keyForList ?? '', + isDisabled: option.isDisabled ?? undefined, + login: option.login ?? undefined, + shouldShowSubscript: option.shouldShowSubscript ?? undefined, + })), + shouldShow: true, + }, + ] + : []; return {sections, headerMessage}; }, [personalDetails, reports, debouncedSearchValue]); useEffect(() => { - Report.searchInServer(debouncedSearchValue); + ReportActions.searchInServer(debouncedSearchValue); }, [debouncedSearchValue]); return ( @@ -87,7 +95,7 @@ function TaskShareDestinationSelectorModal({reports, isSearchingForReports}) { includeSafeAreaPaddingBottom={false} testID="TaskShareDestinationSelectorModal" > - {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( + {({didScreenTransitionEnd}) => ( <> @@ -115,10 +122,8 @@ function TaskShareDestinationSelectorModal({reports, isSearchingForReports}) { } TaskShareDestinationSelectorModal.displayName = 'TaskShareDestinationSelectorModal'; -TaskShareDestinationSelectorModal.propTypes = propTypes; -TaskShareDestinationSelectorModal.defaultProps = defaultProps; -export default withOnyx({ +export default withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, }, diff --git a/src/pages/tasks/TaskTitlePage.js b/src/pages/tasks/TaskTitlePage.tsx similarity index 50% rename from src/pages/tasks/TaskTitlePage.js rename to src/pages/tasks/TaskTitlePage.tsx index 370baab7cd89..009983beac3e 100644 --- a/src/pages/tasks/TaskTitlePage.js +++ b/src/pages/tasks/TaskTitlePage.tsx @@ -1,98 +1,85 @@ import React, {useCallback, useRef} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import withReportOrNotFound from '@pages/home/report/withReportOrNotFound'; -import reportPropTypes from '@pages/reportPropTypes'; +import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/EditTaskForm'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; -const propTypes = { - /** The report currently being looked at */ - report: reportPropTypes, +type TaskTitlePageProps = WithReportOrNotFoundProps & WithCurrentUserPersonalDetailsProps; - /* Onyx Props */ - ...withLocalizePropTypes, -}; - -const defaultProps = { - report: {}, -}; - -function TaskTitlePage(props) { +function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) { const styles = useThemeStyles(); - /** - * @param {Object} values - * @param {String} values.title - * @returns {Object} - An object containing the errors for each inputID - */ - const validate = useCallback((values) => { - const errors = {}; + const {translate} = useLocalize(); + + const validate = useCallback(({title}: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; - if (_.isEmpty(values.title)) { + if (!title) { errors.title = 'newTaskPage.pleaseEnterTaskName'; - } else if (values.title.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'title', ['common.error.characterLimitExceedCounter', {length: values.title.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); } return errors; }, []); const submit = useCallback( - (values) => { - if (values.title !== props.report.reportName) { + (values: FormOnyxValues) => { + if (values.title !== report?.reportName && !isEmptyObject(report)) { // Set the title of the report in the store and then call EditTask API // to update the title of the report on the server - Task.editTask(props.report, {title: values.title}); + Task.editTask(report, {title: values.title}); } - Navigation.dismissModal(props.report.reportID); + Navigation.dismissModal(report?.reportID); }, - [props], + [report], ); - if (!ReportUtils.isTaskReport(props.report)) { + if (!ReportUtils.isTaskReport(report)) { Navigation.isNavigationReady().then(() => { - Navigation.dismissModal(props.report.reportID); + Navigation.dismissModal(report?.reportID); }); } - const inputRef = useRef(null); - const isOpen = ReportUtils.isOpenTaskReport(props.report); - const canModifyTask = Task.canModifyTask(props.report, props.currentUserPersonalDetails.accountID); - const isTaskNonEditable = ReportUtils.isTaskReport(props.report) && (!canModifyTask || !isOpen); + const inputRef = useRef(null); + const isOpen = ReportUtils.isOpenTaskReport(report); + const canModifyTask = Task.canModifyTask(report, currentUserPersonalDetails.accountID); + const isTaskNonEditable = ReportUtils.isTaskReport(report) && (!canModifyTask || !isOpen); return ( inputRef.current && inputRef.current.focus()} + onEntryTransitionEnd={() => { + inputRef?.current?.focus(); + }} shouldEnableMaxHeight testID={TaskTitlePage.displayName} > {({didScreenTransitionEnd}) => ( - + @@ -101,17 +88,17 @@ function TaskTitlePage(props) { role={CONST.ROLE.PRESENTATION} inputID={INPUT_IDS.TITLE} name={INPUT_IDS.TITLE} - label={props.translate('task.title')} - accessibilityLabel={props.translate('task.title')} - defaultValue={(props.report && props.report.reportName) || ''} - ref={(el) => { - if (!el) { + label={translate('task.title')} + accessibilityLabel={translate('task.title')} + defaultValue={report?.reportName ?? ''} + ref={(element: AnimatedTextInputRef) => { + if (!element) { return; } if (!inputRef.current && didScreenTransitionEnd) { - el.focus(); + element.focus(); } - inputRef.current = el; + inputRef.current = element; }} /> @@ -122,17 +109,8 @@ function TaskTitlePage(props) { ); } -TaskTitlePage.propTypes = propTypes; -TaskTitlePage.defaultProps = defaultProps; TaskTitlePage.displayName = 'TaskTitlePage'; -export default compose( - withLocalize, - withCurrentUserPersonalDetails, - withReportOrNotFound(), - withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, - }, - }), -)(TaskTitlePage); +const ComponentWithCurrentUserPersonalDetails = withCurrentUserPersonalDetails(TaskTitlePage); + +export default withReportOrNotFound()(ComponentWithCurrentUserPersonalDetails); diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index d2565022075a..c4f4d6399dbd 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect, useState} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -11,6 +11,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import useActiveRoute from '@hooks/useActiveRoute'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; diff --git a/src/pages/workspace/WorkspaceJoinUserPage.tsx b/src/pages/workspace/WorkspaceJoinUserPage.tsx new file mode 100644 index 000000000000..8167e6fc1ebf --- /dev/null +++ b/src/pages/workspace/WorkspaceJoinUserPage.tsx @@ -0,0 +1,80 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useEffect, useRef} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useThemeStyles from '@hooks/useThemeStyles'; +import navigateAfterJoinRequest from '@libs/navigateAfterJoinRequest'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import Navigation from '@navigation/Navigation'; +import type {AuthScreensParamList} from '@navigation/types'; +import * as PolicyAction from '@userActions/Policy'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import type {Policy} from '@src/types/onyx'; + +type WorkspaceJoinUserPageOnyxProps = { + /** The list of this user's policies */ + policies: OnyxCollection; +}; + +type WorkspaceJoinUserPageRoute = {route: StackScreenProps['route']}; +type WorkspaceJoinUserPageProps = WorkspaceJoinUserPageRoute & WorkspaceJoinUserPageOnyxProps; + +let isJoinLinkUsed = false; + +function WorkspaceJoinUserPage({route, policies}: WorkspaceJoinUserPageProps) { + const styles = useThemeStyles(); + const policyID = route?.params?.policyID; + const inviterEmail = route?.params?.email; + const policy = ReportUtils.getPolicy(policyID); + const isUnmounted = useRef(false); + + useEffect(() => { + if (!isJoinLinkUsed) { + return; + } + Navigation.goBack(undefined, false, true); + }, []); + + useEffect(() => { + if (!policy || !policies || isUnmounted.current || isJoinLinkUsed) { + return; + } + const isPolicyMember = PolicyUtils.isPolicyMember(policyID, policies as Record); + if (isPolicyMember) { + Navigation.goBack(undefined, false, true); + return; + } + PolicyAction.inviteMemberToWorkspace(policyID, inviterEmail); + isJoinLinkUsed = true; + Navigation.isNavigationReady().then(() => { + if (isUnmounted.current) { + return; + } + navigateAfterJoinRequest(); + }); + }, [policy, policyID, policies, inviterEmail]); + + useEffect( + () => () => { + isUnmounted.current = true; + }, + [], + ); + + return ( + + + + ); +} + +WorkspaceJoinUserPage.displayName = 'WorkspaceJoinUserPage'; +export default withOnyx({ + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, +})(WorkspaceJoinUserPage); diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index e47bc4a09be4..42f29f885c00 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -254,6 +254,23 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se [selectedEmployees, addUser, removeUser], ); + /** Opens the member details page */ + const openMemberDetails = useCallback( + (item: MemberOption) => { + if (!isPolicyAdmin) { + Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); + return; + } + + if (!PolicyUtils.isPaidGroupPolicy(policy)) { + return; + } + + Navigation.navigate(ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(route.params.policyID, item.accountID, Navigation.getActiveRoute())); + }, + [isPolicyAdmin, policy, route.params.policyID], + ); + /** * Dismisses the errors on one item */ @@ -417,22 +434,24 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se }, ]; - if (selectedEmployees.find((employee) => policyMembers?.[employee]?.role === CONST.POLICY.ROLE.ADMIN)) { - options.push({ - text: translate('workspace.people.makeMember'), - value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_MEMBER, - icon: Expensicons.User, - onSelected: () => changeUserRole(CONST.POLICY.ROLE.USER), - }); - } + if (PolicyUtils.isPaidGroupPolicy(policy)) { + if (selectedEmployees.find((employee) => policyMembers?.[employee]?.role === CONST.POLICY.ROLE.ADMIN)) { + options.push({ + text: translate('workspace.people.makeMember'), + value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_MEMBER, + icon: Expensicons.User, + onSelected: () => changeUserRole(CONST.POLICY.ROLE.USER), + }); + } - if (selectedEmployees.find((employee) => policyMembers?.[employee]?.role === CONST.POLICY.ROLE.USER)) { - options.push({ - text: translate('workspace.people.makeAdmin'), - value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_ADMIN, - icon: Expensicons.MakeAdmin, - onSelected: () => changeUserRole(CONST.POLICY.ROLE.ADMIN), - }); + if (selectedEmployees.find((employee) => policyMembers?.[employee]?.role === CONST.POLICY.ROLE.USER)) { + options.push({ + text: translate('workspace.people.makeAdmin'), + value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_ADMIN, + icon: Expensicons.MakeAdmin, + onSelected: () => changeUserRole(CONST.POLICY.ROLE.ADMIN), + }); + } } return options; @@ -463,7 +482,7 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se onPress={inviteUser} text={translate('workspace.invite.member')} icon={Expensicons.Plus} - iconStyles={{transform: [{scale: 0.6}]}} + iconStyles={StyleUtils.getTransformScaleStyle(0.6)} innerStyles={[isSmallScreenWidth && styles.alignItemsCenter]} style={[isSmallScreenWidth && styles.flexGrow1]} /> @@ -523,13 +542,8 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se disableKeyboardShortcuts={removeMembersConfirmModalVisible} headerMessage={getHeaderMessage()} headerContent={getHeaderContent()} - onSelectRow={(item) => { - if (!isPolicyAdmin) { - Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); - return; - } - toggleUser(item.accountID); - }} + onSelectRow={openMemberDetails} + onCheckboxPress={(item) => toggleUser(item.accountID)} onSelectAll={() => toggleAllUsers(data)} onDismissError={dismissError} showLoadingPlaceholder={!isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policyMembers))} diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index 796f32c343f2..9d90557b1d37 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -1,15 +1,17 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useState} from 'react'; import type {ImageStyle, StyleProp} from 'react-native'; -import {Image, ScrollView, StyleSheet, View} from 'react-native'; +import {Image, StyleSheet, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Avatar from '@components/Avatar'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; import Button from '@components/Button'; +import ConfirmModal from '@components/ConfirmModal'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; @@ -74,6 +76,19 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi [policy?.avatar, policyName, styles.alignSelfCenter, styles.avatarXLarge], ); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + + const confirmDeleteAndHideModal = useCallback(() => { + if (!policy?.id || !policyName) { + return; + } + + Policy.deleteWorkspace(policy?.id, policyName); + + PolicyUtils.goBackFromInvalidPolicy(); + + setIsDeleteModalOpen(false); + }, [policy?.id, policyName]); return (
{!readOnly && ( - +