From 84eab1a1113600fa9e461722dab0f921a7c56b8a Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Sun, 5 Nov 2023 10:42:34 +0700 Subject: [PATCH 001/661] Add CurrencySelectionList component and use it in IOU and WS currency settings Signed-off-by: Tsaqif --- src/components/CurrencySelectionList.js | 92 +++++++++++++++++++ src/pages/iou/IOUCurrencySelection.js | 74 ++------------- .../WorkspaceSettingsCurrencyPage.js | 67 ++------------ 3 files changed, 106 insertions(+), 127 deletions(-) create mode 100644 src/components/CurrencySelectionList.js diff --git a/src/components/CurrencySelectionList.js b/src/components/CurrencySelectionList.js new file mode 100644 index 000000000000..1c0733ae9927 --- /dev/null +++ b/src/components/CurrencySelectionList.js @@ -0,0 +1,92 @@ +import Str from 'expensify-common/lib/str'; +import PropTypes from 'prop-types'; +import React, {useMemo, useState} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; +import compose from '@libs/compose'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import SelectionList from './SelectionList'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; + +const propTypes = { + /** Label for the search text input */ + textInputLabel: PropTypes.string.isRequired, + + /** Callback to fire when a currency is selected */ + onSelect: PropTypes.func.isRequired, + + /** Currency item to be selected initially */ + initiallySelectedCurrencyCode: PropTypes.string.isRequired, + + // The curency list constant object from Onyx + currencyList: PropTypes.objectOf( + PropTypes.shape({ + // Symbol for the currency + symbol: PropTypes.string, + + // Name of the currency + name: PropTypes.string, + + // ISO4217 Code for the currency + ISO4217: PropTypes.string, + }), + ), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + currencyList: {}, +}; + +function CurrencySelectionList(props) { + const [searchValue, setSearchValue] = useState(''); + const {translate, currencyList} = props; + + const {sections, headerMessage} = useMemo(() => { + const currencyOptions = _.map(currencyList, (currencyInfo, currencyCode) => { + const isSelectedCurrency = currencyCode === props.initiallySelectedCurrencyCode; + return { + currencyName: currencyInfo.name, + text: `${currencyCode} - ${CurrencyUtils.getLocalizedCurrencySymbol(currencyCode)}`, + currencyCode, + keyForList: currencyCode, + isSelected: isSelectedCurrency, + }; + }); + + const searchRegex = new RegExp(Str.escapeForRegExp(searchValue.trim()), 'i'); + const filteredCurrencies = _.filter(currencyOptions, (currencyOption) => searchRegex.test(currencyOption.text) || searchRegex.test(currencyOption.currencyName)); + const isEmpty = searchValue.trim() && !filteredCurrencies.length; + + return { + sections: isEmpty ? [] : [{data: filteredCurrencies, indexOffset: 0}], + headerMessage: isEmpty ? translate('common.noResultsFound') : '', + }; + }, [currencyList, searchValue, translate, props.initiallySelectedCurrencyCode]); + + return ( + + ); +} + +CurrencySelectionList.displayName = 'CurrencySelectionList'; +CurrencySelectionList.propTypes = propTypes; +CurrencySelectionList.defaultProps = defaultProps; + +export default compose( + withLocalize, + withOnyx({ + currencyList: {key: ONYXKEYS.CURRENCY_LIST}, + }), +)(CurrencySelectionList); diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index c7b5885865df..a18929061f1f 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -1,17 +1,15 @@ -import Str from 'expensify-common/lib/str'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect} from 'react'; import {Keyboard} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; +import CurrencySelectionList from '@components/CurrencySelectionList'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {withNetwork} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; -import SelectionList from '@components/SelectionList'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import compose from '@libs/compose'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -42,20 +40,6 @@ const propTypes = { }), }).isRequired, - // The currency list constant object from Onyx - currencyList: PropTypes.objectOf( - PropTypes.shape({ - // Symbol for the currency - symbol: PropTypes.string, - - // Name of the currency - name: PropTypes.string, - - // ISO4217 Code for the currency - ISO4217: PropTypes.string, - }), - ), - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: iouPropTypes, @@ -63,13 +47,10 @@ const propTypes = { }; const defaultProps = { - currencyList: {}, iou: iouDefaultProps, }; function IOUCurrencySelection(props) { - const [searchValue, setSearchValue] = useState(''); - const optionsSelectorRef = useRef(); const selectedCurrencyCode = (lodashGet(props.route, 'params.currency', props.iou.currency) || CONST.CURRENCY.USD).toUpperCase(); const iouType = lodashGet(props.route, 'params.iouType', CONST.IOU.TYPE.REQUEST); const reportID = lodashGet(props.route, 'params.reportID', ''); @@ -113,59 +94,19 @@ function IOUCurrencySelection(props) { [props.route, props.navigation], ); - const {translate, currencyList} = props; - const {sections, headerMessage, initiallyFocusedOptionKey} = useMemo(() => { - const currencyOptions = _.map(currencyList, (currencyInfo, currencyCode) => { - const isSelectedCurrency = currencyCode === selectedCurrencyCode; - return { - currencyName: currencyInfo.name, - text: `${currencyCode} - ${CurrencyUtils.getLocalizedCurrencySymbol(currencyCode)}`, - currencyCode, - keyForList: currencyCode, - isSelected: isSelectedCurrency, - }; - }); - - const searchRegex = new RegExp(Str.escapeForRegExp(searchValue.trim()), 'i'); - const filteredCurrencies = _.filter(currencyOptions, (currencyOption) => searchRegex.test(currencyOption.text) || searchRegex.test(currencyOption.currencyName)); - const isEmpty = searchValue.trim() && !filteredCurrencies.length; - - return { - initiallyFocusedOptionKey: _.get( - _.find(filteredCurrencies, (currency) => currency.currencyCode === selectedCurrencyCode), - 'keyForList', - ), - sections: isEmpty - ? [] - : [ - { - data: filteredCurrencies, - indexOffset: 0, - }, - ], - headerMessage: isEmpty ? translate('common.noResultsFound') : '', - }; - }, [currencyList, searchValue, selectedCurrencyCode, translate]); - return ( optionsSelectorRef.current && optionsSelectorRef.current.focus()} testID={IOUCurrencySelection.displayName} > Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID))} /> - ); @@ -178,7 +119,6 @@ IOUCurrencySelection.defaultProps = defaultProps; export default compose( withLocalize, withOnyx({ - currencyList: {key: ONYXKEYS.CURRENCY_LIST}, iou: {key: ONYXKEYS.IOU}, }), withNetwork(), diff --git a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js index ce1e1d7b8966..a20e8e272e4e 100644 --- a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js +++ b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js @@ -1,73 +1,30 @@ import PropTypes from 'prop-types'; -import React, {useCallback, useState} from 'react'; -import {withOnyx} from 'react-native-onyx'; +import React, {useCallback} from 'react'; import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import CurrencySelectionList from '@components/CurrencySelectionList'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; -import SelectionList from '@components/SelectionList'; import useLocalize from '@hooks/useLocalize'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as Policy from '@userActions/Policy'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import {policyDefaultProps, policyPropTypes} from './withPolicy'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; const propTypes = { - /** Constant, list of available currencies */ - currencyList: PropTypes.objectOf( - PropTypes.shape({ - /** Symbol of the currency */ - symbol: PropTypes.string.isRequired, - }), - ), isLoadingReportData: PropTypes.bool, ...policyPropTypes, }; const defaultProps = { - currencyList: {}, isLoadingReportData: true, ...policyDefaultProps, }; -const getDisplayText = (currencyCode, currencySymbol) => `${currencyCode} - ${currencySymbol}`; - -function WorkspaceSettingsCurrencyPage({currencyList, policy, isLoadingReportData}) { +function WorkspaceSettingsCurrencyPage({policy, isLoadingReportData}) { const {translate} = useLocalize(); - const [searchText, setSearchText] = useState(''); - const trimmedText = searchText.trim().toLowerCase(); - const currencyListKeys = _.keys(currencyList); - - const filteredItems = _.filter(currencyListKeys, (currencyCode) => { - const currency = currencyList[currencyCode]; - return getDisplayText(currencyCode, currency.symbol).toLowerCase().includes(trimmedText); - }); - - let initiallyFocusedOptionKey; - - const currencyItems = _.map(filteredItems, (currencyCode) => { - const currency = currencyList[currencyCode]; - const isSelected = policy.outputCurrency === currencyCode; - - if (isSelected) { - initiallyFocusedOptionKey = currencyCode; - } - - return { - text: getDisplayText(currencyCode, currency.symbol), - keyForList: currencyCode, - isSelected, - }; - }); - - const sections = [{data: currencyItems, indexOffset: 0}]; - - const headerMessage = searchText.trim() && !currencyItems.length ? translate('common.noResultsFound') : ''; - const onBackButtonPress = useCallback(() => Navigation.goBack(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id)), [policy.id]); const onSelectCurrency = (item) => { @@ -90,15 +47,10 @@ function WorkspaceSettingsCurrencyPage({currencyList, policy, isLoadingReportDat onBackButtonPress={onBackButtonPress} /> - @@ -109,9 +61,4 @@ WorkspaceSettingsCurrencyPage.displayName = 'WorkspaceSettingsCurrencyPage'; WorkspaceSettingsCurrencyPage.propTypes = propTypes; WorkspaceSettingsCurrencyPage.defaultProps = defaultProps; -export default compose( - withPolicyAndFullscreenLoading, - withOnyx({ - currencyList: {key: ONYXKEYS.CURRENCY_LIST}, - }), -)(WorkspaceSettingsCurrencyPage); +export default withPolicyAndFullscreenLoading(WorkspaceSettingsCurrencyPage); From 87c644c9f65a5d9845a92aa0a6531ace1855ddb4 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Wed, 8 Nov 2023 20:19:09 +0700 Subject: [PATCH 002/661] Add currency url parameter for WorkspaceSettingsCurrencyPage Signed-off-by: Tsaqif --- src/ROUTES.ts | 2 +- src/components/CurrencySelectionList.js | 5 ++++- src/pages/iou/IOUCurrencySelection.js | 1 + .../workspace/WorkspaceSettingsCurrencyPage.js | 18 +++++++++++++++++- src/pages/workspace/WorkspaceSettingsPage.js | 2 +- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index bcc4685368cb..0902309514fc 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -328,7 +328,7 @@ export default { }, WORKSPACE_SETTINGS_CURRENCY: { route: 'workspace/:policyID/settings/currency', - getRoute: (policyID: string) => `workspace/${policyID}/settings/currency`, + getRoute: (policyID: string, currency: string) => `workspace/${policyID}/settings/currency?currency=${currency}`, }, WORKSPACE_CARD: { route: 'workspace/:policyID/card', diff --git a/src/components/CurrencySelectionList.js b/src/components/CurrencySelectionList.js index 795e8477a523..ba3cc9f7d7ed 100644 --- a/src/components/CurrencySelectionList.js +++ b/src/components/CurrencySelectionList.js @@ -20,6 +20,9 @@ const propTypes = { /** Currency item to be selected initially */ initiallySelectedCurrencyCode: PropTypes.string.isRequired, + /** Currency item to be focused initially */ + initiallyFocusedCurrencyCode: PropTypes.string.isRequired, + // The curency list constant object from Onyx currencyList: PropTypes.objectOf( PropTypes.shape({ @@ -79,7 +82,7 @@ function CurrencySelectionList(props) { onChangeText={setSearchValue} onSelectRow={props.onSelect} headerMessage={headerMessage} - initiallyFocusedOptionKey={props.initiallySelectedCurrencyCode} + initiallyFocusedOptionKey={props.initiallyFocusedCurrencyCode} showScrollIndicator /> ); diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index a18929061f1f..3fce092abda0 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -106,6 +106,7 @@ function IOUCurrencySelection(props) { diff --git a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js index a20e8e272e4e..2bb2a283acbc 100644 --- a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js +++ b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js @@ -1,3 +1,4 @@ +import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; import _ from 'underscore'; @@ -6,6 +7,7 @@ import CurrencySelectionList from '@components/CurrencySelectionList'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as Policy from '@userActions/Policy'; @@ -19,12 +21,25 @@ const propTypes = { }; const defaultProps = { + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** Focused currency code */ + currency: PropTypes.string, + + /** ID of a policy */ + policyID: PropTypes.string, + }), + }).isRequired, isLoadingReportData: true, ...policyDefaultProps, }; -function WorkspaceSettingsCurrencyPage({policy, isLoadingReportData}) { +function WorkspaceSettingsCurrencyPage({route, policy, isLoadingReportData}) { const {translate} = useLocalize(); + const currencyParam = lodashGet(route, 'params.currency', '').toUpperCase(); + const focusedCurrencyCode = CurrencyUtils.isValidCurrencyCode(currencyParam) ? currencyParam : policy.outputCurrency; const onBackButtonPress = useCallback(() => Navigation.goBack(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id)), [policy.id]); const onSelectCurrency = (item) => { @@ -50,6 +65,7 @@ function WorkspaceSettingsCurrencyPage({policy, isLoadingReportData}) { diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index d913ae26c170..287f31305164 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -87,7 +87,7 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { return errors; }, []); - const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS_CURRENCY.getRoute(policy.id)), [policy.id]); + const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS_CURRENCY.getRoute(policy.id, policy.outputCurrency)), [policy.id, policy.outputCurrency]); const policyName = lodashGet(policy, 'name', ''); From 1e508c14374e7d0809770031c7fd455eb593edf9 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Fri, 24 Nov 2023 07:45:11 +0700 Subject: [PATCH 003/661] Don't automatically save ws default currency when selecting currency in selection list Signed-off-by: Tsaqif --- src/ROUTES.ts | 2 +- src/components/CurrencySelectionList.js | 5 +---- .../workspace/WorkspaceSettingsCurrencyPage.js | 11 ++++------- src/pages/workspace/WorkspaceSettingsPage.js | 16 +++++++++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 0902309514fc..9545a7ab5008 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -324,7 +324,7 @@ export default { }, WORKSPACE_SETTINGS: { route: 'workspace/:policyID/settings', - getRoute: (policyID: string) => `workspace/${policyID}/settings`, + getRoute: (policyID: string, currency: string) => `workspace/${policyID}/settings?currency=${currency}`, }, WORKSPACE_SETTINGS_CURRENCY: { route: 'workspace/:policyID/settings/currency', diff --git a/src/components/CurrencySelectionList.js b/src/components/CurrencySelectionList.js index ba3cc9f7d7ed..795e8477a523 100644 --- a/src/components/CurrencySelectionList.js +++ b/src/components/CurrencySelectionList.js @@ -20,9 +20,6 @@ const propTypes = { /** Currency item to be selected initially */ initiallySelectedCurrencyCode: PropTypes.string.isRequired, - /** Currency item to be focused initially */ - initiallyFocusedCurrencyCode: PropTypes.string.isRequired, - // The curency list constant object from Onyx currencyList: PropTypes.objectOf( PropTypes.shape({ @@ -82,7 +79,7 @@ function CurrencySelectionList(props) { onChangeText={setSearchValue} onSelectRow={props.onSelect} headerMessage={headerMessage} - initiallyFocusedOptionKey={props.initiallyFocusedCurrencyCode} + initiallyFocusedOptionKey={props.initiallySelectedCurrencyCode} showScrollIndicator /> ); diff --git a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js index 2bb2a283acbc..b86ee9e34404 100644 --- a/src/pages/workspace/WorkspaceSettingsCurrencyPage.js +++ b/src/pages/workspace/WorkspaceSettingsCurrencyPage.js @@ -10,7 +10,6 @@ import useLocalize from '@hooks/useLocalize'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as Policy from '@userActions/Policy'; import ROUTES from '@src/ROUTES'; import {policyDefaultProps, policyPropTypes} from './withPolicy'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; @@ -39,12 +38,11 @@ const defaultProps = { function WorkspaceSettingsCurrencyPage({route, policy, isLoadingReportData}) { const {translate} = useLocalize(); const currencyParam = lodashGet(route, 'params.currency', '').toUpperCase(); - const focusedCurrencyCode = CurrencyUtils.isValidCurrencyCode(currencyParam) ? currencyParam : policy.outputCurrency; - const onBackButtonPress = useCallback(() => Navigation.goBack(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id)), [policy.id]); + const selectedCurrencyCode = CurrencyUtils.isValidCurrencyCode(currencyParam) ? currencyParam : policy.outputCurrency; + const onBackButtonPress = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id, selectedCurrencyCode)), [policy.id, selectedCurrencyCode]); const onSelectCurrency = (item) => { - Policy.updateGeneralSettings(policy.id, policy.name, item.keyForList); - Navigation.goBack(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id)); + Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id, item.currencyCode)); }; return ( @@ -65,8 +63,7 @@ function WorkspaceSettingsCurrencyPage({route, policy, isLoadingReportData}) { diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index 287f31305164..df43145d6ffd 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -16,6 +16,7 @@ import TextInput from '@components/TextInput'; import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; 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 * as UserUtils from '@libs/UserUtils'; @@ -42,6 +43,9 @@ const propTypes = { params: PropTypes.shape({ /** The policyID that is being configured */ policyID: PropTypes.string.isRequired, + + /** Selected currency code */ + currency: PropTypes.string, }).isRequired, }).isRequired, @@ -56,8 +60,10 @@ const defaultProps = { function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { const {translate} = useLocalize(); + const currencyParam = lodashGet(route, 'params.currency', '').toUpperCase(); + const currencyCode = CurrencyUtils.isValidCurrencyCode(currencyParam) ? currencyParam : policy.outputCurrency; - const formattedCurrency = !_.isEmpty(policy) && !_.isEmpty(currencyList) ? `${policy.outputCurrency} - ${currencyList[policy.outputCurrency].symbol}` : ''; + const formattedCurrency = !_.isEmpty(policy) && !_.isEmpty(currencyList) ? `${currencyCode} - ${currencyList[currencyCode].symbol}` : ''; const submit = useCallback( (values) => { @@ -65,11 +71,11 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { return; } - Policy.updateGeneralSettings(policy.id, values.name.trim(), policy.outputCurrency); + Policy.updateGeneralSettings(policy.id, values.name.trim(), currencyCode); Keyboard.dismiss(); - Navigation.goBack(ROUTES.WORKSPACE_INITIAL.getRoute(policy.id)); + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policy.id)); }, - [policy.id, policy.isPolicyUpdating, policy.outputCurrency], + [policy.id, policy.isPolicyUpdating, currencyCode], ); const validate = useCallback((values) => { @@ -87,7 +93,7 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { return errors; }, []); - const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS_CURRENCY.getRoute(policy.id, policy.outputCurrency)), [policy.id, policy.outputCurrency]); + const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS_CURRENCY.getRoute(policy.id, currencyCode)), [policy.id, currencyCode]); const policyName = lodashGet(policy, 'name', ''); From 81dc4ec8a8ef6feb66e8e685857ad738b1eab33f Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Fri, 24 Nov 2023 09:43:02 +0700 Subject: [PATCH 004/661] Fix adding currency parameter to worksapace_settings get route calls Signed-off-by: Tsaqif --- src/pages/workspace/WorkspaceInitialPage.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js index 77e831e62b63..1e27c89b405c 100644 --- a/src/pages/workspace/WorkspaceInitialPage.js +++ b/src/pages/workspace/WorkspaceInitialPage.js @@ -53,10 +53,10 @@ const defaultProps = { }; /** - * @param {string} policyID + * @param {Object} policy */ -function openEditor(policyID) { - Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policyID)); +function openEditor(policy) { + Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id, policy.outputCurrency)); } /** @@ -152,7 +152,7 @@ function WorkspaceInitialPage(props) { { translationKey: 'workspace.common.settings', icon: Expensicons.Gear, - action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id)))), + action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_SETTINGS.getRoute(policy.id, policy.outputCurrency)))), brickRoadIndicator: hasGeneralSettingsError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '', }, { @@ -269,7 +269,7 @@ function WorkspaceInitialPage(props) { openEditor(policy.id)))} + onPress={singleExecution(waitForNavigate(() => openEditor(policy)))} accessibilityLabel={translate('workspace.common.settings')} role={CONST.ACCESSIBILITY_ROLE.BUTTON} > @@ -289,7 +289,7 @@ function WorkspaceInitialPage(props) { openEditor(policy.id)))} + onPress={singleExecution(waitForNavigate(() => openEditor(policy)))} accessibilityLabel={translate('workspace.common.settings')} role={CONST.ACCESSIBILITY_ROLE.BUTTON} > From 6c4c5b0cc041e57c020cf1c0efaa5976cd0f5e78 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Sun, 3 Dec 2023 15:54:49 +0700 Subject: [PATCH 005/661] Change display text format of currency selection list Signed-off-by: Tsaqif --- src/components/CurrencySelectionList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CurrencySelectionList.js b/src/components/CurrencySelectionList.js index 795e8477a523..7d43b09c8d66 100644 --- a/src/components/CurrencySelectionList.js +++ b/src/components/CurrencySelectionList.js @@ -50,7 +50,7 @@ function CurrencySelectionList(props) { const isSelectedCurrency = currencyCode === props.initiallySelectedCurrencyCode; return { currencyName: currencyInfo.name, - text: `${currencyCode} - ${CurrencyUtils.getLocalizedCurrencySymbol(currencyCode)}`, + text: `${currencyCode} - ${CurrencyUtils.getCurrencySymbol(currencyCode)}`, currencyCode, keyForList: currencyCode, isSelected: isSelectedCurrency, From 06e5faa05332dd6969f949dcbb12bdc77b3cbe3f Mon Sep 17 00:00:00 2001 From: Sheena Trepanier Date: Wed, 31 Jan 2024 16:33:41 -0800 Subject: [PATCH 006/661] Create unlimited-virtual-cards.md This is a new help doc for the imminent release of Unlimited Virtual Cards. --- .../expensify-card/unlimited-virtual-cards.md | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 docs/articles/expensify-classic/expensify-card/unlimited-virtual-cards.md diff --git a/docs/articles/expensify-classic/expensify-card/unlimited-virtual-cards.md b/docs/articles/expensify-classic/expensify-card/unlimited-virtual-cards.md new file mode 100644 index 000000000000..c5578249289a --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/unlimited-virtual-cards.md @@ -0,0 +1,86 @@ +--- +title: Unlimited Virtual Cards +description: Learn more about virtual cards and how they can help your business gain efficiency and insight into company spending. +--- + +# Overview + +For admins to issue virtual cards, your company **must upgrade to Expensify’s new Expensify Visa® Commercial Card.** + +Once upgraded to the new Expensify Card, admins can issue an unlimited number of virtual cards with a fixed or monthly limit for specific company purchases or recurring subscription payments _(e.g., Marketing purchases, Advertising, Travel, Amazon Web Services, etc.)._ + +This feature supports businesses that require tighter controls on company spending, allowing customers to set fixed or monthly spending limits for each virtual card. + +Use virtual cards if your company needs or wants: + +- To use one card per vendor or subscription, +- To issue cards for one-time purchases with a fixed amount, +- To issue cards for events or trips, +- To issue cards with a low limit that renews monthly, + +Admins can also name each virtual card, making it easy to categorize and assign them to specific accounts upon creation. Naming the card ensures a clear and organized overview of expenses within the Expensify platform. + +# How to set up virtual cards + +After adopting the new Expensify Card, domain admins can issue virtual cards to any employee using an email matching your domain. Once created and assigned, the card will be visible under the name given to the card. + +**To assign a virtual card:** + +1. Head to **Settings** > **Domains** > [**Company Cards**](https://www.expensify.com/domain_companycards). +2. Click the **Issue Virtual Cards** button. +3. Enter a card name (i.e., "Google Ads"). +4. Select a domain member to assign the card to. +5. Enter a card limit. +6. Select a **Limit Type** of _Fixed_ or _Monthly_. +7. Click **Issue Card**. + +![The Issue Virtual Cards modal is open in the middle of the screen. There are four options to set; Card Name, Assignee, Card Limit, and Limit type. A cancel (left) and save (right) button are at the bottom right of the modal.]({{site.url}}/assets/images/AdminissuedVirtualCards.png){:width="100%"} + +# How to edit virtual cards + +Domain admin can update the details of a virtual card on the [Company Cards](https://www.expensify.com/domain_companycards) page. + +**To edit a virtual card:** + +1. Click the **Edit** button to the right of the card. +2. Change the editable details. +3. Click **Edit Card** to save the changes. + +# How to terminate a virtual card + +Domain admin can also terminate a virtual card on the [Company Cards](https://www.expensify.com/domain_companycards) page by setting the limit to $0. + +**To terminate a virtual card:** + +1. Click the **Edit** button to the right of the card. +2. Set the limit to $0. +3. Click **Save**. +4. Refresh your web page, and the card will be removed from the list. + +{% include faq-begin.md %} + +**What is the difference between a fixed limit and a monthly limit?** + +There are two different limit types that are best suited for their intended purpose. + +- _Fixed limit_ spend cards are ideal for one-time expenses or providing employees access to a card for a designated purchase. +- _Monthly_ limit spend cards are perfect for managing recurring expenses such as subscriptions and memberships. + +**Where can employees see their virtual cards?** + +Employees can see their assigned virtual cards by navigating to **Settings** > **Account** > [**Credit Cards Import**](https://www.expensify.com/settings?param=%7B%22section%22:%22creditcards%22%7D) in their account. + +On this page, employees can see the remaining card limit, the type of card it is (i.e., fixed or monthly), and view the name given to the card. + +When the employee needs to use the card, they’ll click the **Show Details** button to expose the card details for making purchases. + +_Note: If the employee doesn’t have Two-Factor Authentication (2FA) enabled when they display the card details, they’ll be prompted to enable it. Enabling 2FA for their account provides the best protection from fraud and is **required** to dispute virtual card expenses._ + +**What do I do when there is fraud on one of our virtual cards?** + +If you or an employee loses their virtual card, experiences fraud, or suspects the card details are no longer secure, please [request a new card](https://help.expensify.com/articles/expensify-classic/expensify-card/Dispute-A-Transaction) immediately. A domain admin can also set the limit for the card to $0 to terminate the specific card immediately if the employee cannot take action. + +When the employee requests a new card, the compromised card will be terminated immediately. This is best practice for any Expensify Card and if fraud is suspected, action should be taken as soon as possible to reduce financial impact on the company. + +{% include faq-end.md %} + From 93072a93a9f792e0a63a5660b6bb8bbfb83429d6 Mon Sep 17 00:00:00 2001 From: Sheena Trepanier Date: Wed, 31 Jan 2024 16:37:45 -0800 Subject: [PATCH 007/661] Add files via upload --- docs/assets/images/AdminissuedVirtualCards.png | Bin 0 -> 157289 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/assets/images/AdminissuedVirtualCards.png diff --git a/docs/assets/images/AdminissuedVirtualCards.png b/docs/assets/images/AdminissuedVirtualCards.png new file mode 100644 index 0000000000000000000000000000000000000000..88df9b2f3fecef011db3e71c7eb12c02847cc97b GIT binary patch literal 157289 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 z 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 Date: Thu, 22 Feb 2024 00:58:36 +0700 Subject: [PATCH 008/661] fix: 32978 --- src/libs/SidebarUtils.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 49436576295c..9ac5227399f8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -292,27 +292,35 @@ function getOptionData({ const isThreadMessage = ReportUtils.isThread(report) && reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && reportAction?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + // Currently the back-end is not returning the `lastActionName` so I use this to mock, + // ideally it should be returned from the back-end in the `report`, like the `lastMessageText` and `lastMessageHtml` + if (report.lastMessageHtml && report.lastMessageHtml.includes('invited ]*><\/mention-user>/g)?.length ?? []; const verb = - lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM + lastActionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastActionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM ? Localize.translate(preferredLocale, 'workspace.invite.invited') : Localize.translate(preferredLocale, 'workspace.invite.removed'); - const users = Localize.translate(preferredLocale, targetAccountIDs.length > 1 ? 'workspace.invite.users' : 'workspace.invite.user'); - result.alternateText = `${lastActorDisplayName} ${verb} ${targetAccountIDs.length} ${users}`.trim(); + const users = Localize.translate(preferredLocale, targetAccountIDsLength > 1 ? 'workspace.invite.users' : 'workspace.invite.user'); + result.alternateText = `${lastActorDisplayName} ${verb} ${targetAccountIDsLength} ${users}`.trim(); const roomName = lastAction?.originalMessage?.roomName ?? ''; if (roomName) { From 9467b3a7f068b071f380ae36a3e39aeeb607b1e1 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 6 Mar 2024 13:35:59 +0530 Subject: [PATCH 009/661] feat: Receipt Audit Feature / Note type violations. Signed-off-by: Krishna Gupta --- src/components/ReceiptAudit.tsx | 33 +++++++++++++++++++ .../ReportActionItem/MoneyRequestView.tsx | 4 +++ src/styles/index.ts | 13 ++++++++ 3 files changed, 50 insertions(+) create mode 100644 src/components/ReceiptAudit.tsx diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx new file mode 100644 index 000000000000..af8c063668e1 --- /dev/null +++ b/src/components/ReceiptAudit.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import {View} from 'react-native'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; +import Text from './Text'; + +export default function ReceiptAudit({notes}: {notes: string[]}) { + const styles = useThemeStyles(); + const theme = useTheme(); + + return ( + + + + 0 ? Expensicons.Receipt : Expensicons.Checkmark} + fill={theme.white} + /> + + {notes.length > 0 ? `Receipt Audit : ${notes.length} Issue(s) Found` : 'Receipt Verified : No issues Found'} + + + + + {/* // If notes is a array of strings, map through it & show notes. */} + {notes.length > 0 && notes.map((message) => {message})} + + ); +} diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 0bd18d8ee7ea..234aa4465d40 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -6,6 +6,7 @@ import ConfirmedRoute from '@components/ConfirmedRoute'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import ReceiptAudit from '@components/ReceiptAudit'; import ReceiptEmptyState from '@components/ReceiptEmptyState'; import SpacerView from '@components/SpacerView'; import Switch from '@components/Switch'; @@ -285,6 +286,9 @@ function MoneyRequestView({ } /> )} + + + {canUseViolations && } borderWidth: 1, }, + receiptAuditTitleContainer: { + flexDirection: 'row', + gap: 4, + padding: 4, + paddingHorizontal: 8, + height: variables.inputHeightSmall, + borderRadius: variables.componentBorderRadiusSmall, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: theme.border, + }, + mapViewContainer: { ...flex.flex1, minHeight: 300, From 8c400c3fae12d28f95ded76a52806e92ec54f740 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 6 Mar 2024 14:57:49 +0530 Subject: [PATCH 010/661] added translations. Signed-off-by: Krishna Gupta --- src/components/ReceiptAudit.tsx | 8 +++++--- src/components/ReportActionItem/MoneyRequestView.tsx | 5 ++++- src/languages/en.ts | 4 ++++ src/languages/es.ts | 4 ++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index af8c063668e1..7f11e9b9a1e4 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Icon from './Icon'; @@ -9,7 +10,9 @@ import Text from './Text'; export default function ReceiptAudit({notes}: {notes: string[]}) { const styles = useThemeStyles(); const theme = useTheme(); + const {translate} = useLocalize(); + const issuesFoundText = notes.length > 0 ? translate('iou.receiptIssuesFound', notes.length) : translate('iou.receiptNoIssuesFound'); return ( @@ -20,10 +23,9 @@ export default function ReceiptAudit({notes}: {notes: string[]}) { src={notes.length > 0 ? Expensicons.Receipt : Expensicons.Checkmark} fill={theme.white} /> - - {notes.length > 0 ? `Receipt Audit : ${notes.length} Issue(s) Found` : 'Receipt Verified : No issues Found'} - + {notes.length > 0 ? translate('iou.receiptAudit') : translate('iou.receiptVerified')} + {issuesFoundText} {/* // If notes is a array of strings, map through it & show notes. */} diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 234aa4465d40..639739780a73 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -287,7 +287,10 @@ function MoneyRequestView({ /> )} - + {canUseViolations && } diff --git a/src/languages/en.ts b/src/languages/en.ts index 0a52cca62ef5..f073e79335ea 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -601,6 +601,10 @@ export default { posted: 'Posted', deleteReceipt: 'Delete receipt', routePending: 'Route pending...', + receiptAudit: 'Receipt Audit', + receiptVerified: 'Receipt Verified', + receiptNoIssuesFound: 'No issues Found', + receiptIssuesFound: (count: number) => `${count} Issue(s) Found`, receiptScanning: 'Scan in progress…', receiptMissingDetails: 'Receipt missing details', receiptStatusTitle: 'Scanning…', diff --git a/src/languages/es.ts b/src/languages/es.ts index 013255c1e11e..b908300f4e46 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -594,6 +594,10 @@ export default { posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', routePending: 'Ruta pendiente...', + receiptAudit: 'Auditoría de recibos', + receiptVerified: 'Recibo verificado', + receiptNoIssuesFound: 'No se encontraron problemas', + receiptIssuesFound: (count: number) => `Se encontró ${count} problema(s)`, receiptScanning: 'Escaneo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', receiptStatusTitle: 'Escaneando…', From 96744d5c4aa2622075e9a2febbe7d2c357eb740d Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 6 Mar 2024 15:56:41 +0530 Subject: [PATCH 011/661] extract noticeViolations from transactionViolations. Signed-off-by: Krishna Gupta --- src/components/ReceiptAudit.tsx | 6 +++--- src/components/ReportActionItem/MoneyRequestView.tsx | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index 7f11e9b9a1e4..f756ecd5dc7e 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -7,14 +7,14 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; -export default function ReceiptAudit({notes}: {notes: string[]}) { +export default function ReceiptAudit({notes = []}: {notes?: string[]}) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); const issuesFoundText = notes.length > 0 ? translate('iou.receiptIssuesFound', notes.length) : translate('iou.receiptNoIssuesFound'); return ( - + {/* // If notes is a array of strings, map through it & show notes. */} - {notes.length > 0 && notes.map((message) => {message})} + {notes.length > 0 && notes.map((message) => {message})} ); } diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 639739780a73..c4f067e35e89 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -147,6 +147,7 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); + const noticeViolations = transactionViolations?.filter((violation) => violation.type === 'notice').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); let amountDescription = `${translate('iou.amount')}`; @@ -286,11 +287,7 @@ function MoneyRequestView({ } /> )} - - + {noticeViolations?.length && } {canUseViolations && } From 952a4dac2dff9293d91ce53a58467042262bb531 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 7 Mar 2024 11:50:47 +0530 Subject: [PATCH 012/661] Update the dot separator sub-state for the request preview. Signed-off-by: Krishna Gupta --- .../MoneyRequestPreviewContent.tsx | 3 +++ .../ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/TransactionUtils.ts | 13 +++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 8577c9fa5f97..fb524c6b5cb2 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -87,6 +87,7 @@ function MoneyRequestPreviewContent({ const hasReceipt = TransactionUtils.hasReceipt(transaction); const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '', transactionViolations); + const hasNoteTypeViolations = TransactionUtils.hasNoteTypeViolation(transaction?.transactionID ?? '', transactionViolations); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const shouldShowRBR = hasViolations || hasFieldErrors; const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); @@ -159,6 +160,8 @@ function MoneyRequestPreviewContent({ const isTooLong = violations.filter((v) => v.type === 'violation').length > 1 || violationMessage.length > 15; message += ` • ${isTooLong ? translate('violations.reviewRequired') : violationMessage}`; } + } else if (hasNoteTypeViolations && transaction && !ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) { + message += ` • ${translate('violations.reviewRequired')}`; } else if (ReportUtils.isPaidGroupPolicyExpenseReport(iouReport) && ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) { message += ` • ${translate('iou.approved')}`; } else if (iouReport?.isWaitingOnBankAccount) { diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index c4f067e35e89..440f8afb73ee 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -147,7 +147,7 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); - const noticeViolations = transactionViolations?.filter((violation) => violation.type === 'notice').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); + const noticeViolations = transactionViolations?.filter((violation) => violation.type === 'note').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); let amountDescription = `${translate('iou.amount')}`; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 8a98fe0f2cdc..5dea541b2af2 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -593,6 +593,17 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'violation')); } +/** + * Checks if any violations for the provided transaction are of type 'note' + */ +function hasNoteTypeViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { + return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'note')); +} + +function getTransactionNoteViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null { + return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation: TransactionViolation) => violation.type === 'note') ?? null; +} + function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null { return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; } @@ -638,6 +649,7 @@ export { getTagArrayFromName, getTagForDisplay, getTransactionViolations, + getTransactionNoteViolations, getLinkedTransaction, getAllReportTransactions, hasReceipt, @@ -663,6 +675,7 @@ export { waypointHasValidAddress, getRecentTransactions, hasViolation, + hasNoteTypeViolation, }; export type {TransactionChanges}; From 8438383448a735e0d5dd871e10ae0e27f85fd9f4 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 7 Mar 2024 11:51:50 +0530 Subject: [PATCH 013/661] Remove redundant code. Signed-off-by: Krishna Gupta --- src/libs/TransactionUtils.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 5dea541b2af2..bc2fcaa39290 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -600,10 +600,6 @@ function hasNoteTypeViolation(transactionID: string, transactionViolations: Onyx return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'note')); } -function getTransactionNoteViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null { - return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation: TransactionViolation) => violation.type === 'note') ?? null; -} - function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null { return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; } @@ -649,7 +645,6 @@ export { getTagArrayFromName, getTagForDisplay, getTransactionViolations, - getTransactionNoteViolations, getLinkedTransaction, getAllReportTransactions, hasReceipt, From 7f975195bf133d3ef94d5b1123a53b5fd171a457 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 7 Mar 2024 11:56:22 +0530 Subject: [PATCH 014/661] minor fix. Signed-off-by: Krishna Gupta --- src/components/ReportActionItem/MoneyRequestView.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 440f8afb73ee..481bcb177569 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -147,7 +147,12 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); - const noticeViolations = transactionViolations?.filter((violation) => violation.type === 'note').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); + const noticeViolations = [ + {name: 'missingComment', type: 'violation'}, + {name: 'modifiedDate', type: 'violation'}, + ] + ?.filter((violation) => violation.type === 'note') + .map((v) => ViolationsUtils.getViolationTranslation(v, translate)); let amountDescription = `${translate('iou.amount')}`; @@ -287,7 +292,7 @@ function MoneyRequestView({ } /> )} - {noticeViolations?.length && } + {canUseViolations && } From cebc960dcf6e17bc04f2c0ed7158c91e2b112a23 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 7 Mar 2024 12:20:13 +0530 Subject: [PATCH 015/661] minor fix. Signed-off-by: Krishna Gupta --- src/components/ReportActionItem/MoneyRequestView.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 481bcb177569..79639a62babe 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -147,12 +147,7 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); - const noticeViolations = [ - {name: 'missingComment', type: 'violation'}, - {name: 'modifiedDate', type: 'violation'}, - ] - ?.filter((violation) => violation.type === 'note') - .map((v) => ViolationsUtils.getViolationTranslation(v, translate)); + const noteTypeViolations = transactionViolations?.filter((violation) => violation.type === 'note').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); let amountDescription = `${translate('iou.amount')}`; @@ -292,8 +287,7 @@ function MoneyRequestView({ } /> )} - - + {canUseViolations && } Date: Thu, 7 Mar 2024 15:25:49 +0530 Subject: [PATCH 016/661] hide ReceiptAudit when scan is in progress. Signed-off-by: Krishna Gupta --- src/components/ReportActionItem/MoneyRequestView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 79639a62babe..415a2666512a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -129,6 +129,7 @@ function MoneyRequestView({ const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); + const isReceiptBeingScanned = TransactionUtils.hasReceipt(transaction) && !TransactionUtils.isReceiptBeingScanned(transaction); // A flag for verifying that the current report is a sub-report of a workspace chat // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat @@ -287,7 +288,7 @@ function MoneyRequestView({ } /> )} - + {!isReceiptBeingScanned && canUseViolations && } {canUseViolations && } Date: Fri, 8 Mar 2024 16:53:21 +0530 Subject: [PATCH 017/661] minor updates. Signed-off-by: Krishna Gupta --- src/components/ReceiptAudit.tsx | 2 -- src/components/ReportActionItem/MoneyRequestView.tsx | 7 ++++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index f756ecd5dc7e..5b91fe97ca0d 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -27,8 +27,6 @@ export default function ReceiptAudit({notes = []}: {notes?: string[]}) { {issuesFoundText} - - {/* // If notes is a array of strings, map through it & show notes. */} {notes.length > 0 && notes.map((message) => {message})} ); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 415a2666512a..ffe373aade00 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -129,7 +129,8 @@ function MoneyRequestView({ const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); - const isReceiptBeingScanned = TransactionUtils.hasReceipt(transaction) && !TransactionUtils.isReceiptBeingScanned(transaction); + const hasReceipt = TransactionUtils.hasReceipt(transaction); + const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); // A flag for verifying that the current report is a sub-report of a workspace chat // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat @@ -149,6 +150,7 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); const noteTypeViolations = transactionViolations?.filter((violation) => violation.type === 'note').map((v) => ViolationsUtils.getViolationTranslation(v, translate)); + const shouldShowNotesViolations = !isReceiptBeingScanned && canUseViolations && hasReceipt; let amountDescription = `${translate('iou.amount')}`; @@ -190,7 +192,6 @@ function MoneyRequestView({ } } - const hasReceipt = TransactionUtils.hasReceipt(transaction); let receiptURIs; const hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction); if (hasReceipt) { @@ -288,7 +289,7 @@ function MoneyRequestView({ } /> )} - {!isReceiptBeingScanned && canUseViolations && } + {shouldShowNotesViolations && } {canUseViolations && } Date: Mon, 11 Mar 2024 11:50:00 +0530 Subject: [PATCH 018/661] Update MoneyRequestView.tsx --- 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 3569791d64d4..d79ff88a4f1e 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -147,7 +147,7 @@ function MoneyRequestView({ const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true)); const {getViolationsForField} = useViolations(transactionViolations ?? []); - const hasViolations = useCallback( + const hasViolations = useCallback( (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0, [canUseViolations, getViolationsForField], ); From 48d1543f0ff3a75aea75eb2bb9f176671475c5dd Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 12 Mar 2024 13:12:52 +0530 Subject: [PATCH 019/661] fix: translations. Signed-off-by: Krishna Gupta --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 2c4f5d21f66f..7979981392fe 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -604,7 +604,7 @@ export default { receiptAudit: 'Receipt Audit', receiptVerified: 'Receipt Verified', receiptNoIssuesFound: 'No issues Found', - receiptIssuesFound: (count: number) => `${count} Issue(s) Found`, + receiptIssuesFound: (count: number) => `${count} ${count === 1 ? 'Issue' : 'Issues'} Found`, receiptScanning: 'Scan in progress…', receiptMissingDetails: 'Receipt missing details', receiptStatusTitle: 'Scanning…', diff --git a/src/languages/es.ts b/src/languages/es.ts index 2f6a064a3615..730d110a60a9 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -597,7 +597,7 @@ export default { receiptAudit: 'Auditoría de recibos', receiptVerified: 'Recibo verificado', receiptNoIssuesFound: 'No se encontraron problemas', - receiptIssuesFound: (count: number) => `Se encontró ${count} problema(s)`, + receiptIssuesFound: (count: number) => `Se encontró ${count} ${count === 1 ? 'problema' : 'problemas'}`, receiptScanning: 'Escaneo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', receiptStatusTitle: 'Escaneando…', From 42b22e0383afdfa5221f116b197eb2877834c085 Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 13 Mar 2024 10:52:07 +0100 Subject: [PATCH 020/661] Initial config to workspace address page --- src/ROUTES.ts | 4 +++ src/SCREENS.ts | 1 + src/languages/en.ts | 1 + src/languages/es.ts | 1 + .../AppNavigator/ModalStackNavigators.tsx | 1 + .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 3 ++ src/libs/Navigation/types.ts | 1 + .../workspace/WorkspaceProfileAddressPage.tsx | 32 +++++++++++++++++++ src/pages/workspace/WorkspaceProfilePage.tsx | 17 ++++++++++ 10 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/pages/workspace/WorkspaceProfileAddressPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index d9f0c6658a2b..2b27179db7a0 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -465,6 +465,10 @@ const ROUTES = { route: 'workspace/:policyID/profile', getRoute: (policyID: string) => `workspace/${policyID}/profile` as const, }, + WORKSPACE_PROFILE_ADDRESS: { + route: 'workspace/:policyID/profile/address', + getRoute: (policyID: string) => `workspace/${policyID}/profile/address` as const, + }, WORKSPACE_PROFILE_CURRENCY: { route: 'workspace/:policyID/profile/currency', getRoute: (policyID: string) => `workspace/${policyID}/profile/currency` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index a0e06b98da2b..b5c0040dd9f7 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -219,6 +219,7 @@ const SCREENS = { TAGS_SETTINGS: 'Tags_Settings', TAGS_EDIT: 'Tags_Edit', CURRENCY: 'Workspace_Profile_Currency', + ADDRESS: 'Workspace_Profile_Address', WORKFLOWS: 'Workspace_Workflows', WORKFLOWS_APPROVER: 'Workspace_Workflows_Approver', WORKFLOWS_AUTO_REPORTING_FREQUENCY: 'Workspace_Workflows_Auto_Reporting_Frequency', diff --git a/src/languages/en.ts b/src/languages/en.ts index ff91a4f6f205..9aeca12dd5d7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1984,6 +1984,7 @@ export default { currencyInputLabel: 'Default currency', currencyInputHelpText: 'All expenses on this workspace will be converted to this currency.', currencyInputDisabledText: "The default currency can't be changed because this workspace is linked to a USD bank account.", + addressInputLabel: 'Company address', save: 'Save', genericFailureMessage: 'An error occurred updating the workspace, please try again.', avatarUploadFailureMessage: 'An error occurred uploading the avatar, please try again.', diff --git a/src/languages/es.ts b/src/languages/es.ts index c21f46ed8853..9580ec8df535 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2009,6 +2009,7 @@ export default { currencyInputLabel: 'Moneda por defecto', currencyInputHelpText: 'Todas los gastos en este espacio de trabajo serán convertidos a esta moneda.', currencyInputDisabledText: 'La moneda predeterminada no se puede cambiar porque este espacio de trabajo está vinculado a una cuenta bancaria en USD.', + addressInputLabel: 'Dirección de la empresa', save: 'Guardar', genericFailureMessage: 'Se produjo un error al guardar el espacio de trabajo. Por favor, inténtalo de nuevo.', avatarUploadFailureMessage: 'No se pudo subir el avatar. Por favor, inténtalo de nuevo.', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index d56e38564149..df67de82d635 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -249,6 +249,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/WorkspaceProfileDescriptionPage').default as React.ComponentType, [SCREENS.WORKSPACE.SHARE]: () => require('../../../pages/workspace/WorkspaceProfileSharePage').default as React.ComponentType, [SCREENS.WORKSPACE.CURRENCY]: () => require('../../../pages/workspace/WorkspaceProfileCurrencyPage').default as React.ComponentType, + [SCREENS.WORKSPACE.ADDRESS]: () => require('../../../pages/workspace/WorkspaceProfileAddressPage').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, 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 743bf2e0cff1..f6fe75901848 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -2,7 +2,7 @@ import type {CentralPaneName} from '@libs/Navigation/types'; 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.PROFILE]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.CURRENCY, SCREENS.WORKSPACE.ADDRESS, 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.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], diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 97d7650a9043..296ccc30ba69 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -249,6 +249,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.CURRENCY]: { path: ROUTES.WORKSPACE_PROFILE_CURRENCY.route, }, + [SCREENS.WORKSPACE.ADDRESS]: { + path: ROUTES.WORKSPACE_PROFILE_ADDRESS.route, + }, [SCREENS.WORKSPACE.DESCRIPTION]: { path: ROUTES.WORKSPACE_PROFILE_DESCRIPTION.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 33e79b637cc4..1443a60f4b16 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -186,6 +186,7 @@ type SettingsNavigatorParamList = { [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: undefined; [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: undefined; [SCREENS.WORKSPACE.CURRENCY]: undefined; + [SCREENS.WORKSPACE.ADDRESS]: undefined; [SCREENS.WORKSPACE.NAME]: undefined; [SCREENS.WORKSPACE.DESCRIPTION]: undefined; [SCREENS.WORKSPACE.SHARE]: undefined; diff --git a/src/pages/workspace/WorkspaceProfileAddressPage.tsx b/src/pages/workspace/WorkspaceProfileAddressPage.tsx new file mode 100644 index 000000000000..80f9066040be --- /dev/null +++ b/src/pages/workspace/WorkspaceProfileAddressPage.tsx @@ -0,0 +1,32 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {Text, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import ScreenWrapper from '@components/ScreenWrapper'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import type {PrivatePersonalDetails} from '@src/types/onyx'; + +type WorkspaceProfileAddressPageOnyxProps = { + /** User's private personal details */ + privatePersonalDetails: OnyxEntry; +}; + +type WorkspaceProfileAddressPageProps = StackScreenProps & WorkspaceProfileAddressPageOnyxProps; + +function WorkspaceProfileAddressPage({privatePersonalDetails, route}: WorkspaceProfileAddressPageProps) { + return ( + + abc + + ); +} + +WorkspaceProfileAddressPage.displayName = 'WorkspaceProfileAddressPage'; + +export default WorkspaceProfileAddressPage; diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index 9d90557b1d37..154708688fb0 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -50,7 +50,10 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi const currencySymbol = currencyList?.[outputCurrency]?.symbol ?? ''; const formattedCurrency = !isEmptyObject(policy) && !isEmptyObject(currencyList) ? `${outputCurrency} - ${currencySymbol}` : ''; + const formattedAddress = 'abc'; + const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_CURRENCY.getRoute(policy?.id ?? '')), [policy?.id]); + const onPressAddress = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(policy?.id ?? '')), [policy?.id]); const onPressName = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_NAME.getRoute(policy?.id ?? '')), [policy?.id]); const onPressDescription = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_DESCRIPTION.getRoute(policy?.id ?? '')), [policy?.id]); const onPressShare = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_SHARE.getRoute(policy?.id ?? '')), [policy?.id]); @@ -186,6 +189,20 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi + + + + + {!readOnly && ( + + )} ) : ( - + ); } diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx deleted file mode 100644 index 64391909b197..000000000000 --- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import {Animated, View} from 'react-native'; -import useThemeStyles from '@hooks/useThemeStyles'; -import type FloatingMessageCounterContainerProps from './types'; - -function FloatingMessageCounterContainer({containerStyles, children}: FloatingMessageCounterContainerProps) { - const styles = useThemeStyles(); - - return ( - - {children} - - ); -} - -FloatingMessageCounterContainer.displayName = 'FloatingMessageCounterContainer'; - -export default FloatingMessageCounterContainer; diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx deleted file mode 100644 index 8757d66160c4..000000000000 --- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import {Animated} from 'react-native'; -import useThemeStyles from '@hooks/useThemeStyles'; -import type FloatingMessageCounterContainerProps from './types'; - -function FloatingMessageCounterContainer({accessibilityHint, containerStyles, children}: FloatingMessageCounterContainerProps) { - const styles = useThemeStyles(); - - return ( - - {children} - - ); -} - -FloatingMessageCounterContainer.displayName = 'FloatingMessageCounterContainer'; - -export default FloatingMessageCounterContainer; diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts deleted file mode 100644 index cfe791eed79c..000000000000 --- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type {StyleProp, ViewStyle} from 'react-native'; -import type ChildrenProps from '@src/types/utils/ChildrenProps'; - -type FloatingMessageCounterContainerProps = ChildrenProps & { - /** Styles to be assigned to Container */ - containerStyles?: StyleProp; - - /** Specifies the accessibility hint for the component */ - accessibilityHint?: string; -}; - -export default FloatingMessageCounterContainerProps; From 35ea3650ee17950c264c2af6275fec819511e24b Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Apr 2024 17:28:44 -0700 Subject: [PATCH 143/661] Remove unused styles --- src/styles/index.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 537038d9f2e1..9bba82ac6b95 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3341,21 +3341,6 @@ const styles = (theme: ThemeColors) => ...visibility.hidden, }, - floatingMessageCounterWrapperAndroid: { - left: 0, - width: '100%', - alignItems: 'center', - position: 'absolute', - top: 0, - zIndex: 100, - ...visibility.hidden, - }, - - floatingMessageCounterSubWrapperAndroid: { - left: '50%', - width: 'auto', - }, - floatingMessageCounter: { left: '-50%', ...visibility.visible, From fda608b3b896d4d2ea2ede7101b5cd42ad85c93f Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Apr 2024 17:31:41 -0700 Subject: [PATCH 144/661] Combine styles together --- src/pages/home/report/FloatingMessageCounter.tsx | 2 +- src/styles/index.ts | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/home/report/FloatingMessageCounter.tsx b/src/pages/home/report/FloatingMessageCounter.tsx index 997198192bb6..34eb01b62817 100644 --- a/src/pages/home/report/FloatingMessageCounter.tsx +++ b/src/pages/home/report/FloatingMessageCounter.tsx @@ -52,7 +52,7 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating return ( diff --git a/src/styles/index.ts b/src/styles/index.ts index 9bba82ac6b95..df94258b5c77 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3333,24 +3333,20 @@ const styles = (theme: ThemeColors) => height: variables.communicationsLinkHeight, }, - floatingMessageCounterWrapper: { + floatingMessageCounterWrapper: (translateY: AnimatableNumericValue) => ({ position: 'absolute', left: '50%', top: 0, zIndex: 100, + transform: [{translateY}], ...visibility.hidden, - }, + }), floatingMessageCounter: { left: '-50%', ...visibility.visible, }, - floatingMessageCounterTransformation: (translateY: AnimatableNumericValue) => - ({ - transform: [{translateY}], - } satisfies ViewStyle), - confirmationAnimation: { height: 180, width: 180, From cf6d23e8bd6eefe3af55e1b6c529e336d69e1ada Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Apr 2024 17:48:03 -0700 Subject: [PATCH 145/661] Migrate animation to reanimated --- .../home/report/FloatingMessageCounter.tsx | 27 ++++++++++--------- src/styles/index.ts | 5 ++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/pages/home/report/FloatingMessageCounter.tsx b/src/pages/home/report/FloatingMessageCounter.tsx index 34eb01b62817..8e73d36a878a 100644 --- a/src/pages/home/report/FloatingMessageCounter.tsx +++ b/src/pages/home/report/FloatingMessageCounter.tsx @@ -1,5 +1,6 @@ import React, {useCallback, useEffect, useMemo} from 'react'; -import {Animated, View} from 'react-native'; +import {View} from 'react-native'; +import Animated, {useAnimatedStyle, useSharedValue, withSpring} from 'react-native-reanimated'; import Button from '@components/Button'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -7,7 +8,6 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useNativeDriver from '@libs/useNativeDriver'; import CONST from '@src/CONST'; type FloatingMessageCounterProps = { @@ -25,20 +25,18 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const translateY = useMemo(() => new Animated.Value(MARKER_INACTIVE_TRANSLATE_Y), []); + const translateY = useSharedValue(MARKER_INACTIVE_TRANSLATE_Y); const show = useCallback(() => { - Animated.spring(translateY, { - toValue: MARKER_ACTIVE_TRANSLATE_Y, - useNativeDriver, - }).start(); + 'worklet'; + + translateY.value = withSpring(MARKER_ACTIVE_TRANSLATE_Y); }, [translateY]); const hide = useCallback(() => { - Animated.spring(translateY, { - toValue: MARKER_INACTIVE_TRANSLATE_Y, - useNativeDriver, - }).start(); + 'worklet'; + + translateY.value = withSpring(MARKER_INACTIVE_TRANSLATE_Y); }, [translateY]); useEffect(() => { @@ -49,10 +47,15 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating } }, [isActive, show, hide]); + const wrapperStyle = useAnimatedStyle(() => ({ + ...styles.floatingMessageCounterWrapper, + transform: [{translateY: translateY.value}], + })); + return ( diff --git a/src/styles/index.ts b/src/styles/index.ts index df94258b5c77..0badb7412b1a 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3333,14 +3333,13 @@ const styles = (theme: ThemeColors) => height: variables.communicationsLinkHeight, }, - floatingMessageCounterWrapper: (translateY: AnimatableNumericValue) => ({ + floatingMessageCounterWrapper: { position: 'absolute', left: '50%', top: 0, zIndex: 100, - transform: [{translateY}], ...visibility.hidden, - }), + }, floatingMessageCounter: { left: '-50%', From 1044bad394543fde51d2e89b6f935444b5fd3779 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 17 Apr 2024 11:49:42 +0700 Subject: [PATCH 146/661] rename props Avatar --- src/components/Avatar.tsx | 6 +++--- src/components/HeaderWithBackButton/index.tsx | 2 +- src/components/MentionSuggestions.tsx | 2 +- src/components/MultipleAvatars.tsx | 8 ++++---- src/components/RoomHeaderAvatars.tsx | 4 ++-- src/components/SubscriptAvatar.tsx | 4 ++-- .../UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx | 2 +- src/pages/home/report/ReportActionItemSingle.tsx | 2 +- src/pages/workspace/WorkspaceProfilePage.tsx | 2 +- src/pages/workspace/WorkspacesListRow.tsx | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index dfcf2cc675b9..09d22132b44e 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -51,7 +51,7 @@ type AvatarProps = { name?: string; /** ID of the Icon */ - iconID?: number | string; + accountID?: number | string; }; function Avatar({ @@ -65,7 +65,7 @@ function Avatar({ fallbackIconTestID = '', type = CONST.ICON_TYPE_AVATAR, name = '', - iconID, + accountID, }: AvatarProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -92,7 +92,7 @@ function Avatar({ let iconColors; if (isWorkspace) { - iconColors = StyleUtils.getDefaultWorkspaceAvatarColor(iconID?.toString() ?? ''); + iconColors = StyleUtils.getDefaultWorkspaceAvatarColor(accountID?.toString() ?? ''); } else if (useFallBackAvatar) { iconColors = StyleUtils.getBackgroundColorAndFill(theme.border, theme.icon); } else { diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx index 6e649955880a..29d010d9c803 100755 --- a/src/components/HeaderWithBackButton/index.tsx +++ b/src/components/HeaderWithBackButton/index.tsx @@ -182,7 +182,7 @@ function HeaderWithBackButton({ containerStyles={[StyleUtils.getWidthAndHeightStyle(StyleUtils.getAvatarSize(CONST.AVATAR_SIZE.DEFAULT)), styles.mr3]} source={policyAvatar?.source} name={policyAvatar?.name} - iconID={policyAvatar?.id} + accountID={policyAvatar?.id} type={policyAvatar?.type} /> )} diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx index e69484eacadd..b11ae4f5ecd8 100644 --- a/src/components/MentionSuggestions.tsx +++ b/src/components/MentionSuggestions.tsx @@ -83,7 +83,7 @@ function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSe source={item.icons[0].source} size={isIcon ? CONST.AVATAR_SIZE.MENTION_ICON : CONST.AVATAR_SIZE.SMALLER} name={item.icons[0].name} - iconID={item.icons[0].id} + accountID={item.icons[0].id} type={item.icons[0].type} fill={isIcon ? theme.success : undefined} fallbackIcon={item.icons[0].fallbackIcon} diff --git a/src/components/MultipleAvatars.tsx b/src/components/MultipleAvatars.tsx index cbceace6df16..d5fcf6607179 100644 --- a/src/components/MultipleAvatars.tsx +++ b/src/components/MultipleAvatars.tsx @@ -156,7 +156,7 @@ function MultipleAvatars({ size={size} fill={icons[0].fill} name={icons[0].name} - iconID={icons[0].id} + accountID={icons[0].id} type={icons[0].type} fallbackIcon={icons[0].fallbackIcon} /> @@ -206,7 +206,7 @@ function MultipleAvatars({ source={icon.source ?? fallbackIcon} size={size} name={icon.name} - iconID={icon.id} + accountID={icon.id} type={icon.type} fallbackIcon={icon.fallbackIcon} /> @@ -265,7 +265,7 @@ function MultipleAvatars({ imageStyles={[singleAvatarStyle]} name={icons[0].name} type={icons[0].type} - iconID={icons[0].id} + accountID={icons[0].id} fallbackIcon={icons[0].fallbackIcon} /> @@ -285,7 +285,7 @@ function MultipleAvatars({ size={avatarSize} imageStyles={[singleAvatarStyle]} name={icons[1].name} - iconID={icons[1].id} + accountID={icons[1].id} type={icons[1].type} fallbackIcon={icons[1].fallbackIcon} /> diff --git a/src/components/RoomHeaderAvatars.tsx b/src/components/RoomHeaderAvatars.tsx index 341398e44b06..bdb4a0ac78ab 100644 --- a/src/components/RoomHeaderAvatars.tsx +++ b/src/components/RoomHeaderAvatars.tsx @@ -47,7 +47,7 @@ function RoomHeaderAvatars({icons, reportID}: RoomHeaderAvatarsProps) { imageStyles={styles.avatarLarge} size={CONST.AVATAR_SIZE.LARGE} name={icons[0].name} - iconID={icons[0].id} + accountID={icons[0].id} type={icons[0].type} fallbackIcon={icons[0].fallbackIcon} /> @@ -83,7 +83,7 @@ function RoomHeaderAvatars({icons, reportID}: RoomHeaderAvatarsProps) { size={CONST.AVATAR_SIZE.LARGE} containerStyles={[...iconStyle, StyleUtils.getAvatarBorderRadius(CONST.AVATAR_SIZE.LARGE_BORDERED, icon.type)]} name={icon.name} - iconID={icon.id} + accountID={icon.id} type={icon.type} fallbackIcon={icon.fallbackIcon} /> diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index d7ddb9ddf9b0..cc36657826f6 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -82,7 +82,7 @@ function SubscriptAvatar({ source={mainAvatar?.source} size={size} name={mainAvatar?.name} - iconID={mainAvatar?.id} + accountID={mainAvatar?.id} type={mainAvatar?.type} fallbackIcon={mainAvatar?.fallbackIcon} /> @@ -109,7 +109,7 @@ function SubscriptAvatar({ size={isSmall ? CONST.AVATAR_SIZE.SMALL_SUBSCRIPT : CONST.AVATAR_SIZE.SUBSCRIPT} fill={secondaryAvatar.fill} name={secondaryAvatar.name} - iconID={secondaryAvatar.id} + accountID={secondaryAvatar.id} type={secondaryAvatar.type} fallbackIcon={secondaryAvatar.fallbackIcon} /> diff --git a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx index fa301167a230..a530bcfddc46 100644 --- a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx +++ b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx @@ -59,7 +59,7 @@ function BaseUserDetailsTooltip({accountID, fallbackUserDetails, icon, delegateA type={icon?.type ?? CONST.ICON_TYPE_AVATAR} name={icon?.name ?? userLogin} fallbackIcon={icon?.fallbackIcon} - iconID={icon?.id} + accountID={icon?.id} /> {title} diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 59c5b0e240ad..ce8d116a0b2d 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -200,7 +200,7 @@ function ReportActionItemSingle({ source={icon.source} type={icon.type} name={icon.name} - iconID={icon.id} + accountID={icon.id} fallbackIcon={fallbackIcon} /> diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index c328bea9bb96..d872cb0275f1 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -83,7 +83,7 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi fallbackIcon={Expensicons.FallbackWorkspaceAvatar} size={CONST.AVATAR_SIZE.XLARGE} name={policyName} - iconID={policy?.id ?? ''} + accountID={policy?.id ?? ''} type={CONST.ICON_TYPE_WORKSPACE} /> ), diff --git a/src/pages/workspace/WorkspacesListRow.tsx b/src/pages/workspace/WorkspacesListRow.tsx index 21c14ff8cda4..c9473914eaae 100644 --- a/src/pages/workspace/WorkspacesListRow.tsx +++ b/src/pages/workspace/WorkspacesListRow.tsx @@ -151,7 +151,7 @@ function WorkspacesListRow({ source={workspaceIcon} fallbackIcon={fallbackWorkspaceIcon} name={title} - iconID={policyID} + accountID={policyID} type={CONST.ICON_TYPE_WORKSPACE} /> Date: Wed, 17 Apr 2024 13:00:27 +0700 Subject: [PATCH 147/661] safely get personal detail --- src/components/OptionListContextProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx index 94fe51711e02..5d4e704262f5 100644 --- a/src/components/OptionListContextProvider.tsx +++ b/src/components/OptionListContextProvider.tsx @@ -121,8 +121,8 @@ function OptionsListContextProvider({reports, children}: OptionsListProviderProp }> = []; Object.keys(personalDetails).forEach((accoutID) => { - const prevPersonalDetail = prevPersonalDetails.current[accoutID]; - const personalDetail = personalDetails[accoutID]; + const prevPersonalDetail = prevPersonalDetails.current?.[accoutID]; + const personalDetail = personalDetails?.[accoutID]; if (isEqualPersonalDetail(prevPersonalDetail, personalDetail)) { return; From 1fecd977f9b534f119955a62bd4c7ecf31135bd2 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 17 Apr 2024 13:07:31 +0700 Subject: [PATCH 148/661] update to use usePrevious hook --- src/components/OptionListContextProvider.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx index 5d4e704262f5..473c0f5bb083 100644 --- a/src/components/OptionListContextProvider.tsx +++ b/src/components/OptionListContextProvider.tsx @@ -51,7 +51,7 @@ function OptionsListContextProvider({reports, children}: OptionsListProviderProp }); const personalDetails = usePersonalDetails(); - const prevPersonalDetails = useRef(personalDetails); + const prevPersonalDetails = usePrevious(personalDetails); const prevReports = usePrevious(reports); /** @@ -121,7 +121,7 @@ function OptionsListContextProvider({reports, children}: OptionsListProviderProp }> = []; Object.keys(personalDetails).forEach((accoutID) => { - const prevPersonalDetail = prevPersonalDetails.current?.[accoutID]; + const prevPersonalDetail = prevPersonalDetails?.[accoutID]; const personalDetail = personalDetails?.[accoutID]; if (isEqualPersonalDetail(prevPersonalDetail, personalDetail)) { @@ -153,7 +153,6 @@ function OptionsListContextProvider({reports, children}: OptionsListProviderProp return newOptions; }); - prevPersonalDetails.current = personalDetails; // eslint-disable-next-line react-hooks/exhaustive-deps }, [personalDetails]); From 4d872eb62691bc0f30b5ec0f9605e9a2926691b1 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 10:10:13 +0200 Subject: [PATCH 149/661] Minor improvements --- src/libs/PolicyUtils.ts | 2 +- src/libs/actions/IOU.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 35abc7258d45..48e0032da8cd 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -11,8 +11,8 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import getPolicyIDFromState from './Navigation/getPolicyIDFromState'; import Navigation, {navigationRef} from './Navigation/Navigation'; import type {RootStackParamList, State} from './Navigation/types'; -import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; import * as NetworkStore from './Network/NetworkStore'; +import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; type MemberEmailsToAccountIDs = Record; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index ab39072a56b8..9916afbf3a96 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -856,7 +856,7 @@ function buildOnyxDataForInvoice( optimisticPolicyRecentlyUsedTags: OnyxTypes.RecentlyUsedTags, isNewChatReport: boolean, transactionThreadReport: OptimisticChatReport, - transactionThreadCreatedReportAction: OptimisticCreatedReportAction, + transactionThreadCreatedReportAction: OptimisticCreatedReportAction | EmptyObject, inviteReportAction?: OptimisticInviteReportAction, policy?: OnyxEntry, policyTagList?: OnyxEntry, From a9d6b4bd5ad26334c9645abe424f70ed7b94e33e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 17 Apr 2024 16:35:14 +0700 Subject: [PATCH 150/661] fix add custom icon right button --- src/components/Button/index.tsx | 20 ++++++++++++------- .../ButtonWithDropdownMenu/index.tsx | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 68f1aac41a5b..25a9fcbc215a 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -115,6 +115,9 @@ type ButtonProps = Partial & { /** Boolean whether to display the right icon */ shouldShowRightIcon?: boolean; + + /** The custom icon to display to the right of the text */ + customRightIcon?: React.ReactNode; }; type KeyboardShortcutComponentProps = Pick; @@ -198,6 +201,7 @@ function Button( id = '', accessibilityLabel = '', + customRightIcon, ...rest }: ButtonProps, ref: ForwardedRef, @@ -253,13 +257,15 @@ function Button( {shouldShowRightIcon && ( - + {customRightIcon ?? ( + + )} )} diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx index 9c2ab4b36734..16c9804f79d0 100644 --- a/src/components/ButtonWithDropdownMenu/index.tsx +++ b/src/components/ButtonWithDropdownMenu/index.tsx @@ -98,7 +98,7 @@ function ButtonWithDropdownMenu({ medium={!isButtonSizeLarge} innerStyles={[innerStyleDropButton, !isSplit && styles.dropDownButtonCartIconView]} enterKeyEventListenerPriority={enterKeyEventListenerPriority} - iconRight={getIconRightButton} + customRightIcon={getIconRightButton()} shouldShowRightIcon={!isSplit} /> From 75f8b2386c9056ec06bd781bb00d61b3afa4ffab Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 11:58:26 +0200 Subject: [PATCH 151/661] Update optimistic invoice room creation to include current user as a member --- src/libs/actions/IOU.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9916afbf3a96..130a43a79319 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1619,6 +1619,7 @@ function getDeleteTrackExpenseInformation( /** Gathers all the data needed to create an invoice. */ function getSendInvoiceInformation( transaction: OnyxEntry, + currentUserAccountID: number, invoiceChatReport?: OnyxEntry, receipt?: Receipt, policy?: OnyxEntry, @@ -1641,7 +1642,7 @@ function getSendInvoiceInformation( if (!chatReport) { isNewChatReport = true; - chatReport = ReportUtils.buildOptimisticChatReport([receiverAccountID], CONST.REPORT.DEFAULT_REPORT_NAME, CONST.REPORT.CHAT_TYPE.INVOICE, senderWorkspaceID); + chatReport = ReportUtils.buildOptimisticChatReport([receiverAccountID, currentUserAccountID], CONST.REPORT.DEFAULT_REPORT_NAME, CONST.REPORT.CHAT_TYPE.INVOICE, senderWorkspaceID); } // STEP 3: Create a new optimistic invoice report. @@ -3341,7 +3342,7 @@ function sendInvoice( optimisticTransactionID, optimisticTransactionThreadReportID, onyxData, - } = getSendInvoiceInformation(transaction, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories); + } = getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories); let parameters: SendInvoiceParams = { senderWorkspaceID, From b8e9d4368a6d2e90e3114cd8ea291d469a11330f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 12:31:31 +0200 Subject: [PATCH 152/661] feat: add proper copies --- src/languages/en.ts | 6 ++++++ src/languages/es.ts | 6 ++++++ .../EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx | 2 +- src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx | 2 +- .../PersonalInfo/substeps/SocialSecurityNumber.tsx | 4 ++-- .../utils/getInitialSubstepForPersonalInfo.ts | 4 ++-- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 495ec8426ea8..bc4192735cea 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1702,6 +1702,12 @@ export default { address: 'Address', letsDoubleCheck: "Let's double check that everything looks right.", byAddingThisBankAccount: 'By adding this bank account, you confirm that you have read, understand and accept', + whatsYourLegalName: 'What’s your legal name?', + whatsYourDOB: 'What’s your date of birth?', + whatsYourAddress: 'What’s your address?', + noPOBoxesPlease: 'No PO boxes or mail-drop addresses, please.', + whatsYourSSN: 'What are the last four digits of your Social Security Number?', + noPersonalChecks: 'Don’t worry, no personal credit checks here!', whatsYourPhoneNumber: 'What’s your phone number?', weNeedThisToVerify: 'We need this to verify your wallet.', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 74076c22393f..37c51d2148aa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1727,6 +1727,12 @@ export default { address: 'Dirección', letsDoubleCheck: 'Revisemos que todo esté bien', byAddingThisBankAccount: 'Añadiendo esta cuenta bancaria, confirmas que has leído, entendido y aceptado', + whatsYourLegalName: '¿Cuál es tu nombre legal?', + whatsYourDOB: '¿Cuál es tu fecha de nacimiento?', + whatsYourAddress: '¿Cuál es tu dirección?', + noPOBoxesPlease: 'Nada de apartados de correos ni direcciones de envío, por favor.', + whatsYourSSN: '¿Cuáles son los últimos 4 dígitos de tu número de la seguridad social?', + noPersonalChecks: 'No te preocupes, no hacemos verificaciones de crédito personales.', whatsYourPhoneNumber: '¿Cuál es tu número de teléfono?', weNeedThisToVerify: 'Necesitamos esto para verificar tu billetera.', }, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx index a42b9c76d1a9..f83edb706686 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx @@ -67,7 +67,7 @@ function DateOfBirth({walletAdditionalDetails, onNext, isEditing}: DateOfBirthPr style={[styles.mh5, styles.flexGrow2, styles.justifyContentBetween]} submitButtonStyles={[styles.pb5, styles.mb0]} > - {translate('personalInfoStep.enterYourDateOfBirth')} + {translate('personalInfoStep.whatsYourDOB')} - {translate('personalInfoStep.enterYourLegalFirstAndLast')} + {translate('personalInfoStep.whatsYourLegalName')} - {translate('personalInfoStep.enterTheLast4')} - {translate('personalInfoStep.dontWorry')} + {translate('personalInfoStep.whatsYourSSN')} + {translate('personalInfoStep.noPersonalChecks')} Date: Wed, 17 Apr 2024 13:18:35 +0200 Subject: [PATCH 153/661] refactor: make useStepSubmit generic --- .../useReimbursementAccountStepFormSubmit.ts | 39 +++++------------- src/hooks/useStepFormSubmit.ts | 40 +++++++++++++++++++ ...seWalletAdditionalDetailsStepFormSubmit.ts | 27 +++++++++++++ .../PersonalInfo/substeps/Address.tsx | 5 +-- .../PersonalInfo/substeps/DateOfBirth.tsx | 5 +-- .../PersonalInfo/substeps/FullName.tsx | 5 +-- .../PersonalInfo/substeps/PhoneNumber.tsx | 5 +-- .../substeps/SocialSecurityNumber.tsx | 5 +-- 8 files changed, 87 insertions(+), 44 deletions(-) create mode 100644 src/hooks/useStepFormSubmit.ts create mode 100644 src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts diff --git a/src/hooks/useReimbursementAccountStepFormSubmit.ts b/src/hooks/useReimbursementAccountStepFormSubmit.ts index 85a9b21b4d8d..bd17bfaa1234 100644 --- a/src/hooks/useReimbursementAccountStepFormSubmit.ts +++ b/src/hooks/useReimbursementAccountStepFormSubmit.ts @@ -1,46 +1,27 @@ -import {useCallback} from 'react'; -import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; -import * as FormActions from '@userActions/FormActions'; +import type {FormOnyxKeys} from '@components/Form/types'; +import useStepFormSubmit from '@hooks/useStepFormSubmit'; import type {OnyxFormKey} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SubStepProps} from './useSubStep/types'; type UseReimbursementAccountStepFormSubmitParams = Pick & { formId?: OnyxFormKey; - fieldIds: Array>; + fieldIds: Array>; shouldSaveDraft: boolean; }; /** * Hook for handling submit method in ReimbursementAccount substeps. * When user is in editing mode we should save values only when user confirm that - * @param formId - ID for particular form * @param onNext - callback * @param fieldIds - field IDs for particular step * @param shouldSaveDraft - if we should save draft values */ -export default function useReimbursementAccountStepFormSubmit({ - formId = ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, - onNext, - fieldIds, - shouldSaveDraft, -}: UseReimbursementAccountStepFormSubmitParams) { - return useCallback( - (values: FormOnyxValues) => { - if (shouldSaveDraft) { - const stepValues = fieldIds.reduce( - (acc, key) => ({ - ...acc, - [key]: values[key], - }), - {}, - ); - - FormActions.setDraftValues(formId, stepValues); - } - - onNext(); - }, - [onNext, formId, fieldIds, shouldSaveDraft], - ); +export default function useReimbursementAccountStepFormSubmit({onNext, fieldIds, shouldSaveDraft}: UseReimbursementAccountStepFormSubmitParams) { + return useStepFormSubmit({ + formId: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, + onNext, + fieldIds, + shouldSaveDraft, + }); } diff --git a/src/hooks/useStepFormSubmit.ts b/src/hooks/useStepFormSubmit.ts new file mode 100644 index 000000000000..86d754bf2fc9 --- /dev/null +++ b/src/hooks/useStepFormSubmit.ts @@ -0,0 +1,40 @@ +import {useCallback} from 'react'; +import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import * as FormActions from '@userActions/FormActions'; +import type {OnyxFormKey, OnyxFormValuesMapping} from '@src/ONYXKEYS'; +import type {SubStepProps} from './useSubStep/types'; + +type UseStepFormSubmitParams = Pick & { + formId: OnyxFormKey; + fieldIds: Array>; + shouldSaveDraft: boolean; +}; + +/** + * Hook for handling submit method in substeps. + * When user is in editing mode we should save values only when user confirm that + * @param formId - ID for particular form + * @param onNext - callback + * @param fieldIds - field IDs for particular step + * @param shouldSaveDraft - if we should save draft values + */ +export default function useStepFormSubmit({formId, onNext, fieldIds, shouldSaveDraft}: UseStepFormSubmitParams) { + return useCallback( + (values: FormOnyxValues) => { + if (shouldSaveDraft) { + const stepValues = fieldIds.reduce( + (acc, key) => ({ + ...acc, + [key]: values[key], + }), + {}, + ); + + FormActions.setDraftValues(formId, stepValues); + } + + onNext(); + }, + [onNext, formId, fieldIds, shouldSaveDraft], + ); +} diff --git a/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts b/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts new file mode 100644 index 000000000000..2ca079a506cc --- /dev/null +++ b/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts @@ -0,0 +1,27 @@ +import type {FormOnyxKeys} from '@components/Form/types'; +import useStepFormSubmit from '@hooks/useStepFormSubmit'; +import type {OnyxFormKey} from '@src/ONYXKEYS'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {SubStepProps} from './useSubStep/types'; + +type UseWalletAdditionalDetailsStepFormSubmitParams = Pick & { + formId?: OnyxFormKey; + fieldIds: Array>; + shouldSaveDraft: boolean; +}; + +/** + * Hook for handling submit method in WalletAdditionalDetails substeps. + * When user is in editing mode we should save values only when user confirm that + * @param onNext - callback + * @param fieldIds - field IDs for particular step + * @param shouldSaveDraft - if we should save draft values + */ +export default function useWalletAdditionalDetailsStepFormSubmit({onNext, fieldIds, shouldSaveDraft}: UseWalletAdditionalDetailsStepFormSubmitParams) { + return useStepFormSubmit({ + formId: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, + onNext, + fieldIds, + shouldSaveDraft, + }); +} diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx index 445cf11282b1..d63dd7c08f01 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx @@ -6,9 +6,9 @@ import FormProvider from '@components/Form/FormProvider'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; -import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; import AddressFormFields from '@pages/ReimbursementAccount/AddressFormFields'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; @@ -59,8 +59,7 @@ function Address({walletAdditionalDetails, onNext, isEditing}: AddressProps) { zipCode: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.ZIP_CODE] ?? '', }; - const handleSubmit = useReimbursementAccountStepFormSubmit({ - formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx index f83edb706686..42cfe4ca11de 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx @@ -8,9 +8,9 @@ import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; -import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; @@ -51,8 +51,7 @@ function DateOfBirth({walletAdditionalDetails, onNext, isEditing}: DateOfBirthPr const dobDefaultValue = walletAdditionalDetails?.[PERSONAL_INFO_DOB_KEY] ?? walletAdditionalDetails?.[PERSONAL_INFO_DOB_KEY] ?? ''; - const handleSubmit = useReimbursementAccountStepFormSubmit({ - formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx index 32bd1537ce3b..6dbe855b2721 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx @@ -8,9 +8,9 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; -import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; @@ -49,8 +49,7 @@ function FullName({walletAdditionalDetails, onNext, isEditing}: FullNameProps) { lastName: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.LAST_NAME] ?? '', }; - const handleSubmit = useReimbursementAccountStepFormSubmit({ - formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx index b465efe6fe79..9b5f8cf0a611 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx @@ -8,9 +8,9 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; -import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; @@ -42,8 +42,7 @@ function PhoneNumber({walletAdditionalDetails, onNext, isEditing}: PhoneNumberPr const defaultPhoneNumber = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.PHONE_NUMBER] ?? ''; - const handleSubmit = useReimbursementAccountStepFormSubmit({ - formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx index fe23f4b9cbea..de9c9ce25938 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx @@ -8,9 +8,9 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; -import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; @@ -43,8 +43,7 @@ function SocialSecurityNumber({walletAdditionalDetails, onNext, isEditing}: Soci const defaultSsnLast4 = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.SSN_LAST_4] ?? ''; - const handleSubmit = useReimbursementAccountStepFormSubmit({ - formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, From 74260e0750b612f6786a5ce585a5630b92fd76ea Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Wed, 17 Apr 2024 13:31:19 +0200 Subject: [PATCH 154/661] make connect to integrations work --- src/CONFIG.ts | 3 ++- src/libs/ApiUtils.ts | 6 ++++-- src/libs/actions/connections/ConnectToXero.ts | 2 +- .../actions/connections/getQuickBooksOnlineSetupLink.ts | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/CONFIG.ts b/src/CONFIG.ts index 76ea18d37d5f..9ed4242d7604 100644 --- a/src/CONFIG.ts +++ b/src/CONFIG.ts @@ -48,6 +48,7 @@ export default { EXPENSIFY: { // Note: This will be EXACTLY what is set for EXPENSIFY_URL whether the proxy is enabled or not. EXPENSIFY_URL: expensifyURL, + SECURE_EXPENSIFY_URL: secureExpensifyUrl, NEW_EXPENSIFY_URL: newExpensifyURL, // The DEFAULT API is the API used by most environments, except staging, where we use STAGING (defined below) @@ -72,7 +73,7 @@ export default { IS_USING_LOCAL_WEB: useNgrok || expensifyURLRoot.includes('dev'), PUSHER: { APP_KEY: get(Config, 'PUSHER_APP_KEY', '268df511a204fbb60884'), - SUFFIX: get(Config, 'PUSHER_DEV_SUFFIX', ''), + SUFFIX: ENVIRONMENT === CONST.ENVIRONMENT.DEV ? get(Config, 'PUSHER_DEV_SUFFIX', '') : '', CLUSTER: 'mt1', }, SITE_TITLE: 'New Expensify', diff --git a/src/libs/ApiUtils.ts b/src/libs/ApiUtils.ts index 0c8fa3f53915..1da9795f333b 100644 --- a/src/libs/ApiUtils.ts +++ b/src/libs/ApiUtils.ts @@ -38,12 +38,14 @@ function getApiRoot(request?: Request): string { const shouldUseSecure = request?.shouldUseSecure ?? false; if (shouldUseStagingServer) { - if (CONFIG.IS_USING_WEB_PROXY) { + if (CONFIG.IS_USING_WEB_PROXY && !request?.shouldSkipWebProxy) { return shouldUseSecure ? proxyConfig.STAGING_SECURE : proxyConfig.STAGING; } return shouldUseSecure ? CONFIG.EXPENSIFY.STAGING_SECURE_API_ROOT : CONFIG.EXPENSIFY.STAGING_API_ROOT; } - + if (request?.shouldSkipWebProxy) { + return shouldUseSecure ? CONFIG.EXPENSIFY.SECURE_EXPENSIFY_URL : CONFIG.EXPENSIFY.EXPENSIFY_URL; + } return shouldUseSecure ? CONFIG.EXPENSIFY.DEFAULT_SECURE_API_ROOT : CONFIG.EXPENSIFY.DEFAULT_API_ROOT; } diff --git a/src/libs/actions/connections/ConnectToXero.ts b/src/libs/actions/connections/ConnectToXero.ts index 0bd3d61a6c6e..abe96603fd85 100644 --- a/src/libs/actions/connections/ConnectToXero.ts +++ b/src/libs/actions/connections/ConnectToXero.ts @@ -2,7 +2,7 @@ import {getCommandURL} from '@libs/ApiUtils'; const getXeroSetupLink = (policyID: string) => { const params = new URLSearchParams({policyID}); - const commandURL = getCommandURL({command: 'ConnectPolicyToXero'}); + const commandURL = getCommandURL({command: 'ConnectPolicyToXero', shouldSkipWebProxy: true}); return commandURL + params.toString(); }; diff --git a/src/libs/actions/connections/getQuickBooksOnlineSetupLink.ts b/src/libs/actions/connections/getQuickBooksOnlineSetupLink.ts index b4317025b057..56b8309f7134 100644 --- a/src/libs/actions/connections/getQuickBooksOnlineSetupLink.ts +++ b/src/libs/actions/connections/getQuickBooksOnlineSetupLink.ts @@ -2,7 +2,7 @@ import {getCommandURL} from '@libs/ApiUtils'; function getQuickBooksOnlineSetupLink(policyID: string) { const params = new URLSearchParams({policyID}); - const commandURL = getCommandURL({command: 'ConnectPolicyToQuickbooksOnline'}); + const commandURL = getCommandURL({command: 'ConnectPolicyToQuickbooksOnline', shouldSkipWebProxy: true}); return commandURL + params.toString(); } From ed736323a070ed66e4e9f8208e56f1c779b60271 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Wed, 17 Apr 2024 13:47:52 +0200 Subject: [PATCH 155/661] fix connections empty object bug --- src/pages/workspace/accounting/WorkspaceAccountingPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx b/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx index ad28cac7032c..c932b38f7642 100644 --- a/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx +++ b/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx @@ -30,6 +30,7 @@ import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyConnectionSyncProgress} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ConnectToQuickbooksOnlineButton from './qboConnectionButton'; type WorkspaceAccountingPageOnyxProps = { @@ -44,6 +45,8 @@ type WorkspaceAccountingPageProps = WithPolicyAndFullscreenLoadingProps & policy: OnyxEntry; }; +// const AccountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME); + function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAccountingPageProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -80,7 +83,7 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco ); const connectionsMenuItems: MenuItemProps[] = useMemo(() => { - if (!policyConnectedToQbo && !policyConnectedToXero && !isSyncInProgress) { + if (isEmptyObject(policy?.connections) && !isSyncInProgress) { return [ { icon: Expensicons.QBOSquare, From 6db22e200e3c0ca660000d34303c52fa6c04acf0 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 14:26:15 +0200 Subject: [PATCH 156/661] Add INVOICE_RECEIVER_TYPE to consts --- src/CONST.ts | 9 +++++++++ src/types/onyx/Report.ts | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index fa66b916ab7c..08ba93053685 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -839,6 +839,10 @@ const CONST = { OWNER_EMAIL_FAKE: '__FAKE__', OWNER_ACCOUNT_ID_FAKE: 0, DEFAULT_REPORT_NAME: 'Chat Report', + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + BUSINESS: 'policy', + }, }, NEXT_STEP: { FINISHED: 'Finished!', @@ -4353,6 +4357,11 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, + + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + POLICY: 'policy', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 344b7df5b2eb..51e3ce5fb37d 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -30,11 +30,11 @@ type Participant = { type InvoiceReceiver = | { - type: 'individual'; + type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; accountID: number; } | { - type: 'policy'; + type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS; policyID: string; }; From 173432f67cb35058def61c27c6ab78d90b6087d2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 14:37:20 +0200 Subject: [PATCH 157/661] Code improvements --- src/CONST.ts | 5 -- ...raryForRefactorRequestConfirmationList.tsx | 5 +- src/libs/actions/IOU.ts | 67 +++++++++---------- 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 08ba93053685..6467769263dd 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4357,11 +4357,6 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, - - INVOICE_RECEIVER_TYPE: { - INDIVIDUAL: 'individual', - POLICY: 'policy', - }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index e41c6cc96085..7b444be18da9 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -256,10 +256,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${senderWorkspaceParticipant?.policyID}`]; }, [allPolicies, pickedParticipants]); - const canUpdateSenderWorkspace = useMemo( - () => PolicyUtils.getActiveAdminWorkspaces(allPolicies).length > 0 && !!transaction?.isFromGlobalCreate, - [allPolicies, transaction?.isFromGlobalCreate], - ); + const canUpdateSenderWorkspace = useMemo(() => PolicyUtils.canSendInvoice(allPolicies) && !!transaction?.isFromGlobalCreate, [allPolicies, transaction?.isFromGlobalCreate]); // A flag for showing the tags field const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 130a43a79319..d6be557b8107 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -863,24 +863,7 @@ function buildOnyxDataForInvoice( policyCategories?: OnyxEntry, ): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] { const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); - const optimisticData: OnyxUpdate[] = []; - - if (chatReport) { - optimisticData.push({ - // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page - onyxMethod: isNewChatReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - ...chatReport, - lastReadTime: DateUtils.getDBTime(), - lastMessageTranslationKey: '', - iouReportID: iouReport.reportID, - ...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), - }, - }); - } - - optimisticData.push( + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, @@ -950,7 +933,22 @@ function buildOnyxDataForInvoice( key: `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`, value: null, }, - ); + ]; + + if (chatReport) { + optimisticData.push({ + // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page + onyxMethod: isNewChatReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + ...chatReport, + lastReadTime: DateUtils.getDBTime(), + lastMessageTranslationKey: '', + iouReportID: iouReport.reportID, + ...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), + }, + }); + } if (optimisticPolicyRecentlyUsedCategories.length) { optimisticData.push({ @@ -968,21 +966,7 @@ function buildOnyxDataForInvoice( }); } - const successData: OnyxUpdate[] = []; - - if (isNewChatReport) { - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, - value: { - pendingFields: null, - errorFields: null, - isOptimisticReport: false, - }, - }); - } - - successData.push( + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, @@ -1007,7 +991,6 @@ function buildOnyxDataForInvoice( pendingFields: clearedPendingFields, }, }, - { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, @@ -1049,7 +1032,19 @@ function buildOnyxDataForInvoice( }, }, }, - ); + ]; + + if (isNewChatReport) { + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, + value: { + pendingFields: null, + errorFields: null, + isOptimisticReport: false, + }, + }); + } const errorKey = DateUtils.getMicroseconds(); From b8a7641fb717f9210082575e24475fa8e09d9db7 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Wed, 17 Apr 2024 15:17:05 +0200 Subject: [PATCH 158/661] make accounting integrations more generative --- .../workspace/WorkspaceMoreFeaturesPage.tsx | 2 +- .../accounting/WorkspaceAccountingPage.tsx | 158 +++++++++--------- src/types/onyx/Request.ts | 1 + 3 files changed, 83 insertions(+), 78 deletions(-) diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index f37921f07c36..d496d3872683 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -48,7 +48,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const {translate} = useLocalize(); const {canUseAccountingIntegrations} = usePermissions(); const hasAccountingConnection = !!policy?.areConnectionsEnabled && !!policy?.connections; - const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline.config.syncTax; + const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline?.config.syncTax; const spendItems: Item[] = [ { diff --git a/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx b/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx index c932b38f7642..c269db9f667b 100644 --- a/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx +++ b/src/pages/workspace/accounting/WorkspaceAccountingPage.tsx @@ -30,6 +30,7 @@ import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyConnectionSyncProgress} from '@src/types/onyx'; +import type {ConnectionName} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ConnectToQuickbooksOnlineButton from './qboConnectionButton'; @@ -45,7 +46,47 @@ type WorkspaceAccountingPageProps = WithPolicyAndFullscreenLoadingProps & policy: OnyxEntry; }; -// const AccountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME); +const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME); + +function connectToAccountingIntegrationButton(connectionName: ConnectionName, policyID: string, environmentURL: string) { + // eslint-disable-next-line default-case + switch (connectionName) { + case 'quickbooksOnline': + return ( + + ); + case 'xero': + return ( + + ); + } +} + +function accountingIntegrationIcon(connectionName: ConnectionName) { + // eslint-disable-next-line default-case + switch (connectionName) { + case 'quickbooksOnline': + return Expensicons.QBOSquare; + case 'xero': + return Expensicons.XeroSquare; + } +} + +function accountingIntegrationTitleKey(connectionName: ConnectionName) { + // eslint-disable-next-line default-case + switch (connectionName) { + case 'quickbooksOnline': + return 'workspace.accounting.qbo'; + case 'xero': + return 'workspace.accounting.xero'; + } +} function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAccountingPageProps) { const theme = useTheme(); @@ -60,10 +101,7 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco const threeDotsMenuContainerRef = useRef(null); const isSyncInProgress = !!connectionSyncProgress?.stageInProgress && connectionSyncProgress.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE; - const qboSyncInProgress = isSyncInProgress && connectionSyncProgress.connectionName === 'quickbooksOnline'; - const xeroSyncInProgress = isSyncInProgress && connectionSyncProgress.connectionName === 'xero'; - const policyConnectedToQbo = !!policy?.connections?.quickbooksOnline; - const policyConnectedToXero = !!policy?.connections?.xero; + const connectedIntegration = accountingIntegrations.find((integration) => !!policy?.connections?.[integration]); const policyID = policy?.id ?? ''; const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo( @@ -84,47 +122,26 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco const connectionsMenuItems: MenuItemProps[] = useMemo(() => { if (isEmptyObject(policy?.connections) && !isSyncInProgress) { - return [ - { - icon: Expensicons.QBOSquare, - iconType: 'avatar', - interactive: false, - wrapperStyle: [styles.sectionMenuItemTopDescription], - shouldShowRightComponent: true, - title: translate('workspace.accounting.qbo'), - rightComponent: ( - - ), - }, - { - icon: Expensicons.XeroSquare, - iconType: 'avatar', - interactive: false, - wrapperStyle: [styles.sectionMenuItemTopDescription], - shouldShowRightComponent: true, - title: translate('workspace.accounting.xero'), - rightComponent: ( - - ), - }, - ]; + return accountingIntegrations.map((integration) => ({ + icon: accountingIntegrationIcon(integration), + iconType: 'avatar', + interactive: false, + wrapperStyle: [styles.sectionMenuItemTopDescription], + shouldShowRightComponent: true, + title: translate(accountingIntegrationTitleKey(integration)), + rightComponent: connectToAccountingIntegrationButton(integration, policyID, environmentURL), + })); } return [ { - icon: qboSyncInProgress || policyConnectedToQbo ? Expensicons.QBOSquare : Expensicons.XeroSquare, + icon: accountingIntegrationIcon(connectedIntegration ?? connectionSyncProgress?.connectionName), iconType: 'avatar', interactive: false, wrapperStyle: [styles.sectionMenuItemTopDescription], shouldShowRightComponent: true, - title: qboSyncInProgress || policyConnectedToQbo ? translate('workspace.accounting.qbo') : translate('workspace.accounting.xero'), - description: qboSyncInProgress + title: translate(accountingIntegrationTitleKey(connectedIntegration ?? connectionSyncProgress?.connectionName)), + description: isSyncInProgress ? translate('workspace.accounting.connections.syncStageName', connectionSyncProgress.stageInProgress) : translate('workspace.accounting.lastSync'), rightComponent: isSyncInProgress ? ( @@ -150,7 +167,7 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco ), }, - ...(policyConnectedToQbo || policyConnectedToXero + ...(connectedIntegration ? [ { icon: Expensicons.Pencil, @@ -180,14 +197,14 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco : []), ]; }, [ + connectedIntegration, + connectionSyncProgress?.connectionName, connectionSyncProgress?.stageInProgress, environmentURL, isSyncInProgress, overflowMenu, - policyConnectedToQbo, - policyConnectedToXero, + policy?.connections, policyID, - qboSyncInProgress, styles.popoverMenuIcon, styles.sectionMenuItemTopDescription, theme.spinner, @@ -195,32 +212,17 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco translate, ]); - const otherIntegrationsItem = useMemo(() => { - if (qboSyncInProgress || policyConnectedToQbo) { - return { - icon: Expensicons.XeroSquare, - title: translate('workspace.accounting.xero'), - rightComponent: ( - - ), - }; - } - if (xeroSyncInProgress || policyConnectedToXero) { - return { - icon: Expensicons.QBOSquare, - title: translate('workspace.accounting.qbo'), - rightComponent: ( - - ), - }; + const otherIntegrationsItems = useMemo(() => { + if (isEmptyObject(policy?.connections) && !isSyncInProgress) { + return; } - }, [environmentURL, policyConnectedToQbo, policyConnectedToXero, policyID, qboSyncInProgress, translate, xeroSyncInProgress]); + const otherIntegrations = accountingIntegrations.filter((integration) => integration !== connectionSyncProgress?.connectionName && integration !== connectedIntegration); + return otherIntegrations.map((integration) => ({ + icon: Expensicons.XeroSquare, + title: translate(accountingIntegrationTitleKey(integration)), + rightComponent: connectToAccountingIntegrationButton(integration, policyID, environmentURL), + })); + }, [connectedIntegration, connectionSyncProgress?.connectionName, environmentURL, isSyncInProgress, policy?.connections, policyID, translate]); const headerThreeDotsMenuItems: ThreeDotsMenuProps['menuItems'] = [ { @@ -271,21 +273,23 @@ function WorkspaceAccountingPage({policy, connectionSyncProgress}: WorkspaceAcco menuItems={connectionsMenuItems} shouldUseSingleExecution /> - {otherIntegrationsItem && ( + {otherIntegrationsItems && ( - + {otherIntegrationsItems.map((integration) => ( + + ))} )} diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index f6e82d75db70..233519f010fc 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -21,6 +21,7 @@ type RequestData = { finallyData?: OnyxUpdate[]; resolve?: (value: Response) => void; reject?: (value?: unknown) => void; + shouldSkipWebProxy?: boolean; }; type Request = RequestData & OnyxData; From c711542ba80bfc705bad89ade38209adc5a2b135 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 17 Apr 2024 21:58:33 +0800 Subject: [PATCH 159/661] correctly access the report data --- src/libs/TaskUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TaskUtils.ts b/src/libs/TaskUtils.ts index 19e1025a09c8..188b8f5b31ae 100644 --- a/src/libs/TaskUtils.ts +++ b/src/libs/TaskUtils.ts @@ -38,7 +38,7 @@ function getTaskReportActionMessage(action: OnyxEntry): Pick Date: Wed, 17 Apr 2024 16:01:32 +0200 Subject: [PATCH 160/661] Change caret position styles --- src/components/Composer/index.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index a8737fdac47a..59f7333433bb 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -289,13 +289,7 @@ function Composer( opacity: 0, }} > - + {`${valueBeforeCaret} `} Date: Wed, 17 Apr 2024 17:40:55 +0200 Subject: [PATCH 161/661] refactor: finishing touches --- ...seWalletAdditionalDetailsStepFormSubmit.ts | 2 +- src/libs/actions/Wallet.ts | 5 ++ .../PersonalInfo/PersonalInfo.tsx | 88 ++++++++----------- .../PersonalInfo/substeps/Address.tsx | 8 +- .../PersonalInfo/substeps/Confirmation.tsx | 13 +-- .../PersonalInfo/substeps/DateOfBirth.tsx | 8 +- .../PersonalInfo/substeps/FullName.tsx | 16 ++-- .../PersonalInfo/substeps/PhoneNumber.tsx | 8 +- .../substeps/SocialSecurityNumber.tsx | 12 +-- .../utils/getInitialSubstepForPersonalInfo.ts | 4 +- .../EnablePayments/utils/getSubstepValues.ts | 17 ++-- src/types/form/PersonalBankAccountForm.ts | 29 +----- src/types/form/WalletAdditionalDetailsForm.ts | 36 ++++++++ src/types/form/index.ts | 1 + src/types/onyx/WalletAdditionalDetails.ts | 26 +++--- 15 files changed, 142 insertions(+), 131 deletions(-) create mode 100644 src/types/form/WalletAdditionalDetailsForm.ts diff --git a/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts b/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts index 2ca079a506cc..24fcc88b505c 100644 --- a/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts +++ b/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts @@ -19,7 +19,7 @@ type UseWalletAdditionalDetailsStepFormSubmitParams = Pick({ - formId: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, + formId: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, onNext, fieldIds, shouldSaveDraft, diff --git a/src/libs/actions/Wallet.ts b/src/libs/actions/Wallet.ts index 097d9ee0419a..045cc34f39ef 100644 --- a/src/libs/actions/Wallet.ts +++ b/src/libs/actions/Wallet.ts @@ -295,6 +295,10 @@ function requestPhysicalExpensifyCard(cardID: number, authToken: string, private API.write(WRITE_COMMANDS.REQUEST_PHYSICAL_EXPENSIFY_CARD, requestParams, {optimisticData}); } +function resetWalletAdditionalDetailsDraft() { + Onyx.set(ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_DRAFT, null); +} + export { openOnfidoFlow, openInitialSettingsPage, @@ -309,4 +313,5 @@ export { acceptWalletTerms, setKYCWallSource, requestPhysicalExpensifyCard, + resetWalletAdditionalDetailsDraft, }; diff --git a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx index 92b005ab8a88..61957ee2865b 100644 --- a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo} 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'; @@ -6,20 +6,20 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; -import useNetwork from '@hooks/useNetwork'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; -import Navigation from '@libs/Navigation/Navigation'; -import {parsePhoneNumber} from '@libs/PhoneNumber'; +// TODO: uncomment in the next PR +// import {parsePhoneNumber} from '@libs/PhoneNumber'; +import Navigation from '@navigation/Navigation'; import PhoneNumber from '@pages/EnablePayments/PersonalInfo/substeps/PhoneNumber'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalBankAccountForm} from '@src/types/form/PersonalBankAccountForm'; -import INPUT_IDS from '@src/types/form/PersonalBankAccountForm'; -import type {UserWallet, WalletAdditionalDetails} from '@src/types/onyx'; +import type {WalletAdditionalDetailsForm} from '@src/types/form'; +import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; +import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; import getInitialSubstepForPersonalInfo from '../utils/getInitialSubstepForPersonalInfo'; import getSubstepValues from '../utils/getSubstepValues'; import Address from './substeps/Address'; @@ -29,14 +29,11 @@ import FullName from './substeps/FullName'; import SocialSecurityNumber from './substeps/SocialSecurityNumber'; type PersonalInfoPageOnyxProps = { - /** The user's wallet */ - userWallet: OnyxEntry; - /** Reimbursement account from ONYX */ - walletAdditionalDetails: OnyxEntry; + walletAdditionalDetails: OnyxEntry; /** The draft values of the bank account being setup */ - walletAdditionalDetailsDraft: OnyxEntry; + walletAdditionalDetailsDraft: OnyxEntry; }; type PersonalInfoPageProps = PersonalInfoPageOnyxProps; @@ -44,44 +41,29 @@ type PersonalInfoPageProps = PersonalInfoPageOnyxProps; const PERSONAL_INFO_STEP_KEYS = INPUT_IDS.PERSONAL_INFO_STEP; const bodyContent: Array> = [FullName, DateOfBirth, PhoneNumber, SocialSecurityNumber, Address, Confirmation]; -function PersonalInfoPage({userWallet, walletAdditionalDetails, walletAdditionalDetailsDraft}: PersonalInfoPageProps) { +function PersonalInfoPage({walletAdditionalDetails, walletAdditionalDetailsDraft}: PersonalInfoPageProps) { const {translate} = useLocalize(); - const {isOffline} = useNetwork(); const styles = useThemeStyles(); - const {isPendingOnfidoResult, hasFailedOnfido} = userWallet ?? {}; - const values = useMemo(() => getSubstepValues(PERSONAL_INFO_STEP_KEYS, walletAdditionalDetailsDraft, walletAdditionalDetails), [walletAdditionalDetails, walletAdditionalDetailsDraft]); const submit = () => { - const personalDetails = { - phoneNumber: (values.phoneNumber && parsePhoneNumber(values.phoneNumber, {regionCode: CONST.COUNTRY.US}).number?.significant) ?? '', - legalFirstName: values?.[PERSONAL_INFO_STEP_KEYS.FIRST_NAME] ?? '', - legalLastName: values?.[PERSONAL_INFO_STEP_KEYS.LAST_NAME] ?? '', - addressStreet: values?.[PERSONAL_INFO_STEP_KEYS.STREET] ?? '', - addressCity: values?.[PERSONAL_INFO_STEP_KEYS.CITY] ?? '', - addressState: values?.[PERSONAL_INFO_STEP_KEYS.STATE] ?? '', - addressZip: values?.[PERSONAL_INFO_STEP_KEYS.ZIP_CODE] ?? '', - dob: values?.[PERSONAL_INFO_STEP_KEYS.DOB] ?? '', - ssn: values?.[PERSONAL_INFO_STEP_KEYS.SSN_LAST_4] ?? '', - }; + // TODO: uncomment in the next PR + // const personalDetails = { + // phoneNumber: (values.phoneNumber && parsePhoneNumber(values.phoneNumber, {regionCode: CONST.COUNTRY.US}).number?.significant) ?? '', + // legalFirstName: values?.[PERSONAL_INFO_STEP_KEYS.FIRST_NAME] ?? '', + // legalLastName: values?.[PERSONAL_INFO_STEP_KEYS.LAST_NAME] ?? '', + // addressStreet: values?.[PERSONAL_INFO_STEP_KEYS.STREET] ?? '', + // addressCity: values?.[PERSONAL_INFO_STEP_KEYS.CITY] ?? '', + // addressState: values?.[PERSONAL_INFO_STEP_KEYS.STATE] ?? '', + // addressZip: values?.[PERSONAL_INFO_STEP_KEYS.ZIP_CODE] ?? '', + // dob: values?.[PERSONAL_INFO_STEP_KEYS.DOB] ?? '', + // ssn: values?.[PERSONAL_INFO_STEP_KEYS.SSN_LAST_4] ?? '', + // }; // Attempt to set the personal details - Wallet.updatePersonalDetails(personalDetails); + // Wallet.updatePersonalDetails(personalDetails); + Navigation.navigate(ROUTES.SETTINGS_WALLET); }; - useEffect(() => { - if (isOffline) { - return; - } - - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if (isPendingOnfidoResult || hasFailedOnfido) { - Navigation.navigate(ROUTES.SETTINGS_WALLET, CONST.NAVIGATION.TYPE.UP); - return; - } - - Wallet.openEnablePaymentsPage(); - }, [isOffline, isPendingOnfidoResult, hasFailedOnfido]); - const startFrom = useMemo(() => getInitialSubstepForPersonalInfo(values), [values]); const { @@ -90,21 +72,32 @@ function PersonalInfoPage({userWallet, walletAdditionalDetails, walletAdditional nextScreen, prevScreen, moveTo, + screenIndex, } = useSubStep({ bodyContent, startFrom, onFinished: submit, }); + const handleBackButtonPress = () => { + // TODO: connect to the fist step of the wallet setup + if (screenIndex === 0) { + Navigation.navigate(ROUTES.SETTINGS_WALLET); + Wallet.resetWalletAdditionalDetailsDraft(); + return; + } + prevScreen(); + }; + return ( ({ - userWallet: { - key: ONYXKEYS.USER_WALLET, - - // We want to refresh the wallet each time the user attempts to activate the wallet so we won't use the - // stored values here. - initWithStoredValues: false, - }, // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_FORM walletAdditionalDetails: { key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx index d63dd7c08f01..f0e240274d3d 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx @@ -13,12 +13,12 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import AddressFormFields from '@pages/ReimbursementAccount/AddressFormFields'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/PersonalBankAccountForm'; -import type {WalletAdditionalDetails} from '@src/types/onyx'; +import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; +import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; type AddressOnyxProps = { /** wallet additional details from ONYX */ - walletAdditionalDetails: OnyxEntry; + walletAdditionalDetails: OnyxEntry; }; type AddressProps = AddressOnyxProps & SubStepProps; @@ -92,7 +92,7 @@ function Address({walletAdditionalDetails, onNext, isEditing}: AddressProps) { Address.displayName = 'Address'; export default withOnyx({ - // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_FORM + // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, }, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx index 49caca27db9f..f97e8035775a 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx @@ -16,16 +16,17 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/PersonalBankAccountForm'; -import type {WalletAdditionalDetails} from '@src/types/onyx'; +import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; +import type {WalletAdditionalDetailsForm} from '@src/types/form/WalletAdditionalDetailsForm'; +import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; import getSubstepValues from '../../utils/getSubstepValues'; type ConfirmationOnyxProps = { /** wallet additional details from ONYX */ - walletAdditionalDetails: OnyxEntry; + walletAdditionalDetails: OnyxEntry; /** The draft values of the bank account being setup */ - walletAdditionalDetailsDraft: OnyxEntry; + walletAdditionalDetailsDraft: OnyxEntry; }; type ConfirmationProps = ConfirmationOnyxProps & SubStepProps; @@ -143,11 +144,11 @@ function Confirmation({walletAdditionalDetails, walletAdditionalDetailsDraft, on Confirmation.displayName = 'Confirmation'; export default withOnyx({ - // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_FORM + // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, }, - // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_FORM + // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetailsDraft: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_DRAFT, }, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx index 42cfe4ca11de..8abcb2715e0d 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx @@ -15,12 +15,12 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/PersonalBankAccountForm'; -import type {WalletAdditionalDetails} from '@src/types/onyx'; +import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; +import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; type DateOfBirthOnyxProps = { /** Reimbursement account from ONYX */ - walletAdditionalDetails: OnyxEntry; + walletAdditionalDetails: OnyxEntry; }; type DateOfBirthProps = DateOfBirthOnyxProps & SubStepProps; @@ -85,7 +85,7 @@ function DateOfBirth({walletAdditionalDetails, onNext, isEditing}: DateOfBirthPr DateOfBirth.displayName = 'DateOfBirth'; export default withOnyx({ - // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_FORM + // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, }, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx index 6dbe855b2721..a1a1fd0f45f4 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx @@ -15,12 +15,12 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/PersonalBankAccountForm'; -import type {WalletAdditionalDetails} from '@src/types/onyx'; +import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; +import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; type FullNameOnyxProps = { /** Wallet Additional Details from ONYX */ - walletAdditionalDetails: OnyxEntry; + walletAdditionalDetails: OnyxEntry; }; type FullNameProps = FullNameOnyxProps & SubStepProps; @@ -30,12 +30,12 @@ const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.L const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.firstName && !ValidationUtils.isValidLegalName(values.firstName)) { - errors.firstName = 'bankAccount.error.firstName'; + if (values.legalFirstName && !ValidationUtils.isValidLegalName(values.legalFirstName)) { + errors.legalFirstName = 'bankAccount.error.firstName'; } - if (values.lastName && !ValidationUtils.isValidLegalName(values.lastName)) { - errors.lastName = 'bankAccount.error.lastName'; + if (values.legalLastName && !ValidationUtils.isValidLegalName(values.legalLastName)) { + errors.legalLastName = 'bankAccount.error.lastName'; } return errors; }; @@ -97,7 +97,7 @@ function FullName({walletAdditionalDetails, onNext, isEditing}: FullNameProps) { FullName.displayName = 'FullName'; export default withOnyx({ - // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM + // @ts-expect-error: ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, }, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx index 9b5f8cf0a611..095df6f573db 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx @@ -15,12 +15,12 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/PersonalBankAccountForm'; -import type {WalletAdditionalDetails} from '@src/types/onyx'; +import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; +import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; type PhoneNumberOnyxProps = { /** Reimbursement account from ONYX */ - walletAdditionalDetails: OnyxEntry; + walletAdditionalDetails: OnyxEntry; }; type PhoneNumberProps = PhoneNumberOnyxProps & SubStepProps; @@ -83,7 +83,7 @@ function PhoneNumber({walletAdditionalDetails, onNext, isEditing}: PhoneNumberPr PhoneNumber.displayName = 'PhoneNumber'; export default withOnyx({ - // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_FORM + // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, }, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx index de9c9ce25938..5f9f2756927f 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx @@ -15,12 +15,12 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/PersonalBankAccountForm'; -import type {WalletAdditionalDetails} from '@src/types/onyx'; +import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; +import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; type SocialSecurityNumberOnyxProps = { /** Reimbursement account from ONYX */ - walletAdditionalDetails: OnyxEntry; + walletAdditionalDetails: OnyxEntry; }; type SocialSecurityNumberProps = SocialSecurityNumberOnyxProps & SubStepProps; @@ -31,8 +31,8 @@ const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.SSN_LAST_4]; const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.ssnLast4 && !ValidationUtils.isValidSSNLastFour(values.ssnLast4)) { - errors.ssnLast4 = 'bankAccount.error.ssnLast4'; + if (values.ssn && !ValidationUtils.isValidSSNLastFour(values.ssn)) { + errors.ssn = 'bankAccount.error.ssnLast4'; } return errors; @@ -84,7 +84,7 @@ function SocialSecurityNumber({walletAdditionalDetails, onNext, isEditing}: Soci SocialSecurityNumber.displayName = 'SocialSecurityNumber'; export default withOnyx({ - // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_FORM + // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, }, diff --git a/src/pages/EnablePayments/utils/getInitialSubstepForPersonalInfo.ts b/src/pages/EnablePayments/utils/getInitialSubstepForPersonalInfo.ts index c1063b962d46..74da50570795 100644 --- a/src/pages/EnablePayments/utils/getInitialSubstepForPersonalInfo.ts +++ b/src/pages/EnablePayments/utils/getInitialSubstepForPersonalInfo.ts @@ -1,5 +1,5 @@ -import INPUT_IDS from '@src/types/form/PersonalBankAccountForm'; -import type {PersonalInfoStepProps} from '@src/types/form/PersonalBankAccountForm'; +import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; +import type {PersonalInfoStepProps} from '@src/types/form/WalletAdditionalDetailsForm'; const personalInfoKeys = INPUT_IDS.PERSONAL_INFO_STEP; diff --git a/src/pages/EnablePayments/utils/getSubstepValues.ts b/src/pages/EnablePayments/utils/getSubstepValues.ts index d78c081197c9..d1fdda5fcbc1 100644 --- a/src/pages/EnablePayments/utils/getSubstepValues.ts +++ b/src/pages/EnablePayments/utils/getSubstepValues.ts @@ -1,18 +1,19 @@ import type {OnyxEntry} from 'react-native-onyx'; -import type {PersonalBankAccountForm} from '@src/types/form'; -import type {WalletAdditionalDetails} from '@src/types/onyx'; +import type {WalletAdditionalDetailsForm} from '@src/types/form'; +import type {PersonalInfoStepProps} from '@src/types/form/WalletAdditionalDetailsForm'; +import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; -function getSubstepValues( +function getSubstepValues( inputKeys: Record, - walletAdditionalDetailsDraft: OnyxEntry, - walletAdditionalDetails: OnyxEntry, -): {[K in T]: PersonalBankAccountForm[K]} { + walletAdditionalDetailsDraft: OnyxEntry, + walletAdditionalDetails: OnyxEntry, +): {[K in T]: WalletAdditionalDetailsForm[K]} { return Object.entries(inputKeys).reduce( (acc, [, value]) => ({ ...acc, - [value]: walletAdditionalDetailsDraft?.[value] ?? walletAdditionalDetails?.[value] ?? '', + [value]: walletAdditionalDetailsDraft?.[value] ?? walletAdditionalDetails?.[value as keyof PersonalInfoStepProps] ?? '', }), - {} as {[K in T]: PersonalBankAccountForm[K]}, + {} as {[K in T]: WalletAdditionalDetailsForm[K]}, ); } diff --git a/src/types/form/PersonalBankAccountForm.ts b/src/types/form/PersonalBankAccountForm.ts index 8d0ff4f3869c..1a1922bf8013 100644 --- a/src/types/form/PersonalBankAccountForm.ts +++ b/src/types/form/PersonalBankAccountForm.ts @@ -13,18 +13,6 @@ const INPUT_IDS = { PLAID_ACCESS_TOKEN: 'plaidAccessToken', SELECTED_PLAID_ACCOUNT_ID: 'selectedPlaidAccountID', }, - PERSONAL_INFO_STEP: { - FIRST_NAME: 'firstName', - LAST_NAME: 'lastName', - DOB: 'dob', - SSN_LAST_4: 'ssnLast4', - STREET: 'addressStreet', - CITY: 'addressCity', - STATE: 'addressState', - ZIP_CODE: 'addressZipCode', - PHONE_NUMBER: 'phoneNumber', - IS_ONFIDO_SETUP_COMPLETE: 'isOnfidoSetupComplete', - }, } as const; type InputID = DeepValueOf; @@ -37,19 +25,6 @@ type BankAccountStepProps = { [INPUT_IDS.BANK_INFO_STEP.SETUP_TYPE]: string; }; -type PersonalInfoStepProps = { - [INPUT_IDS.PERSONAL_INFO_STEP.FIRST_NAME]: string; - [INPUT_IDS.PERSONAL_INFO_STEP.LAST_NAME]: string; - [INPUT_IDS.PERSONAL_INFO_STEP.STREET]: string; - [INPUT_IDS.PERSONAL_INFO_STEP.CITY]: string; - [INPUT_IDS.PERSONAL_INFO_STEP.STATE]: string; - [INPUT_IDS.PERSONAL_INFO_STEP.ZIP_CODE]: string; - [INPUT_IDS.PERSONAL_INFO_STEP.DOB]: string; - [INPUT_IDS.PERSONAL_INFO_STEP.PHONE_NUMBER]: string; - [INPUT_IDS.PERSONAL_INFO_STEP.SSN_LAST_4]: string; - [INPUT_IDS.PERSONAL_INFO_STEP.IS_ONFIDO_SETUP_COMPLETE]: boolean; -}; - type PlaidAccountProps = { [INPUT_IDS.BANK_INFO_STEP.IS_SAVINGS]: boolean; [INPUT_IDS.BANK_INFO_STEP.BANK_NAME]: string; @@ -57,8 +32,8 @@ type PlaidAccountProps = { [INPUT_IDS.BANK_INFO_STEP.SELECTED_PLAID_ACCOUNT_ID]: string; }; -type PersonalBankAccountForm = Form; +type PersonalBankAccountForm = Form; -export type {BankAccountStepProps, PlaidAccountProps, PersonalInfoStepProps, PersonalBankAccountForm}; +export type {BankAccountStepProps, PlaidAccountProps, PersonalBankAccountForm}; export default INPUT_IDS; diff --git a/src/types/form/WalletAdditionalDetailsForm.ts b/src/types/form/WalletAdditionalDetailsForm.ts new file mode 100644 index 000000000000..f3b167b09a14 --- /dev/null +++ b/src/types/form/WalletAdditionalDetailsForm.ts @@ -0,0 +1,36 @@ +import type DeepValueOf from '@src/types/utils/DeepValueOf'; +import type Form from './Form'; + +const INPUT_IDS = { + PERSONAL_INFO_STEP: { + FIRST_NAME: 'legalFirstName', + LAST_NAME: 'legalLastName', + DOB: 'dob', + SSN_LAST_4: 'ssn', + STREET: 'addressStreet', + CITY: 'addressCity', + STATE: 'addressState', + ZIP_CODE: 'addressZipCode', + PHONE_NUMBER: 'phoneNumber', + }, +} as const; + +type InputID = DeepValueOf; + +type PersonalInfoStepProps = { + [INPUT_IDS.PERSONAL_INFO_STEP.FIRST_NAME]: string; + [INPUT_IDS.PERSONAL_INFO_STEP.LAST_NAME]: string; + [INPUT_IDS.PERSONAL_INFO_STEP.STREET]: string; + [INPUT_IDS.PERSONAL_INFO_STEP.CITY]: string; + [INPUT_IDS.PERSONAL_INFO_STEP.STATE]: string; + [INPUT_IDS.PERSONAL_INFO_STEP.ZIP_CODE]: string; + [INPUT_IDS.PERSONAL_INFO_STEP.DOB]: string; + [INPUT_IDS.PERSONAL_INFO_STEP.PHONE_NUMBER]: string; + [INPUT_IDS.PERSONAL_INFO_STEP.SSN_LAST_4]: string; +}; + +type WalletAdditionalDetailsForm = Form; + +export type {PersonalInfoStepProps, WalletAdditionalDetailsForm}; + +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index ce3fcd428999..e68d0b4b50af 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -47,5 +47,6 @@ export type {WorkspaceTaxValueForm} from './WorkspaceTaxValueForm'; export type {WorkspaceTaxCustomName} from './WorkspaceTaxCustomName'; export type {PolicyCreateDistanceRateForm} from './PolicyCreateDistanceRateForm'; export type {PolicyDistanceRateEditForm} from './PolicyDistanceRateEditForm'; +export type {WalletAdditionalDetailsForm} from './WalletAdditionalDetailsForm'; export type {NewChatNameForm} from './NewChatNameForm'; export type {default as Form} from './Form'; diff --git a/src/types/onyx/WalletAdditionalDetails.ts b/src/types/onyx/WalletAdditionalDetails.ts index f2a9d10b1293..c574006e9f66 100644 --- a/src/types/onyx/WalletAdditionalDetails.ts +++ b/src/types/onyx/WalletAdditionalDetails.ts @@ -6,6 +6,18 @@ type WalletAdditionalQuestionDetails = { answer: string[]; }; +type WalletPersonalDetails = { + legalFirstName: string; + legalLastName: string; + dob: string; + ssn: string; + addressStreet: string; + addressCity: string; + addressState: string; + addressZipCode: string; + phoneNumber: string; +}; + type WalletAdditionalDetails = { /** Questions returned by Idology */ questions?: WalletAdditionalQuestionDetails[]; @@ -21,16 +33,10 @@ type WalletAdditionalDetails = { additionalErrorMessage?: string; isLoading?: boolean; errors?: OnyxCommon.Errors; - firstName: string; - lastName: string; - dob: string; - ssnLast4: string; - addressStreet: string; - addressCity: string; - addressState: string; - addressZipCode: string; - phoneNumber: string; }; +// TODO: refactor into one type after removing old wallet flow +type WalletAdditionalDetailsRefactor = WalletAdditionalDetails & WalletPersonalDetails; + export default WalletAdditionalDetails; -export type {WalletAdditionalQuestionDetails}; +export type {WalletAdditionalQuestionDetails, WalletPersonalDetails, WalletAdditionalDetailsRefactor}; From c888b6fbcb9a63fb21f513a7c63e6d50191f9971 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 17:55:59 +0200 Subject: [PATCH 162/661] fix: change step order --- src/CONST.ts | 6 +++--- .../PersonalInfo/PersonalInfo.tsx | 2 +- .../PersonalInfo/substeps/Confirmation.tsx | 21 +++++++++---------- .../utils/getInitialSubstepForPersonalInfo.ts | 6 +++--- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 9e39b86aaef2..7036f69d9096 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1317,9 +1317,9 @@ const CONST = { PERSONAL_INFO: { LEGAL_NAME: 0, DATE_OF_BIRTH: 1, - PHONE_NUMBER: 2, - SSN: 3, - ADDRESS: 4, + ADDRESS: 2, + PHONE_NUMBER: 3, + SSN: 4, }, }, TIER_NAME: { diff --git a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx index 61957ee2865b..6c38ad83b163 100644 --- a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx @@ -39,7 +39,7 @@ type PersonalInfoPageOnyxProps = { type PersonalInfoPageProps = PersonalInfoPageOnyxProps; const PERSONAL_INFO_STEP_KEYS = INPUT_IDS.PERSONAL_INFO_STEP; -const bodyContent: Array> = [FullName, DateOfBirth, PhoneNumber, SocialSecurityNumber, Address, Confirmation]; +const bodyContent: Array> = [FullName, DateOfBirth, Address, PhoneNumber, SocialSecurityNumber, Confirmation]; function PersonalInfoPage({walletAdditionalDetails, walletAdditionalDetailsDraft}: PersonalInfoPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx index f97e8035775a..06a214fd528e 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx @@ -67,6 +67,16 @@ function Confirmation({walletAdditionalDetails, walletAdditionalDetailsDraft, on onMove(PERSONAL_INFO_STEP_INDEXES.DATE_OF_BIRTH); }} /> + { + onMove(PERSONAL_INFO_STEP_INDEXES.ADDRESS); + }} + /> - { - onMove(PERSONAL_INFO_STEP_INDEXES.ADDRESS); - }} - /> - {`${translate('personalInfoStep.byAddingThisBankAccount')} `} Date: Wed, 17 Apr 2024 18:00:27 +0200 Subject: [PATCH 163/661] fix: rename components --- .../EnablePayments/PersonalInfo/PersonalInfo.tsx | 12 ++++++------ .../substeps/{Address.tsx => AddressStep.tsx} | 6 +++--- .../{Confirmation.tsx => ConfirmationStep.tsx} | 6 +++--- .../{DateOfBirth.tsx => DateOfBirthStep.tsx} | 6 +++--- .../substeps/{FullName.tsx => FullNameStep.tsx} | 6 +++--- .../{PhoneNumber.tsx => PhoneNumberStep.tsx} | 6 +++--- ...curityNumber.tsx => SocialSecurityNumberStep.tsx} | 6 +++--- 7 files changed, 24 insertions(+), 24 deletions(-) rename src/pages/EnablePayments/PersonalInfo/substeps/{Address.tsx => AddressStep.tsx} (96%) rename src/pages/EnablePayments/PersonalInfo/substeps/{Confirmation.tsx => ConfirmationStep.tsx} (97%) rename src/pages/EnablePayments/PersonalInfo/substeps/{DateOfBirth.tsx => DateOfBirthStep.tsx} (95%) rename src/pages/EnablePayments/PersonalInfo/substeps/{FullName.tsx => FullNameStep.tsx} (96%) rename src/pages/EnablePayments/PersonalInfo/substeps/{PhoneNumber.tsx => PhoneNumberStep.tsx} (96%) rename src/pages/EnablePayments/PersonalInfo/substeps/{SocialSecurityNumber.tsx => SocialSecurityNumberStep.tsx} (95%) diff --git a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx index 6c38ad83b163..2233343ff992 100644 --- a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx @@ -12,7 +12,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; // TODO: uncomment in the next PR // import {parsePhoneNumber} from '@libs/PhoneNumber'; import Navigation from '@navigation/Navigation'; -import PhoneNumber from '@pages/EnablePayments/PersonalInfo/substeps/PhoneNumber'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -22,11 +21,12 @@ import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; import getInitialSubstepForPersonalInfo from '../utils/getInitialSubstepForPersonalInfo'; import getSubstepValues from '../utils/getSubstepValues'; -import Address from './substeps/Address'; -import Confirmation from './substeps/Confirmation'; -import DateOfBirth from './substeps/DateOfBirth'; -import FullName from './substeps/FullName'; -import SocialSecurityNumber from './substeps/SocialSecurityNumber'; +import Address from './substeps/AddressStep'; +import Confirmation from './substeps/ConfirmationStep'; +import DateOfBirth from './substeps/DateOfBirthStep'; +import FullName from './substeps/FullNameStep'; +import PhoneNumber from './substeps/PhoneNumberStep'; +import SocialSecurityNumber from './substeps/SocialSecurityNumberStep'; type PersonalInfoPageOnyxProps = { /** Reimbursement account from ONYX */ diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx similarity index 96% rename from src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx rename to src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx index f0e240274d3d..4d8f4ad6f235 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/Address.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx @@ -48,7 +48,7 @@ const validate = (values: FormOnyxValues({ // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, }, -})(Address); +})(AddressStep); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx similarity index 97% rename from src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx rename to src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx index 06a214fd528e..0529f2ae8981 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/Confirmation.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx @@ -34,7 +34,7 @@ type ConfirmationProps = ConfirmationOnyxProps & SubStepProps; const PERSONAL_INFO_STEP_KEYS = INPUT_IDS.PERSONAL_INFO_STEP; const PERSONAL_INFO_STEP_INDEXES = CONST.WALLET.SUBSTEP_INDEXES.PERSONAL_INFO; -function Confirmation({walletAdditionalDetails, walletAdditionalDetailsDraft, onNext, onMove}: ConfirmationProps) { +function ConfirmationStep({walletAdditionalDetails, walletAdditionalDetailsDraft, onNext, onMove}: ConfirmationProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); @@ -140,7 +140,7 @@ function Confirmation({walletAdditionalDetails, walletAdditionalDetailsDraft, on ); } -Confirmation.displayName = 'Confirmation'; +ConfirmationStep.displayName = 'ConfirmationStep'; export default withOnyx({ // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS @@ -151,4 +151,4 @@ export default withOnyx({ walletAdditionalDetailsDraft: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_DRAFT, }, -})(Confirmation); +})(ConfirmationStep); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx similarity index 95% rename from src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx rename to src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx index 8abcb2715e0d..b69bdf4d7621 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx @@ -45,7 +45,7 @@ const validate = (values: FormOnyxValues({ // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, }, -})(DateOfBirth); +})(DateOfBirthStep); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx similarity index 96% rename from src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx rename to src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx index a1a1fd0f45f4..beccda648f9b 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx @@ -40,7 +40,7 @@ const validate = (values: FormOnyxValues({ // @ts-expect-error: ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, }, -})(FullName); +})(FullNameStep); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx similarity index 96% rename from src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx rename to src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx index 095df6f573db..7767cea7a715 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumber.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx @@ -36,7 +36,7 @@ const validate = (values: FormOnyxValues({ // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, }, -})(PhoneNumber); +})(PhoneNumberStep); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx similarity index 95% rename from src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx rename to src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx index 5f9f2756927f..f0daa3d36110 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumber.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx @@ -37,7 +37,7 @@ const validate = (values: FormOnyxValues({ // @ts-expect-error ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, }, -})(SocialSecurityNumber); +})(SocialSecurityNumberStep); From 042138bf69f2d3d67a291a1c0e21f0cec0867ce5 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 20:06:11 +0200 Subject: [PATCH 164/661] fix: linter --- src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts | 2 +- src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx | 4 ++-- .../EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts b/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts index 24fcc88b505c..8674092383a2 100644 --- a/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts +++ b/src/hooks/useWalletAdditionalDetailsStepFormSubmit.ts @@ -1,7 +1,7 @@ import type {FormOnyxKeys} from '@components/Form/types'; -import useStepFormSubmit from '@hooks/useStepFormSubmit'; import type {OnyxFormKey} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; +import useStepFormSubmit from './useStepFormSubmit'; import type {SubStepProps} from './useSubStep/types'; type UseWalletAdditionalDetailsStepFormSubmitParams = Pick & { diff --git a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx index 2233343ff992..049e948e18f2 100644 --- a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx @@ -12,6 +12,8 @@ import useThemeStyles from '@hooks/useThemeStyles'; // TODO: uncomment in the next PR // import {parsePhoneNumber} from '@libs/PhoneNumber'; import Navigation from '@navigation/Navigation'; +import getInitialSubstepForPersonalInfo from '@pages/EnablePayments/utils/getInitialSubstepForPersonalInfo'; +import getSubstepValues from '@pages/EnablePayments/utils/getSubstepValues'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -19,8 +21,6 @@ import ROUTES from '@src/ROUTES'; import type {WalletAdditionalDetailsForm} from '@src/types/form'; import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; -import getInitialSubstepForPersonalInfo from '../utils/getInitialSubstepForPersonalInfo'; -import getSubstepValues from '../utils/getSubstepValues'; import Address from './substeps/AddressStep'; import Confirmation from './substeps/ConfirmationStep'; import DateOfBirth from './substeps/DateOfBirthStep'; diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx index 0529f2ae8981..2a2809d84e10 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx @@ -14,12 +14,12 @@ import useNetwork from '@hooks/useNetwork'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; +import getSubstepValues from '@pages/EnablePayments/utils/getSubstepValues'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; import type {WalletAdditionalDetailsForm} from '@src/types/form/WalletAdditionalDetailsForm'; import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditionalDetails'; -import getSubstepValues from '../../utils/getSubstepValues'; type ConfirmationOnyxProps = { /** wallet additional details from ONYX */ From 10bb99688827a6ccbdbcc0f8833d5f461692ad94 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 23:05:01 +0200 Subject: [PATCH 165/661] fix: minor fix --- src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx index 049e948e18f2..a06d779e690d 100644 --- a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx @@ -61,7 +61,8 @@ function PersonalInfoPage({walletAdditionalDetails, walletAdditionalDetailsDraft // }; // Attempt to set the personal details // Wallet.updatePersonalDetails(personalDetails); - Navigation.navigate(ROUTES.SETTINGS_WALLET); + Navigation.goBack(ROUTES.SETTINGS_WALLET); + Wallet.resetWalletAdditionalDetailsDraft(); }; const startFrom = useMemo(() => getInitialSubstepForPersonalInfo(values), [values]); @@ -82,7 +83,7 @@ function PersonalInfoPage({walletAdditionalDetails, walletAdditionalDetailsDraft const handleBackButtonPress = () => { // TODO: connect to the fist step of the wallet setup if (screenIndex === 0) { - Navigation.navigate(ROUTES.SETTINGS_WALLET); + Navigation.goBack(ROUTES.SETTINGS_WALLET); Wallet.resetWalletAdditionalDetailsDraft(); return; } From dd0eb360c589858085f88a43a4c2f0b7c00cc99d Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 18 Apr 2024 07:26:52 +0700 Subject: [PATCH 166/661] Allow selecting a payer from the splits page --- src/ROUTES.ts | 5 ++ src/SCREENS.ts | 1 + ...raryForRefactorRequestConfirmationList.tsx | 80 +++++++++++------ src/languages/en.ts | 3 +- src/languages/es.ts | 1 + src/libs/API/parameters/SplitBillParams.ts | 1 + .../ModalStackNavigators/index.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + src/libs/Navigation/types.ts | 6 ++ src/libs/OptionsListUtils.ts | 9 +- src/libs/actions/IOU.ts | 15 +++- .../iou/request/step/IOURequestStepAmount.tsx | 2 +- .../step/IOURequestStepConfirmation.tsx | 11 +-- .../request/step/IOURequestStepDistance.tsx | 2 +- .../step/IOURequestStepScan/index.native.tsx | 2 +- .../request/step/IOURequestStepScan/index.tsx | 2 +- .../request/step/IOURequestStepSplitPayer.tsx | 88 +++++++++++++++++++ .../step/IOURequestStepTaxAmountPage.tsx | 2 +- src/types/onyx/Transaction.ts | 3 + 19 files changed, 193 insertions(+), 42 deletions(-) create mode 100644 src/pages/iou/request/step/IOURequestStepSplitPayer.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ec2bf11957e1..f89085be723b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -380,6 +380,11 @@ const ROUTES = { getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '', action: ValueOf = 'create') => getUrlWithBackToParam(`${action}/${iouType}/participants/${transactionID}/${reportID}`, backTo), }, + MONEY_REQUEST_STEP_SPLIT_PAYER: { + route: ':action/:iouType/confirmation/:transactionID/:reportID/payer', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, action: ValueOf = 'create') => + `${action}/${iouType}/confirmation/${transactionID}/${reportID}/payer`, + }, MONEY_REQUEST_STEP_SCAN: { route: ':action/:iouType/scan/:transactionID/:reportID', getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 96372d5bbabb..dcc333559300 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -155,6 +155,7 @@ const SCREENS = { STEP_WAYPOINT: 'Money_Request_Step_Waypoint', STEP_TAX_AMOUNT: 'Money_Request_Step_Tax_Amount', STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate', + STEP_SPLIT_PAYER: 'Money_Request_Step_Split_Payer', PARTICIPANTS: 'Money_Request_Participants', CURRENCY: 'Money_Request_Currency', WAYPOINT: 'Money_Request_Waypoint', diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index f460b9d8e88c..a005240c7354 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -28,6 +28,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; +import * as UserUtils from '@libs/UserUtils'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -43,7 +44,9 @@ import ConfirmedRoute from './ConfirmedRoute'; import ConfirmModal from './ConfirmModal'; import FormHelpMessage from './FormHelpMessage'; import * as Expensicons from './Icon/Expensicons'; +import MenuItem from './MenuItem'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; +import {usePersonalDetails} from './OnyxProvider'; import OptionsSelector from './OptionsSelector'; import PDFThumbnail from './PDFThumbnail'; import ReceiptEmptyState from './ReceiptEmptyState'; @@ -113,7 +116,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & selectedParticipants: Participant[]; /** Payee of the money request with login */ - payeePersonalDetails?: OnyxTypes.PersonalDetails; + payeePersonalDetails?: OnyxEntry; /** Can the participants be modified or not */ canModifyParticipants?: boolean; @@ -214,6 +217,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const personalDetails = usePersonalDetails(); const {canUseViolations} = usePermissions(); const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST; @@ -347,9 +351,15 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ * Returns the participants with amount */ const getParticipantsWithAmount = useCallback( - (participantsList: Participant[]) => { - const amount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode ?? ''); - return OptionsListUtils.getIOUConfirmationOptionsFromParticipants(participantsList, amount > 0 ? CurrencyUtils.convertToDisplayString(amount, iouCurrencyCode) : ''); + (participantsList: Participant[], payerAccountID: number) => { + const amount = IOUUtils.calculateAmount(participantsList.length - 1, iouAmount, iouCurrencyCode ?? ''); + const payerAmount = IOUUtils.calculateAmount(participantsList.length - 1, iouAmount, iouCurrencyCode ?? '', true); + return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( + participantsList, + amount > 0 ? CurrencyUtils.convertToDisplayString(amount, iouCurrencyCode) : '', + payerAmount > 0 ? CurrencyUtils.convertToDisplayString(payerAmount, iouCurrencyCode) : '', + payerAccountID, + ); }, [iouAmount, iouCurrencyCode], ); @@ -384,17 +394,25 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const selectedParticipants = useMemo(() => pickedParticipants.filter((participant) => participant.selected), [pickedParticipants]); const personalDetailsOfPayee = useMemo(() => payeePersonalDetails ?? currentUserPersonalDetails, [payeePersonalDetails, currentUserPersonalDetails]); + const payeeTooltipDetails = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs([personalDetailsOfPayee.accountID], personalDetails), false); + const payeeIcons = [ + { + source: UserUtils.getAvatar(personalDetailsOfPayee.avatar, personalDetailsOfPayee.accountID), + name: personalDetailsOfPayee.login ?? '', + type: CONST.ICON_TYPE_AVATAR, + id: personalDetailsOfPayee.accountID, + }, + ]; const userCanModifyParticipants = useRef(!isReadOnly && canModifyParticipants && hasMultipleParticipants); useEffect(() => { userCanModifyParticipants.current = !isReadOnly && canModifyParticipants && hasMultipleParticipants; }, [isReadOnly, canModifyParticipants, hasMultipleParticipants]); - const shouldDisablePaidBySection = userCanModifyParticipants.current; const optionSelectorSections = useMemo(() => { const sections = []; const unselectedParticipants = pickedParticipants.filter((participant) => !participant.selected); if (hasMultipleParticipants) { - const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipants); + const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipants, personalDetailsOfPayee.accountID); let formattedParticipantsList = [...new Set([...formattedSelectedParticipants, ...unselectedParticipants])]; if (!canModifyParticipants) { @@ -402,27 +420,18 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ...participant, isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), })); + } else { + formattedParticipantsList = formattedParticipantsList.map((participant) => ({ + ...participant, + isDisabled: participant.accountID === personalDetailsOfPayee.accountID, + })); } - const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, iouAmount, iouCurrencyCode ?? '', true); - const formattedPayeeOption = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail( - personalDetailsOfPayee, - iouAmount > 0 ? CurrencyUtils.convertToDisplayString(myIOUAmount, iouCurrencyCode) : '', - ); - - sections.push( - { - title: translate('moneyRequestConfirmationList.paidBy'), - data: [formattedPayeeOption], - shouldShow: true, - isDisabled: shouldDisablePaidBySection, - }, - { - title: translate('moneyRequestConfirmationList.splitWith'), - data: formattedParticipantsList, - shouldShow: true, - }, - ); + sections.push({ + title: translate('moneyRequestConfirmationList.splitAmounts'), + data: formattedParticipantsList, + shouldShow: true, + }); } else { const formattedSelectedParticipants = selectedParticipants.map((participant) => ({ ...participant, @@ -439,12 +448,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ selectedParticipants, pickedParticipants, hasMultipleParticipants, - iouAmount, - iouCurrencyCode, getParticipantsWithAmount, personalDetailsOfPayee, translate, - shouldDisablePaidBySection, canModifyParticipants, ]); @@ -646,6 +652,24 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. const classifiedFields = [ + { + item: ( + { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_SPLIT_PAYER.getRoute(iouType, transaction?.transactionID ?? '', reportID)); + }} + shouldShowRightIcon + titleWithTooltips={payeeTooltipDetails} + /> + ), + shouldShow: isTypeSplit, + isSupplementary: false, + }, { item: ( require('../../../../pages/iou/request/step/IOURequestStepScan').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_TAG]: () => 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.STEP_SPLIT_PAYER]: () => require('../../../../pages/iou/request/step/IOURequestStepSplitPayer').default as React.ComponentType, [SCREENS.MONEY_REQUEST.PARTICIPANTS]: () => require('../../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.HOLD]: () => require('../../../../pages/iou/HoldReasonPage').default as React.ComponentType, [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: () => require('../../../../pages/AddPersonalBankAccountPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 6165ccb16fa3..68b450109d1a 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -579,6 +579,7 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.PARTICIPANTS]: ROUTES.MONEY_REQUEST_PARTICIPANTS.route, [SCREENS.MONEY_REQUEST.RECEIPT]: ROUTES.MONEY_REQUEST_RECEIPT.route, [SCREENS.MONEY_REQUEST.STATE_SELECTOR]: {path: ROUTES.MONEY_REQUEST_STATE_SELECTOR.route, exact: true}, + [SCREENS.MONEY_REQUEST.STEP_SPLIT_PAYER]: ROUTES.MONEY_REQUEST_STEP_SPLIT_PAYER.route, [SCREENS.IOU_SEND.ENABLE_PAYMENTS]: ROUTES.IOU_SEND_ENABLE_PAYMENTS, [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: ROUTES.IOU_SEND_ADD_BANK_ACCOUNT, [SCREENS.IOU_SEND.ADD_DEBIT_CARD]: ROUTES.IOU_SEND_ADD_DEBIT_CARD, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 8273278f971e..4af49d4a10bf 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -432,6 +432,12 @@ type MoneyRequestNavigatorParamList = { reportID: string; backTo: Routes; }; + [SCREENS.MONEY_REQUEST.STEP_SPLIT_PAYER]: { + action: ValueOf; + iouType: ValueOf; + transactionID: string; + reportID: string; + }; [SCREENS.IOU_SEND.ENABLE_PAYMENTS]: undefined; [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: undefined; [SCREENS.IOU_SEND.ADD_DEBIT_CARD]: undefined; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index aa16d7b2dc5a..438964f9659b 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1962,10 +1962,15 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: Person /** * Build the IOUConfirmationOptions for showing participants */ -function getIOUConfirmationOptionsFromParticipants(participants: Array, amountText: string): Array { +function getIOUConfirmationOptionsFromParticipants( + participants: Array, + amountText: string, + payerAmountText = '', + payerAccountID = -1, +): Array { return participants.map((participant) => ({ ...participant, - descriptiveText: amountText, + descriptiveText: participant.accountID === payerAccountID ? payerAmountText : amountText, })); } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 896b88988818..dcde22604b18 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -442,6 +442,10 @@ function setMoneyRequestParticipants_temporaryForRefactor(transactionID: string, Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants}); } +function setSplitPayer(transactionID: string, payerAccountID: number) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {splitPayerAccountIDs: [payerAccountID]}); +} + function setMoneyRequestReceipt(transactionID: string, source: string, filename: string, isDraft: boolean, type?: string) { Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { receipt: {source, type: type ?? ''}, @@ -3506,6 +3510,7 @@ type SplitBillActionsParams = { billable?: boolean; iouRequestType?: IOURequestType; existingSplitChatReportID?: string; + splitPayerAccoutIDs?: number[]; }; /** @@ -3526,6 +3531,7 @@ function splitBill({ billable = false, iouRequestType = CONST.IOU.REQUEST_TYPE.MANUAL, existingSplitChatReportID = '', + splitPayerAccoutIDs = [], }: SplitBillActionsParams) { const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created); const {splitData, splits, onyxData} = createSplitsAndOnyxData( @@ -3560,6 +3566,7 @@ function splitBill({ createdReportActionID: splitData.createdReportActionID, policyID: splitData.policyID, chatType: splitData.chatType, + splitPayerAccoutIDs, }; API.write(WRITE_COMMANDS.SPLIT_BILL, parameters, onyxData); @@ -3619,6 +3626,7 @@ function splitBillAndOpenReport({ createdReportActionID: splitData.createdReportActionID, policyID: splitData.policyID, chatType: splitData.chatType, + splitPayerAccoutIDs: [], }; API.write(WRITE_COMMANDS.SPLIT_BILL_AND_OPEN_REPORT, parameters, onyxData); @@ -5800,7 +5808,7 @@ function replaceReceipt(transactionID: string, file: File, source: string) { * @param transactionID of the transaction to set the participants of * @param report attached to the transaction */ -function setMoneyRequestParticipantsFromReport(transactionID: string, report: OnyxEntry) { +function setMoneyRequestParticipantsFromReport(transactionID: string, report: OnyxEntry, iouType: ValueOf) { // If the report is iou or expense report, we should get the chat report to set participant for request money const chatReport = ReportUtils.isMoneyRequestReport(report) ? ReportUtils.getReport(report?.chatReportID) : report; const currentUserAccountID = currentUserPersonalDetails.accountID; @@ -5810,6 +5818,10 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On ? [{accountID: 0, reportID: chatReport?.reportID, isPolicyExpenseChat: ReportUtils.isPolicyExpenseChat(chatReport), selected: true}] : (chatReport?.participantAccountIDs ?? []).filter((accountID) => currentUserAccountID !== accountID).map((accountID) => ({accountID, selected: true})); + if (iouType === CONST.IOU.TYPE.SPLIT) { + participants.push({accountID: currentUserAccountID, selected: true}); + } + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true}); } @@ -6029,6 +6041,7 @@ export { setMoneyRequestPendingFields, setMoneyRequestReceipt, setMoneyRequestAmount, + setSplitPayer, setMoneyRequestBillable, setMoneyRequestCategory, setMoneyRequestCurrency, diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 690a7e3d9d0d..92828b59952a 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -118,7 +118,7 @@ function IOURequestStepAmount({ // inside a report. In this case, the participants can be automatically assigned from the report and the user can skip the participants step and go straight // to the confirm step. if (report?.reportID) { - IOU.setMoneyRequestParticipantsFromReport(transactionID, report); + IOU.setMoneyRequestParticipantsFromReport(transactionID, report, iouType); Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); return; } diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 46e663956e4a..999302c69945 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -78,6 +78,7 @@ function IOURequestStepConfirmation({ const isSharingTrackExpense = action === CONST.IOU.ACTION.SHARE; const isCategorizingTrackExpense = action === CONST.IOU.ACTION.CATEGORIZE; const isRequestingFromTrackExpense = action === CONST.IOU.ACTION.MOVE; + const payeePersonalDetails = personalDetails?.[transaction?.splitPayerAccountIDs?.[0] ?? -1]; const requestType = TransactionUtils.getRequestType(transaction); @@ -103,14 +104,12 @@ function IOURequestStepConfirmation({ return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); }, [iouType, transaction, translate, isSharingTrackExpense, isCategorizingTrackExpense, isRequestingFromTrackExpense]); - const participants = useMemo( - () => + const participants = useMemo(() => ( transaction?.participants?.map((participant) => { const participantAccountID = participant.accountID ?? 0; return participantAccountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant); - }) ?? [], - [transaction?.participants, personalDetails], - ); + }) ?? [] + ), [transaction?.participants, personalDetails]); const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); const formHasBeenSubmitted = useRef(false); @@ -326,6 +325,7 @@ function IOURequestStepConfirmation({ existingSplitChatReportID: report?.reportID, billable: transaction.billable, iouRequestType: transaction.iouRequestType, + splitPayerAccoutIDs: transaction.splitPayerAccountIDs ?? [], }); } return; @@ -535,6 +535,7 @@ function IOURequestStepConfirmation({ isDistanceRequest={requestType === CONST.IOU.REQUEST_TYPE.DISTANCE} shouldShowSmartScanFields={IOUUtils.isMovingTransactionFromTrackExpense(action) ? transaction?.amount !== 0 : requestType !== CONST.IOU.REQUEST_TYPE.SCAN} action={action} + payeePersonalDetails={payeePersonalDetails} /> )} diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index fae07ad2249c..b720e2f4c8f8 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -160,7 +160,7 @@ function IOURequestStepDistance({ // inside a report. In this case, the participants can be automatically assigned from the report and the user can skip the participants step and go straight // to the confirm step. if (report?.reportID) { - IOU.setMoneyRequestParticipantsFromReport(transactionID, report); + IOU.setMoneyRequestParticipantsFromReport(transactionID, report, iouType); Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); return; } diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index c1b360f89e48..50bd506fd0e0 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -175,7 +175,7 @@ function IOURequestStepScan({ // If the transaction was created from the + menu from the composer inside of a chat, the participants can automatically // be added to the transaction (taken from the chat report participants) and then the person is taken to the confirmation step. - IOU.setMoneyRequestParticipantsFromReport(transactionID, report); + IOU.setMoneyRequestParticipantsFromReport(transactionID, report, iouType); Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); }, [iouType, report, reportID, transactionID, transaction?.isFromGlobalCreate, backTo]); diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 654f9e9d9f91..4037a6cffd25 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -213,7 +213,7 @@ function IOURequestStepScan({ // If the transaction was created from the + menu from the composer inside of a chat, the participants can automatically // be added to the transaction (taken from the chat report participants) and then the person is taken to the confirmation step. - IOU.setMoneyRequestParticipantsFromReport(transactionID, report); + IOU.setMoneyRequestParticipantsFromReport(transactionID, report, iouType); Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); }, [iouType, report, reportID, transactionID, transaction?.isFromGlobalCreate, backTo]); diff --git a/src/pages/iou/request/step/IOURequestStepSplitPayer.tsx b/src/pages/iou/request/step/IOURequestStepSplitPayer.tsx new file mode 100644 index 000000000000..8a8e636975cd --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepSplitPayer.tsx @@ -0,0 +1,88 @@ +import React, {useMemo} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {usePersonalDetails} from '@components/OnyxProvider'; +import SelectionList from '@components/SelectionList'; +import UserListItem from '@components/SelectionList/UserListItem'; +import useLocalize from '@hooks/useLocalize'; +import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus'; +import * as IOUUtils from '@libs/IOUUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import type {OptionData} from '@libs/ReportUtils'; +import * as IOU from '@userActions/IOU'; +import type SCREENS from '@src/SCREENS'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Participant} from '@src/types/onyx/IOU'; +import StepScreenWrapper from './StepScreenWrapper'; +import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; +import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; +import withWritableReportOrNotFound from './withWritableReportOrNotFound'; + +type IOURequestStepSplitPayerProps = WithWritableReportOrNotFoundProps & { + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + transaction: OnyxEntry; + }; + +function IOURequestStepSplitPayer({ + route: { + params: {iouType, transactionID}, + }, + transaction, +}: IOURequestStepSplitPayerProps) { + const {translate} = useLocalize(); + const personalDetails = usePersonalDetails(); + const {didScreenTransitionEnd} = useScreenWrapperTranstionStatus(); + const sections = useMemo(() => { + const participantOptions = + transaction?.participants + ?.filter((participant) => Boolean(participant.accountID)) + ?.map((participant) => { + const participantAccountID = participant.accountID ?? 0; + return participantAccountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant); + }) ?? []; + return [ + { + title: '', + data: participantOptions.map((participantOption) => ({ + ...participantOption, + isSelected: !!transaction?.splitPayerAccountIDs && transaction?.splitPayerAccountIDs?.includes(participantOption.accountID ?? 0), + })), + }, + ]; + }, [transaction?.participants, personalDetails, transaction?.splitPayerAccountIDs]); + + const navigateBack = () => { + Navigation.goBack(); + }; + + const setSplitPayer = (item: Participant | OptionData) => { + IOU.setSplitPayer(transactionID, item.accountID ?? 0); + navigateBack(); + }; + + return ( + + + + ); +} + +IOURequestStepSplitPayer.displayName = 'IOURequestStepSplitPayer'; + +// eslint-disable-next-line rulesdir/no-negated-variables +const IOURequestStepSplitPayerWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepSplitPayer); +// eslint-disable-next-line rulesdir/no-negated-variables +const IOURequestStepSplitPayerWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepSplitPayerWithWritableReportOrNotFound); + +export default IOURequestStepSplitPayerWithFullTransactionOrNotFound; diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx index 40e3e5a06991..6ad4470b8e30 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx @@ -128,7 +128,7 @@ function IOURequestStepTaxAmountPage({ // to the confirm step. if (report?.reportID) { // TODO: Is this really needed at all? - IOU.setMoneyRequestParticipantsFromReport(transactionID, report); + IOU.setMoneyRequestParticipantsFromReport(transactionID, report, iouType); Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); return; } diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index cf997d703680..c94f339b294c 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -225,6 +225,9 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< /** The linked report id for the tracked expense */ linkedTrackedExpenseReportID?: string; + + /** The payers of split bill transaction */ + splitPayerAccountIDs?: number[]; }, keyof Comment >; From c8771068fc49be5de12921021fbb5d85101960b6 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 18 Apr 2024 07:51:08 +0700 Subject: [PATCH 167/661] refactor logic display amount --- ...neyTemporaryForRefactorRequestConfirmationList.tsx | 11 +++++------ src/libs/OptionsListUtils.ts | 5 ++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index df13dcbbf46c..53ac296b54e9 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -351,14 +351,13 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ * Returns the participants with amount */ const getParticipantsWithAmount = useCallback( - (participantsList: Participant[], payerAccountID: number) => { + (participantsList: Participant[]) => { const amount = IOUUtils.calculateAmount(participantsList.length - 1, iouAmount, iouCurrencyCode ?? ''); - const payerAmount = IOUUtils.calculateAmount(participantsList.length - 1, iouAmount, iouCurrencyCode ?? '', true); + const myAmount = IOUUtils.calculateAmount(participantsList.length - 1, iouAmount, iouCurrencyCode ?? '', true); return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participantsList, amount > 0 ? CurrencyUtils.convertToDisplayString(amount, iouCurrencyCode) : '', - payerAmount > 0 ? CurrencyUtils.convertToDisplayString(payerAmount, iouCurrencyCode) : '', - payerAccountID, + myAmount > 0 ? CurrencyUtils.convertToDisplayString(myAmount, iouCurrencyCode) : '', ); }, [iouAmount, iouCurrencyCode], @@ -412,7 +411,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const sections = []; const unselectedParticipants = pickedParticipants.filter((participant) => !participant.selected); if (hasMultipleParticipants) { - const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipants, personalDetailsOfPayee.accountID); + const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipants); let formattedParticipantsList = [...new Set([...formattedSelectedParticipants, ...unselectedParticipants])]; if (!canModifyParticipants) { @@ -573,7 +572,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ playSound(SOUNDS.DONE); setDidConfirm(true); - onConfirm?.(selectedParticipants); + onConfirm?.(selectedParticipants.filter((participant) => participant.accountID !== currentUserPersonalDetails.accountID)); } }, [ diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index f21f21ddf36f..47ccf9927585 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1978,12 +1978,11 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: Person function getIOUConfirmationOptionsFromParticipants( participants: Array, amountText: string, - payerAmountText = '', - payerAccountID = -1, + myAmountText = '', ): Array { return participants.map((participant) => ({ ...participant, - descriptiveText: participant.accountID === payerAccountID ? payerAmountText : amountText, + descriptiveText: participant.accountID === currentUserAccountID ? myAmountText : amountText, })); } From 3b1040bc9ae23ff7546c0623fe96629cf838d0f7 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Wed, 17 Apr 2024 18:26:50 -0700 Subject: [PATCH 168/661] WIP: Add ConnectionComplete page --- src/ROUTES.ts | 1 + src/SCREENS.ts | 1 + src/languages/en.ts | 5 ++ src/languages/es.ts | 4 ++ .../Navigation/AppNavigator/PublicScreens.tsx | 6 +++ src/libs/Navigation/linkingConfig/config.ts | 1 + src/libs/Navigation/types.ts | 1 + src/pages/ConnectionCompletePage.tsx | 51 +++++++++++++++++++ 8 files changed, 70 insertions(+) create mode 100644 src/pages/ConnectionCompletePage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 7d73d8e55503..ce731f5be7da 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -44,6 +44,7 @@ const ROUTES = { TRANSITION_BETWEEN_APPS: 'transition', VALIDATE_LOGIN: 'v/:accountID/:validateCode', + CONNECTION_COMPLETE: 'connection-complete', GET_ASSISTANCE: { route: 'get-assistance/:taskID', getRoute: (taskID: string, backTo: string) => getUrlWithBackToParam(`get-assistance/${taskID}`, backTo), diff --git a/src/SCREENS.ts b/src/SCREENS.ts index d474945d332e..b682388d6cf6 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -20,6 +20,7 @@ const SCREENS = { NOT_FOUND: 'not-found', TRANSITION_BETWEEN_APPS: 'TransitionBetweenApps', VALIDATE_LOGIN: 'ValidateLogin', + CONNECTION_COMPLETE: 'ConnectionComplete', UNLINK_LOGIN: 'UnlinkLogin', SETTINGS_CENTRAL_PANE: 'SettingsCentralPane', WORKSPACES_CENTRAL_PANE: 'WorkspacesCentralPane', diff --git a/src/languages/en.ts b/src/languages/en.ts index ed2587e5e2c6..2a124ffe157e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -350,6 +350,11 @@ export default { notAllowedExtension: 'This file type is not allowed', folderNotAllowedMessage: 'Uploading a folder is not allowed. Try a different file.', protectedPDFNotSupported: 'Password-protected PDF is not supported', + connectionComplete: 'Connection Complete', + }, + connectionComplete: { + title: 'Connection Complete', + supportingText: 'You can close this window and head back to the Expensify app.', }, avatarCropModal: { title: 'Edit photo', diff --git a/src/languages/es.ts b/src/languages/es.ts index beb654cf0bc4..0bf68c48de59 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -312,6 +312,10 @@ export default { subtitleText3: '.', }, }, + connectionComplete: { + title: 'Conexión Completa', + supportingText: 'Ya puedes cerrar esta página y volver a la App de Expensify.', + }, location: { useCurrent: 'Usar ubicación actual', notFound: 'No pudimos encontrar tu ubicación, inténtalo de nuevo o introduce una dirección manualmente.', diff --git a/src/libs/Navigation/AppNavigator/PublicScreens.tsx b/src/libs/Navigation/AppNavigator/PublicScreens.tsx index 6b1557994627..69d5f8214c49 100644 --- a/src/libs/Navigation/AppNavigator/PublicScreens.tsx +++ b/src/libs/Navigation/AppNavigator/PublicScreens.tsx @@ -8,6 +8,7 @@ import SAMLSignInPage from '@pages/signin/SAMLSignInPage'; import SignInPage from '@pages/signin/SignInPage'; import UnlinkLoginPage from '@pages/UnlinkLoginPage'; import ValidateLoginPage from '@pages/ValidateLoginPage'; +import ConnectionCompletePage from '@pages/ConnectionCompletePage'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; import defaultScreenOptions from './defaultScreenOptions'; @@ -33,6 +34,11 @@ function PublicScreens() { options={defaultScreenOptions} component={ValidateLoginPage} /> + ['config'] = { [SCREENS.VALIDATE_LOGIN]: ROUTES.VALIDATE_LOGIN, [SCREENS.UNLINK_LOGIN]: ROUTES.UNLINK_LOGIN, [SCREENS.TRANSITION_BETWEEN_APPS]: ROUTES.TRANSITION_BETWEEN_APPS, + [SCREENS.CONNECTION_COMPLETE]: ROUTES.CONNECTION_COMPLETE, [SCREENS.CONCIERGE]: ROUTES.CONCIERGE, [SCREENS.SIGN_IN_WITH_APPLE_DESKTOP]: ROUTES.APPLE_SIGN_IN, [SCREENS.SIGN_IN_WITH_GOOGLE_DESKTOP]: ROUTES.GOOGLE_SIGN_IN, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 7dd2f274aa9e..1392ba342186 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -755,6 +755,7 @@ type PublicScreensParamList = SharedScreensParamList & { [SCREENS.SIGN_IN_WITH_APPLE_DESKTOP]: undefined; [SCREENS.SIGN_IN_WITH_GOOGLE_DESKTOP]: undefined; [SCREENS.SAML_SIGN_IN]: undefined; + [SCREENS.CONNECTION_COMPLETE]: undefined; }; type AuthScreensParamList = SharedScreensParamList & { diff --git a/src/pages/ConnectionCompletePage.tsx b/src/pages/ConnectionCompletePage.tsx new file mode 100644 index 000000000000..47cad9e00750 --- /dev/null +++ b/src/pages/ConnectionCompletePage.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import {View} from 'react-native'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Lottie from '@components/Lottie'; +import LottieAnimations from '@components/LottieAnimations'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; + + +function ConnectionCompletePage() { + const theme = useTheme(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + return ( + + + + + + + {translate('connectionComplete.title')} + + + {translate('connectionComplete.supportingText')} + + + + + + + ); +} + +ConnectionCompletePage.displayName = 'ConnectionCompletePage'; + +export default ConnectionCompletePage; From 471398a14ca8fdad95f8a400a6c729acc4593764 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Wed, 17 Apr 2024 18:33:04 -0700 Subject: [PATCH 169/661] Enable ConnectionComplete screen for authenticated user --- src/libs/Navigation/AppNavigator/AuthScreens.tsx | 6 ++++++ src/libs/Navigation/types.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 9157d7486c9e..40adf2de889d 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -18,6 +18,7 @@ import PusherConnectionManager from '@libs/PusherConnectionManager'; import * as SessionUtils from '@libs/SessionUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import DesktopSignInRedirectPage from '@pages/signin/DesktopSignInRedirectPage'; +import ConnectionCompletePage from '@pages/ConnectionCompletePage'; import SearchInputManager from '@pages/workspace/SearchInputManager'; import * as App from '@userActions/App'; import * as Download from '@userActions/Download'; @@ -390,6 +391,11 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie getComponent={loadReceiptView} listeners={modalScreenListeners} /> + diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 1392ba342186..39fadf9b1ae5 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -789,6 +789,7 @@ type AuthScreensParamList = SharedScreensParamList & { reportID: string; transactionID: string; }; + [SCREENS.CONNECTION_COMPLETE]: undefined; }; type RootStackParamList = PublicScreensParamList & AuthScreensParamList & ChatFinderNavigatorParamList; From 3c4f3396b327decc1ef071172ffee86011bdc457 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 18 Apr 2024 12:03:56 +0700 Subject: [PATCH 170/661] run prettier --- ...eyTemporaryForRefactorRequestConfirmationList.tsx | 11 ++--------- .../iou/request/step/IOURequestStepConfirmation.tsx | 8 +++++--- .../iou/request/step/IOURequestStepSplitPayer.tsx | 12 ++++++------ 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index 53ac296b54e9..adfe338c18a5 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -443,15 +443,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ }); } return sections; - }, [ - selectedParticipants, - pickedParticipants, - hasMultipleParticipants, - getParticipantsWithAmount, - personalDetailsOfPayee, - translate, - canModifyParticipants, - ]); + }, [selectedParticipants, pickedParticipants, hasMultipleParticipants, getParticipantsWithAmount, personalDetailsOfPayee, translate, canModifyParticipants]); const selectedOptions = useMemo(() => { if (!hasMultipleParticipants) { @@ -589,6 +581,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ iouAmount, isEditingSplitBill, onConfirm, + currentUserPersonalDetails.accountID, ], ); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index dce7f4319f6f..237ad5eb7eb2 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -104,12 +104,14 @@ function IOURequestStepConfirmation({ return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); }, [iouType, report, transaction, translate, isSharingTrackExpense, isCategorizingTrackExpense, isRequestingFromTrackExpense]); - const participants = useMemo(() => ( + const participants = useMemo( + () => transaction?.participants?.map((participant) => { const participantAccountID = participant.accountID ?? 0; return participantAccountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant); - }) ?? [] - ), [transaction?.participants, personalDetails]); + }) ?? [], + [transaction?.participants, personalDetails], + ); const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); const formHasBeenSubmitted = useRef(false); diff --git a/src/pages/iou/request/step/IOURequestStepSplitPayer.tsx b/src/pages/iou/request/step/IOURequestStepSplitPayer.tsx index 8a8e636975cd..8327e37579a9 100644 --- a/src/pages/iou/request/step/IOURequestStepSplitPayer.tsx +++ b/src/pages/iou/request/step/IOURequestStepSplitPayer.tsx @@ -19,9 +19,9 @@ import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotF import withWritableReportOrNotFound from './withWritableReportOrNotFound'; type IOURequestStepSplitPayerProps = WithWritableReportOrNotFoundProps & { - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - transaction: OnyxEntry; - }; + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + transaction: OnyxEntry; +}; function IOURequestStepSplitPayer({ route: { @@ -44,9 +44,9 @@ function IOURequestStepSplitPayer({ { title: '', data: participantOptions.map((participantOption) => ({ - ...participantOption, - isSelected: !!transaction?.splitPayerAccountIDs && transaction?.splitPayerAccountIDs?.includes(participantOption.accountID ?? 0), - })), + ...participantOption, + isSelected: !!transaction?.splitPayerAccountIDs && transaction?.splitPayerAccountIDs?.includes(participantOption.accountID ?? 0), + })), }, ]; }, [transaction?.participants, personalDetails, transaction?.splitPayerAccountIDs]); From 535b2838ef4e5ed9727d540f911ba8397976ca75 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 18 Apr 2024 12:25:33 +0700 Subject: [PATCH 171/661] Not allow deselect the payer --- src/ROUTES.ts | 2 +- ...yTemporaryForRefactorRequestConfirmationList.tsx | 13 ++++--------- src/libs/actions/IOU.ts | 1 + 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index e894e470b670..89044261145d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -383,7 +383,7 @@ const ROUTES = { MONEY_REQUEST_STEP_SPLIT_PAYER: { route: ':action/:iouType/confirmation/:transactionID/:reportID/payer', getRoute: (iouType: ValueOf, transactionID: string, reportID: string, action: ValueOf = 'create') => - `${action}/${iouType}/confirmation/${transactionID}/${reportID}/payer`, + `${action}/${iouType}/confirmation/${transactionID}/${reportID}/payer` as const, }, MONEY_REQUEST_STEP_SCAN: { route: ':action/:iouType/scan/:transactionID/:reportID', diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index adfe338c18a5..ad2a2d63ad2e 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -419,11 +419,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ...participant, isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), })); - } else { - formattedParticipantsList = formattedParticipantsList.map((participant) => ({ - ...participant, - isDisabled: participant.accountID === personalDetailsOfPayee.accountID, - })); } sections.push({ @@ -449,8 +444,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ if (!hasMultipleParticipants) { return []; } - return [...selectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetailsOfPayee)]; - }, [selectedParticipants, hasMultipleParticipants, personalDetailsOfPayee]); + return [...selectedParticipants]; + }, [selectedParticipants, hasMultipleParticipants]); useEffect(() => { if (!isDistanceRequest || isMovingTransactionFromTrackExpense) { @@ -500,12 +495,12 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const selectParticipant = useCallback( (option: Participant) => { // Return early if selected option is currently logged in user. - if (option.accountID === session?.accountID) { + if (option.accountID === session?.accountID || option.accountID === personalDetailsOfPayee.accountID) { return; } onSelectParticipant?.(option); }, - [session?.accountID, onSelectParticipant], + [session?.accountID, onSelectParticipant, personalDetailsOfPayee.accountID], ); /** diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5d9196fc0b10..8eb5e56fc4d1 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -335,6 +335,7 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry, transactionID: newTransactionID, isFromGlobalCreate, merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, + splitPayerAccountIDs: [currentUserPersonalDetails.accountID] }); } From 24fb63b19a7b0bf01fedc02146fdc29a9d2d006f Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 18 Apr 2024 12:49:23 +0700 Subject: [PATCH 172/661] update participants to include the payee --- ...eyTemporaryForRefactorRequestConfirmationList.tsx | 6 +++--- src/libs/actions/IOU.ts | 2 +- .../iou/request/step/IOURequestStepConfirmation.tsx | 12 ++++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index ad2a2d63ad2e..3a373dca77f2 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -438,7 +438,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ }); } return sections; - }, [selectedParticipants, pickedParticipants, hasMultipleParticipants, getParticipantsWithAmount, personalDetailsOfPayee, translate, canModifyParticipants]); + }, [selectedParticipants, pickedParticipants, hasMultipleParticipants, getParticipantsWithAmount, translate, canModifyParticipants]); const selectedOptions = useMemo(() => { if (!hasMultipleParticipants) { @@ -586,7 +586,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ } const shouldShowSettlementButton = iouType === CONST.IOU.TYPE.SEND; - const shouldDisableButton = selectedParticipants.length === 0; + const shouldDisableButton = isTypeSplit ? selectedParticipants.length === 1 : selectParticipants.length === 0; const button = shouldShowSettlementButton ? ( ); - }, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); + }, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2, isTypeSplit]); // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 8eb5e56fc4d1..f870ef0ddc97 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -335,7 +335,7 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry, transactionID: newTransactionID, isFromGlobalCreate, merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, - splitPayerAccountIDs: [currentUserPersonalDetails.accountID] + splitPayerAccountIDs: [currentUserPersonalDetails.accountID], }); } diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 237ad5eb7eb2..bb709b7f6c00 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -115,6 +115,18 @@ function IOURequestStepConfirmation({ const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); const formHasBeenSubmitted = useRef(false); + useEffect(() => { + if (transaction?.participants?.findIndex((participant) => participant.accountID === payeePersonalDetails?.accountID) !== -1 || iouType !== CONST.IOU.TYPE.SPLIT) { + return; + } + + const payeeParticipant = OptionsListUtils.getParticipantsOption({accountID: payeePersonalDetails?.accountID, selected: true}, personalDetails); + IOU.setMoneyRequestParticipants_temporaryForRefactor(transaction.transactionID, [...(transaction?.participants ?? []), payeeParticipant]); + + // We only want to run it when the component is mounted + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { const policyExpenseChat = participants?.find((participant) => participant.isPolicyExpenseChat); if (policyExpenseChat?.policyID) { From 0de971c1fbea30434cf9b7ef6f73365b473a559c Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 18 Apr 2024 12:53:36 +0700 Subject: [PATCH 173/661] fix lint --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index 3a373dca77f2..3246928ec271 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -586,7 +586,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ } const shouldShowSettlementButton = iouType === CONST.IOU.TYPE.SEND; - const shouldDisableButton = isTypeSplit ? selectedParticipants.length === 1 : selectParticipants.length === 0; + const shouldDisableButton = isTypeSplit ? selectedParticipants.length === 1 : selectedParticipants.length === 0; const button = shouldShowSettlementButton ? ( Date: Thu, 18 Apr 2024 09:12:03 +0200 Subject: [PATCH 174/661] Add optimistic personal details for the receiver; update params --- src/libs/API/parameters/SendInvoiceParams.ts | 12 +-- src/libs/actions/IOU.ts | 89 ++++++++++++-------- 2 files changed, 58 insertions(+), 43 deletions(-) diff --git a/src/libs/API/parameters/SendInvoiceParams.ts b/src/libs/API/parameters/SendInvoiceParams.ts index 7b074657233e..5e65968654e6 100644 --- a/src/libs/API/parameters/SendInvoiceParams.ts +++ b/src/libs/API/parameters/SendInvoiceParams.ts @@ -8,12 +8,12 @@ type SendInvoiceParams = { merchant: string; date: string; category?: string; - optimisticInvoiceRoomID?: string; - optimisticCreatedChatReportActionID: string; - optimisticInvoiceReportID: string; - optimisticReportPreviewReportActionID: string; - optimisticTransactionID: string; - optimisticTransactionThreadReportID: string; + invoiceRoomID?: string; + createdChatReportActionID: string; + invoiceReportID: string; + reportPreviewReportActionID: string; + transactionID: string; + transactionThreadReportID: string; }; export default SendInvoiceParams; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index d6be557b8107..87d9c46cc142 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -851,6 +851,7 @@ function buildOnyxDataForInvoice( chatCreatedAction: OptimisticCreatedReportAction, iouCreatedAction: OptimisticCreatedReportAction, iouAction: OptimisticIOUReportAction, + optimisticPersonalDetailListAction: OnyxTypes.PersonalDetailsList, reportPreviewAction: ReportAction, optimisticPolicyRecentlyUsedCategories: string[], optimisticPolicyRecentlyUsedTags: OnyxTypes.RecentlyUsedTags, @@ -966,6 +967,14 @@ function buildOnyxDataForInvoice( }); } + if (!isEmptyObject(optimisticPersonalDetailListAction)) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + value: optimisticPersonalDetailListAction, + }); + } + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -1624,8 +1633,10 @@ function getSendInvoiceInformation( const {amount = 0, currency = '', created = '', merchant = '', category = '', tag = '', billable, comment, participants} = transaction ?? {}; const trimmedComment = (comment?.comment ?? '').trim(); const senderWorkspaceID = participants?.find((participant) => participant?.policyID)?.policyID ?? ''; - const receiverAccountID = participants?.find((participant) => participant?.accountID)?.accountID ?? -1; - const receiver = ReportUtils.getPersonalDetailsForAccountID(receiverAccountID); + const receiverParticipant = participants?.find((participant) => participant?.accountID); + const receiverAccountID = receiverParticipant?.accountID ?? -1; + let receiver = ReportUtils.getPersonalDetailsForAccountID(receiverAccountID); + let optimisticPersonalDetailListAction = {}; // STEP 1: Get existing chat report OR build a new optimistic one let isNewChatReport = false; @@ -1640,10 +1651,10 @@ function getSendInvoiceInformation( chatReport = ReportUtils.buildOptimisticChatReport([receiverAccountID, currentUserAccountID], CONST.REPORT.DEFAULT_REPORT_NAME, CONST.REPORT.CHAT_TYPE.INVOICE, senderWorkspaceID); } - // STEP 3: Create a new optimistic invoice report. + // STEP 2: Create a new optimistic invoice report. const optimisticInvoiceReport = ReportUtils.buildOptimisticInvoiceReport(chatReport.reportID, senderWorkspaceID, receiverAccountID, receiver.displayName ?? '', amount, currency); - // STEP 2: Build optimistic receipt and transaction + // STEP 3: Build optimistic receipt and transaction const receiptObject: Receipt = {}; let filename; if (receipt?.source) { @@ -1671,7 +1682,21 @@ function getSendInvoiceInformation( const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(optimisticInvoiceReport.policyID, category); const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(optimisticInvoiceReport.policyID, tag); - // STEP 4: Build optimistic reportActions. + // STEP 4: Add optimistic personal details for participant + const shouldCreateOptimisticPersonalDetails = isNewChatReport && !allPersonalDetails[receiverAccountID]; + if (shouldCreateOptimisticPersonalDetails) { + receiver = { + accountID: receiverAccountID, + avatar: UserUtils.getDefaultAvatarURL(receiverAccountID), + displayName: LocalePhoneNumber.formatPhoneNumber(receiverParticipant?.login ?? ''), + login: receiverParticipant?.login, + isOptimisticPersonalDetail: true, + }; + + optimisticPersonalDetailListAction = {[receiverAccountID]: receiver}; + } + + // STEP 5: Build optimistic reportActions. let inviteReportAction: OptimisticInviteReportAction | undefined; const [optimisticCreatedActionForChat, optimisticCreatedActionForIOUReport, iouAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] = ReportUtils.buildOptimisticMoneyRequestEntities( @@ -1694,7 +1719,7 @@ function getSendInvoiceInformation( } const reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, optimisticInvoiceReport, trimmedComment, optimisticTransaction); - // STEP 4: Build Onyx Data + // STEP 6: Build Onyx Data const [optimisticData, successData, failureData] = buildOnyxDataForInvoice( chatReport, optimisticInvoiceReport, @@ -1702,6 +1727,7 @@ function getSendInvoiceInformation( optimisticCreatedActionForChat, optimisticCreatedActionForIOUReport, iouAction, + optimisticPersonalDetailListAction, reportPreviewAction, optimisticPolicyRecentlyUsedCategories, optimisticPolicyRecentlyUsedTags, @@ -1717,12 +1743,12 @@ function getSendInvoiceInformation( return { senderWorkspaceID, receiver, - optimisticInvoiceRoomID: chatReport.reportID, - optimisticCreatedChatReportActionID: optimisticCreatedActionForChat.reportActionID, - optimisticInvoiceReportID: optimisticInvoiceReport.reportID, - optimisticReportPreviewReportActionID: reportPreviewAction.reportActionID, - optimisticTransactionID: optimisticTransaction.transactionID, - optimisticTransactionThreadReportID: optimisticTransactionThread.reportID, + invoiceRoomID: chatReport.reportID, + createdChatReportActionID: optimisticCreatedActionForChat.reportActionID, + invoiceReportID: optimisticInvoiceReport.reportID, + reportPreviewReportActionID: reportPreviewAction.reportActionID, + transactionID: optimisticTransaction.transactionID, + transactionThreadReportID: optimisticTransactionThread.reportID, onyxData: { optimisticData, successData, @@ -3327,17 +3353,8 @@ function sendInvoice( policyTagList?: OnyxEntry, policyCategories?: OnyxEntry, ) { - const { - senderWorkspaceID, - receiver, - optimisticInvoiceRoomID, - optimisticCreatedChatReportActionID, - optimisticInvoiceReportID, - optimisticReportPreviewReportActionID, - optimisticTransactionID, - optimisticTransactionThreadReportID, - onyxData, - } = getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories); + const {senderWorkspaceID, receiver, invoiceRoomID, createdChatReportActionID, invoiceReportID, reportPreviewReportActionID, transactionID, transactionThreadReportID, onyxData} = + getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories); let parameters: SendInvoiceParams = { senderWorkspaceID, @@ -3347,33 +3364,31 @@ function sendInvoice( merchant: transaction?.merchant ?? '', category: transaction?.category, date: transaction?.created ?? '', - optimisticInvoiceRoomID, - optimisticCreatedChatReportActionID, - optimisticInvoiceReportID, - optimisticReportPreviewReportActionID, - optimisticTransactionID, - optimisticTransactionThreadReportID, + invoiceRoomID, + createdChatReportActionID, + invoiceReportID, + reportPreviewReportActionID, + transactionID, + transactionThreadReportID, }; - if (!invoiceChatReport) { + if (invoiceChatReport) { parameters = { ...parameters, - receiverEmail: receiver.login, + receiverInvoiceRoomID: invoiceChatReport.reportID, }; - } - - if (!transaction?.isFromGlobalCreate && invoiceChatReport) { + } else { parameters = { ...parameters, - receiverInvoiceRoomID: invoiceChatReport.reportID, + receiverEmail: receiver.login, }; } API.write(WRITE_COMMANDS.SEND_INVOICE, parameters, onyxData); resetMoneyRequestInfo(); - Navigation.dismissModal(optimisticInvoiceRoomID); - Report.notifyNewAction(optimisticInvoiceRoomID, receiver.accountID); + Navigation.dismissModal(invoiceRoomID); + Report.notifyNewAction(invoiceRoomID, receiver.accountID); } /** From 695720dc7ce99ef7b8af8bfbdf84220b26ebcfef Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 18 Apr 2024 14:37:35 +0700 Subject: [PATCH 175/661] revert mistake change --- src/languages/en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 24ef3eee3215..33c9180d0f43 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2318,7 +2318,7 @@ export default { roomMembersPage: { memberNotFound: 'Member not found. To invite a new member to the room, please use the Invite button above.', notAuthorized: `You do not have access to this page. Are you trying to join the room? Please reach out to a member of this room so they can add you as a member! Something else? Reach out to ${CONST.EMAIL.CONCIERGE}`, - removeMembersPrompt: 'Are you sure you want to remove the selecmoneyRequestConfirmationListted members from the room?', + removeMembersPrompt: 'Are you sure you want to remove the selected members from the room?', }, newTaskPage: { assignTask: 'Assign task', From 8e6e602501eb9a8d550db3dcb88bfcfff79dfb49 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Thu, 18 Apr 2024 15:09:15 +0700 Subject: [PATCH 176/661] set onfido web full height --- src/components/Onfido/index.css | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/Onfido/index.css b/src/components/Onfido/index.css index 53f7888fc385..66fe571af9cc 100644 --- a/src/components/Onfido/index.css +++ b/src/components/Onfido/index.css @@ -56,10 +56,13 @@ height: 92% !important; } - /* - * Solves issue with height not working for `onfido-sdk-ui-Modal-inner` container when device width is in between 490 - 600pixels. - */ - #onfido-mount { - height: 100%; - } +} + +#onfido-mount { + height: 100%; +} + +#onfido-sdk { + min-height: initial !important; + max-height: initial !important; } \ No newline at end of file From f19a19cf96d7dcc0bf3e3c076dc1bf94fc55847b Mon Sep 17 00:00:00 2001 From: VickyStash Date: Thu, 18 Apr 2024 10:13:21 +0200 Subject: [PATCH 177/661] Add SendInvoiceInformation type --- src/libs/actions/IOU.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index fc4877c80a74..c66a8da736e4 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -95,6 +95,18 @@ type TrackExpenseInformation = { onyxData: OnyxData; }; +type SendInvoiceInformation = { + senderWorkspaceID: string; + receiver: Partial; + invoiceRoomID: string; + createdChatReportActionID: string; + invoiceReportID: string; + reportPreviewReportActionID: string; + transactionID: string; + transactionThreadReportID: string; + onyxData: OnyxData; +}; + type SplitData = { chatReportID: string; transactionID: string; @@ -1627,7 +1639,7 @@ function getSendInvoiceInformation( policy?: OnyxEntry, policyTagList?: OnyxEntry, policyCategories?: OnyxEntry, -) { +): SendInvoiceInformation { const {amount = 0, currency = '', created = '', merchant = '', category = '', tag = '', billable, comment, participants} = transaction ?? {}; const trimmedComment = (comment?.comment ?? '').trim(); const senderWorkspaceID = participants?.find((participant) => participant?.policyID)?.policyID ?? ''; From 693bd94afbae232f71b2acb67334200e0f6e9c0b Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Thu, 18 Apr 2024 14:07:32 +0200 Subject: [PATCH 178/661] add disconnection logic before connecting to another integration --- .../ConnectToXeroButton/index.native.tsx | 17 +++++++- src/components/ConnectToXeroButton/index.tsx | 39 +++++++++++++++---- src/components/ConnectToXeroButton/types.ts | 3 ++ src/languages/en.ts | 14 ++++++- .../accounting/WorkspaceAccountingPage.tsx | 26 +++++++------ .../qboConnectionButton/index.native.tsx | 25 +++++++++++- .../accounting/qboConnectionButton/index.tsx | 39 +++++++++++++++---- .../accounting/qboConnectionButton/types.ts | 3 ++ 8 files changed, 134 insertions(+), 32 deletions(-) diff --git a/src/components/ConnectToXeroButton/index.native.tsx b/src/components/ConnectToXeroButton/index.native.tsx index ab00b5b0e5f2..b3db831183df 100644 --- a/src/components/ConnectToXeroButton/index.native.tsx +++ b/src/components/ConnectToXeroButton/index.native.tsx @@ -3,6 +3,7 @@ import {withOnyx} from 'react-native-onyx'; import WebView from 'react-native-webview'; import type {WebViewNavigation} from 'react-native-webview'; import Button from '@components/Button'; +import ConfirmModal from '@components/ConfirmModal'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Modal from '@components/Modal'; import useLocalize from '@hooks/useLocalize'; @@ -13,7 +14,7 @@ import type {ConnectToXeroButtonOnyxProps, ConnectToXeroButtonProps} from './typ type WebViewNavigationEvent = WebViewNavigation; -function ConnectToXeroButton({policyID, session}: ConnectToXeroButtonProps) { +function ConnectToXeroButton({policyID, session, disconnectIntegrationBeforeConnecting, integrationToConnect}: ConnectToXeroButtonProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const webViewRef = useRef(null); @@ -28,6 +29,8 @@ function ConnectToXeroButton({policyID, session}: ConnectToXeroButtonProps) { */ const handleNavigationStateChange = useCallback(({url}: WebViewNavigationEvent) => !!url, []); + const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false); + return ( <>