From cd8ca07937025d621e7fb6802526e2a64b08aff5 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 7 Oct 2023 12:53:52 +0530 Subject: [PATCH 01/57] Convert StatePicker modal to StateSelection page --- src/ROUTES.ts | 16 ++ .../StatePicker/StateSelectorModal.js | 112 -------------- src/components/StatePicker/index.js | 97 ------------- src/components/StateSelector.js | 87 +++++++++++ .../AppNavigator/ModalStackNavigators.js | 1 + src/libs/Navigation/linkingConfig.js | 4 + src/libs/getStateFromRoute.ts | 13 ++ src/pages/ReimbursementAccount/AddressForm.js | 4 +- src/pages/ReimbursementAccount/CompanyStep.js | 23 ++- .../ReimbursementAccountPage.js | 1 + .../Profile/PersonalDetails/AddressPage.js | 22 ++- .../PersonalDetails/StateSelectionPage.js | 137 ++++++++++++++++++ src/pages/settings/Wallet/AddDebitCardPage.js | 19 ++- src/stories/Form.stories.js | 6 +- 14 files changed, 317 insertions(+), 225 deletions(-) delete mode 100644 src/components/StatePicker/StateSelectorModal.js delete mode 100644 src/components/StatePicker/index.js create mode 100644 src/components/StateSelector.js create mode 100644 src/libs/getStateFromRoute.ts create mode 100644 src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b2dafa643b2..642e8ec8a2d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -94,6 +94,22 @@ export default { return route; }, }, + SETTINGS_PERSONAL_DETAILS_ADDRESS_STATE: { + route: 'settings/profile/personal-details/address/state', + getRoute: (state: string, backTo?: string, label?: string, stateParamName = 'state') => { + let route = `settings/profile/personal-details/address/state?state=${state}`; + if (backTo) { + route += `&backTo=${encodeURIComponent(backTo)}`; + } + if (label) { + route += `&label=${encodeURIComponent(label)}`; + } + if (stateParamName) { + route += `&stateParamName=${encodeURIComponent(stateParamName)}`; + } + return route; + }, + }, SETTINGS_CONTACT_METHODS: 'settings/profile/contact-methods', SETTINGS_CONTACT_METHOD_DETAILS: { route: 'settings/profile/contact-methods/:contactMethod/details', diff --git a/src/components/StatePicker/StateSelectorModal.js b/src/components/StatePicker/StateSelectorModal.js deleted file mode 100644 index 378dcc4ebc8..00000000000 --- a/src/components/StatePicker/StateSelectorModal.js +++ /dev/null @@ -1,112 +0,0 @@ -import _ from 'underscore'; -import React, {useMemo, useEffect} from 'react'; -import PropTypes from 'prop-types'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import CONST from '../../CONST'; -import Modal from '../Modal'; -import HeaderWithBackButton from '../HeaderWithBackButton'; -import SelectionList from '../SelectionList'; -import useLocalize from '../../hooks/useLocalize'; -import ScreenWrapper from '../ScreenWrapper'; -import styles from '../../styles/styles'; -import searchCountryOptions from '../../libs/searchCountryOptions'; -import StringUtils from '../../libs/StringUtils'; - -const propTypes = { - /** Whether the modal is visible */ - isVisible: PropTypes.bool.isRequired, - - /** State value selected */ - currentState: PropTypes.string, - - /** Function to call when the user selects a State */ - onStateSelected: PropTypes.func, - - /** Function to call when the user closes the State modal */ - onClose: PropTypes.func, - - /** The search value from the selection list */ - searchValue: PropTypes.string.isRequired, - - /** Function to call when the user types in the search input */ - setSearchValue: PropTypes.func.isRequired, - - /** Label to display on field */ - label: PropTypes.string, -}; - -const defaultProps = { - currentState: '', - onClose: () => {}, - onStateSelected: () => {}, - label: undefined, -}; - -function StateSelectorModal({currentState, isVisible, onClose, onStateSelected, searchValue, setSearchValue, label}) { - const {translate} = useLocalize(); - - useEffect(() => { - if (isVisible) { - return; - } - setSearchValue(''); - }, [isVisible, setSearchValue]); - - const countryStates = useMemo( - () => - _.map(_.keys(COMMON_CONST.STATES), (state) => { - const stateName = translate(`allStates.${state}.stateName`); - const stateISO = translate(`allStates.${state}.stateISO`); - return { - value: stateISO, - keyForList: stateISO, - text: stateName, - isSelected: currentState === stateISO, - searchValue: StringUtils.sanitizeString(`${stateISO}${stateName}`), - }; - }), - [translate, currentState], - ); - - const searchResults = searchCountryOptions(searchValue, countryStates); - const headerMessage = searchValue.trim() && !searchResults.length ? translate('common.noResultsFound') : ''; - - return ( - - - - - - - ); -} - -StateSelectorModal.propTypes = propTypes; -StateSelectorModal.defaultProps = defaultProps; -StateSelectorModal.displayName = 'StateSelectorModal'; - -export default StateSelectorModal; diff --git a/src/components/StatePicker/index.js b/src/components/StatePicker/index.js deleted file mode 100644 index f7f894af2a0..00000000000 --- a/src/components/StatePicker/index.js +++ /dev/null @@ -1,97 +0,0 @@ -import React, {useState} from 'react'; -import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import styles from '../../styles/styles'; -import MenuItemWithTopDescription from '../MenuItemWithTopDescription'; -import useLocalize from '../../hooks/useLocalize'; -import FormHelpMessage from '../FormHelpMessage'; -import StateSelectorModal from './StateSelectorModal'; -import refPropTypes from '../refPropTypes'; - -const propTypes = { - /** Error text to display */ - errorText: PropTypes.string, - - /** State to display */ - value: PropTypes.string, - - /** Callback to call when the input changes */ - onInputChange: PropTypes.func, - - /** A ref to forward to MenuItemWithTopDescription */ - forwardedRef: refPropTypes, - - /** Label to display on field */ - label: PropTypes.string, -}; - -const defaultProps = { - value: undefined, - forwardedRef: undefined, - errorText: '', - onInputChange: () => {}, - label: undefined, -}; - -function StatePicker({value, errorText, onInputChange, forwardedRef, label}) { - const {translate} = useLocalize(); - const [isPickerVisible, setIsPickerVisible] = useState(false); - const [searchValue, setSearchValue] = useState(''); - - const showPickerModal = () => { - setIsPickerVisible(true); - }; - - const hidePickerModal = () => { - setIsPickerVisible(false); - }; - - const updateStateInput = (state) => { - if (state.value !== value) { - onInputChange(state.value); - } - hidePickerModal(); - }; - - const title = value && _.keys(COMMON_CONST.STATES).includes(value) ? translate(`allStates.${value}.stateName`) : ''; - const descStyle = title.length === 0 ? styles.textNormal : null; - - return ( - - - - - - - - ); -} - -StatePicker.propTypes = propTypes; -StatePicker.defaultProps = defaultProps; -StatePicker.displayName = 'StatePicker'; - -export default React.forwardRef((props, ref) => ( - -)); diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js new file mode 100644 index 00000000000..c004fb8bf37 --- /dev/null +++ b/src/components/StateSelector.js @@ -0,0 +1,87 @@ +import React, {useEffect} from 'react'; +import PropTypes from 'prop-types'; +import {View} from 'react-native'; +import _ from 'underscore'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; +import styles from '../styles/styles'; +import Navigation from '../libs/Navigation/Navigation'; +import ROUTES from '../ROUTES'; +import useLocalize from '../hooks/useLocalize'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; +import FormHelpMessage from './FormHelpMessage'; + +const propTypes = { + /** Form error text. e.g when no country is selected */ + errorText: PropTypes.string, + + /** Callback called when the country changes. */ + onInputChange: PropTypes.func.isRequired, + + /** Current selected country */ + value: PropTypes.string, + + /** inputID used by the Form component */ + // eslint-disable-next-line react/no-unused-prop-types + inputID: PropTypes.string.isRequired, + + /** React ref being forwarded to the MenuItemWithTopDescription */ + forwardedRef: PropTypes.func, + + /** Label of state in the url */ + paramName: PropTypes.string, + + /** Label to display on field */ + label: PropTypes.string, +}; + +const defaultProps = { + errorText: '', + value: undefined, + forwardedRef: () => {}, + label: undefined, + paramName: 'state', +}; + +function StateSelector({errorText, value: stateCode, label, paramName, onInputChange, forwardedRef}) { + const {translate} = useLocalize(); + + const title = stateCode && _.keys(COMMON_CONST.STATES).includes(stateCode) ? translate(`allStates.${stateCode}.stateName`) : ''; + const descStyle = title.length === 0 ? styles.textNormal : null; + + useEffect(() => { + // This will cause the form to revalidate and remove any error related to country name + onInputChange(stateCode); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [stateCode]); + + return ( + + { + const activeRoute = Navigation.getActiveRoute(); + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_STATE.getRoute(stateCode, activeRoute, label, paramName)); + }} + /> + + + + + ); +} + +StateSelector.propTypes = propTypes; +StateSelector.defaultProps = defaultProps; +StateSelector.displayName = 'StateSelector'; + +export default React.forwardRef((props, ref) => ( + +)); diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 6636702592c..f682d438f27 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -127,6 +127,7 @@ const SettingsModalStackNavigator = createModalStackNavigator({ Settings_PersonalDetails_DateOfBirth: () => require('../../../pages/settings/Profile/PersonalDetails/DateOfBirthPage').default, Settings_PersonalDetails_Address: () => require('../../../pages/settings/Profile/PersonalDetails/AddressPage').default, Settings_PersonalDetails_Address_Country: () => require('../../../pages/settings/Profile/PersonalDetails/CountrySelectionPage').default, + Settings_PersonalDetails_Address_State: () => require('../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default, Settings_ContactMethods: () => require('../../../pages/settings/Profile/Contacts/ContactMethodsPage').default, Settings_ContactMethodDetails: () => require('../../../pages/settings/Profile/Contacts/ContactMethodDetailsPage').default, Settings_NewContactMethod: () => require('../../../pages/settings/Profile/Contacts/NewContactMethodPage').default, diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index bf069aba314..1b39987e725 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -155,6 +155,10 @@ export default { path: ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_COUNTRY.route, exact: true, }, + Settings_PersonalDetails_Address_State: { + path: ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_STATE.route, + exact: true, + }, Settings_TwoFactorAuth: { path: ROUTES.SETTINGS_2FA, exact: true, diff --git a/src/libs/getStateFromRoute.ts b/src/libs/getStateFromRoute.ts new file mode 100644 index 00000000000..ff01068eae3 --- /dev/null +++ b/src/libs/getStateFromRoute.ts @@ -0,0 +1,13 @@ +// eslint-disable-next-line you-dont-need-lodash-underscore/get +import lodashGet from 'lodash/get'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; + +type RouteProps = { + params?: Record; +}; + +export default function getStateFromRoute(route: RouteProps, stateParamName = 'state'): string { + const stateFromUrlTemp = lodashGet(route, `params.${stateParamName}`) as unknown as string; + // check if state is valid + return lodashGet(COMMON_CONST.STATES, stateFromUrlTemp) ? stateFromUrlTemp : ''; +} diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js index d8fbc029013..09e365b58c6 100644 --- a/src/pages/ReimbursementAccount/AddressForm.js +++ b/src/pages/ReimbursementAccount/AddressForm.js @@ -5,7 +5,7 @@ import TextInput from '../../components/TextInput'; import AddressSearch from '../../components/AddressSearch'; import styles from '../../styles/styles'; import CONST from '../../CONST'; -import StatePicker from '../../components/StatePicker'; +import StateSelector from '../../components/StateSelector'; const propTypes = { /** Translate key for Street name */ @@ -123,7 +123,7 @@ function AddressForm(props) { /> - - lodashGet(privatePersonalDetails, 'address') || {}, [privatePersonalDetails]); - const countryFromUrl = lodashGet(route, 'params.country'); + + const countryFromUrlTemp = lodashGet(route, 'params.country'); + // check if country is valid + const countryFromUrl = lodashGet(CONST.ALL_COUNTRIES, countryFromUrlTemp) ? countryFromUrlTemp : ''; + + const stateFromUrl = getStateFromRoute(route); const [currentCountry, setCurrentCountry] = useState(address.country); const zipSampleFormat = lodashGet(CONST.COUNTRY_ZIP_REGEX_DATA, [currentCountry, 'samples'], ''); const zipFormat = translate('common.zipCodeExampleFormat', {zipSampleFormat}); @@ -160,6 +166,13 @@ function AddressPage({privatePersonalDetails, route}) { handleAddressChange(countryFromUrl, 'country'); }, [countryFromUrl, handleAddressChange, currentCountry]); + useEffect(() => { + if (!stateFromUrl || stateFromUrl === state) { + return; + } + handleAddressChange(stateFromUrl, 'state'); + }, [state, handleAddressChange, stateFromUrl]); + return ( {isUSAForm ? ( - ) : ( diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js new file mode 100644 index 00000000000..34364421b4a --- /dev/null +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js @@ -0,0 +1,137 @@ +import React, {useState, useMemo, useCallback} from 'react'; +import PropTypes from 'prop-types'; +import _ from 'underscore'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; +import lodashGet from 'lodash/get'; +import Navigation from '../../../../libs/Navigation/Navigation'; +import ScreenWrapper from '../../../../components/ScreenWrapper'; +import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; +import SelectionList from '../../../../components/SelectionList'; +import searchCountryOptions from '../../../../libs/searchCountryOptions'; +import StringUtils from '../../../../libs/StringUtils'; +import useLocalize from '../../../../hooks/useLocalize'; +import styles from '../../../../styles/styles'; + +const propTypes = { + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** Currently selected country */ + country: PropTypes.string, + + /** Route to navigate back after selecting a currency */ + backTo: PropTypes.string, + }), + }).isRequired, + + /** Navigation from react-navigation */ + navigation: PropTypes.shape({ + /** getState function retrieves the current navigation state from react-navigation's navigation property */ + getState: PropTypes.func.isRequired, + }).isRequired, +}; + +/** + * Appends or updates a query parameter in a given URL. + * + * @param {string} url - The original URL. + * @param {string} paramName - The name of the query parameter to append or update. + * @param {string} paramValue - The value of the query parameter to append or update. + * @returns {string} The updated URL with the appended or updated query parameter. + */ +function appendParam(url, paramName, paramValue) { + if (url.includes(`${paramName}=`)) { + // If parameter exists, replace it + const regex = new RegExp(`${paramName}=([^&]*)`); + return url.replace(regex, `${paramName}=${paramValue}`); + } + // If parameter doesn't exist, append it + const separator = url.includes('?') ? '&' : '?'; + return `${url}${separator}${paramName}=${paramValue}`; +} + +function StateSelectionPage({route, navigation}) { + const [searchValue, setSearchValue] = useState(''); + const {translate} = useLocalize(); + const currentState = lodashGet(route, 'params.state'); + const stateParamName = lodashGet(route, 'params.stateParamName') || 'state'; + const label = lodashGet(route, 'params.label'); + + const countryStates = useMemo( + () => + _.map(_.keys(COMMON_CONST.STATES), (state) => { + const stateName = translate(`allStates.${state}.stateName`); + const stateISO = translate(`allStates.${state}.stateISO`); + return { + value: stateISO, + keyForList: stateISO, + text: stateName, + isSelected: currentState === stateISO, + searchValue: StringUtils.sanitizeString(`${stateISO}${stateName}`), + }; + }), + [translate, currentState], + ); + + const searchResults = searchCountryOptions(searchValue, countryStates); + const headerMessage = searchValue.trim() && !searchResults.length ? translate('common.noResultsFound') : ''; + + const selectCountryState = useCallback( + (option) => { + const backTo = lodashGet(route, 'params.backTo', ''); + + // Check the navigation state and "backTo" parameter to decide navigation behavior + if (navigation.getState().routes.length === 1 && _.isEmpty(backTo)) { + // If there is only one route and "backTo" is empty, go back in navigation + Navigation.goBack(); + } else if (!_.isEmpty(backTo) && navigation.getState().routes.length === 1) { + // If "backTo" is not empty and there is only one route, go back to the specific route defined in "backTo" with a country parameter + Navigation.goBack(appendParam(backTo, stateParamName, option.value)); + } else { + // Otherwise, navigate to the specific route defined in "backTo" with a country parameter + Navigation.navigate(appendParam(backTo, stateParamName, option.value)); + } + }, + [route, navigation, stateParamName], + ); + + return ( + + { + const backTo = lodashGet(route, 'params.backTo', ''); + let backToRoute = ''; + + if (backTo) { + backToRoute = appendParam(backTo, stateParamName, currentState); + } + + Navigation.goBack(backToRoute); + }} + /> + + + + ); +} + +StateSelectionPage.displayName = 'StateSelectionPage'; +StateSelectionPage.propTypes = propTypes; + +export default StateSelectionPage; diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index e75c3b2c517..201a4e76369 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -11,7 +11,6 @@ import useLocalize from '../../../hooks/useLocalize'; import * as PaymentMethods from '../../../libs/actions/PaymentMethods'; import * as ValidationUtils from '../../../libs/ValidationUtils'; import CheckboxWithLabel from '../../../components/CheckboxWithLabel'; -import StatePicker from '../../../components/StatePicker'; import TextInput from '../../../components/TextInput'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; @@ -22,6 +21,8 @@ import ROUTES from '../../../ROUTES'; import usePrevious from '../../../hooks/usePrevious'; import NotFoundPage from '../../ErrorPage/NotFoundPage'; import Permissions from '../../../libs/Permissions'; +import StateSelector from '../../../components/StateSelector'; +import getStateFromRoute from '../../../libs/getStateFromRoute'; const propTypes = { /* Onyx Props */ @@ -31,6 +32,15 @@ const propTypes = { /** List of betas available to current user */ betas: PropTypes.arrayOf(PropTypes.string), + + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** Currently selected country */ + country: PropTypes.string, + }), + }).isRequired, }; const defaultProps = { @@ -104,6 +114,8 @@ function DebitCardPage(props) { return ; } + const stateFromUrl = getStateFromRoute(props.route); + return ( nameOnCardRef.current && nameOnCardRef.current.focus()} @@ -182,7 +194,10 @@ function DebitCardPage(props) { containerStyles={[styles.mt4]} /> - + From 1284e59cf00621f0012d38187e0738b1e7c3e8dc Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 7 Oct 2023 14:43:44 +0530 Subject: [PATCH 02/57] Incorporation state resets bugfix --- src/pages/ReimbursementAccount/CompanyStep.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index ef767489444..4bac76dbd16 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -262,7 +262,7 @@ function CompanyStep({reimbursementAccount, route, reimbursementAccountDraft, ge label={translate('companyStep.incorporationState')} shouldSaveDraft paramName="incorporationState" - value={incorporationState} + {...(incorporationState ? {value: incorporationState} : {})} /> Date: Sat, 7 Oct 2023 15:29:42 +0530 Subject: [PATCH 03/57] Modify pages to accomodate for StateSelection --- src/pages/EnablePayments/AdditionalDetailsStep.js | 15 ++++++++++++++- src/pages/EnablePayments/EnablePaymentsPage.js | 4 ++-- src/pages/ReimbursementAccount/ACHContractStep.js | 13 +++++++++++++ src/pages/ReimbursementAccount/CompanyStep.js | 4 ++-- .../ReimbursementAccountPage.js | 2 ++ src/pages/ReimbursementAccount/RequestorStep.js | 15 ++++++++++++++- .../Profile/PersonalDetails/AddressPage.js | 3 +++ .../Profile/PersonalDetails/StateSelectionPage.js | 4 ++-- src/pages/settings/Wallet/AddDebitCardPage.js | 4 ++-- 9 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js index c304103f69c..f521346b5fc 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.js @@ -24,6 +24,7 @@ import Form from '../../components/Form'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../components/withCurrentUserPersonalDetails'; import * as PersonalDetails from '../../libs/actions/PersonalDetails'; import OfflineIndicator from '../../components/OfflineIndicator'; +import getStateFromRoute from '../../libs/getStateFromRoute'; const propTypes = { ...withLocalizePropTypes, @@ -55,6 +56,15 @@ const propTypes = { /** Error code to determine additional behavior */ errorCode: PropTypes.string, }), + + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** Currently selected state */ + state: PropTypes.string, + }), + }).isRequired, }; const defaultProps = { @@ -79,7 +89,7 @@ const fieldNameTranslationKeys = { ssnFull9: 'common.ssnFull9', }; -function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserPersonalDetails}) { +function AdditionalDetailsStep({walletAdditionalDetails, translate, route, currentUserPersonalDetails}) { const minDate = moment().subtract(CONST.DATE_BIRTH.MAX_AGE, 'Y').toDate(); const maxDate = moment().subtract(CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT, 'Y').toDate(); const shouldAskForFullSSN = walletAdditionalDetails.errorCode === CONST.WALLET.ERROR.SSN; @@ -163,6 +173,8 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP ); } + const state = getStateFromRoute(route); + return ( <> @@ -209,6 +221,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP state: 'addressState', zipCode: 'addressZipCode', }} + values={state ? {state} : {}} translate={translate} streetTranslationKey={fieldNameTranslationKeys.addressStreet} shouldSaveDraft diff --git a/src/pages/EnablePayments/EnablePaymentsPage.js b/src/pages/EnablePayments/EnablePaymentsPage.js index f7ef2a17420..8feb8342ac8 100644 --- a/src/pages/EnablePayments/EnablePaymentsPage.js +++ b/src/pages/EnablePayments/EnablePaymentsPage.js @@ -29,7 +29,7 @@ const defaultProps = { userWallet: {}, }; -function EnablePaymentsPage({userWallet}) { +function EnablePaymentsPage({userWallet, route}) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); @@ -72,7 +72,7 @@ function EnablePaymentsPage({userWallet}) { switch (currentStep) { case CONST.WALLET.STEP.ADDITIONAL_DETAILS: case CONST.WALLET.STEP.ADDITIONAL_DETAILS_KBA: - return ; + return ; case CONST.WALLET.STEP.ONFIDO: return ; case CONST.WALLET.STEP.TERMS: diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index 761be71d864..7209d6250a9 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -19,12 +19,22 @@ import Form from '../../components/Form'; import * as FormActions from '../../libs/actions/FormActions'; import ScreenWrapper from '../../components/ScreenWrapper'; import StepPropTypes from './StepPropTypes'; +import getStateFromRoute from '../../libs/getStateFromRoute'; const propTypes = { ...StepPropTypes, /** Name of the company */ companyName: PropTypes.string.isRequired, + + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** Currently selected state */ + state: PropTypes.string, + }), + }).isRequired, }; function ACHContractStep(props) { @@ -144,6 +154,8 @@ function ACHContractStep(props) { }); }; + const state = getStateFromRoute(props.route); + return ( { return errors; }; -function RequestorStep({reimbursementAccount, shouldShowOnfido, reimbursementAccountDraft, onBackButtonPress, getDefaultStateForField}) { +function RequestorStep({reimbursementAccount, route, shouldShowOnfido, reimbursementAccountDraft, onBackButtonPress, getDefaultStateForField}) { const {translate} = useLocalize(); const defaultValues = useMemo( @@ -118,6 +128,8 @@ function RequestorStep({reimbursementAccount, shouldShowOnfido, reimbursementAcc ); } + const state = getStateFromRoute(route); + return ( Date: Sun, 8 Oct 2023 08:24:10 +0530 Subject: [PATCH 04/57] Refactor to use useGeographicalStateFromRoute hook --- src/hooks/useGeographicalStateFromRoute.ts | 7 +++++++ src/libs/getStateFromRoute.ts | 3 ++- .../EnablePayments/AdditionalDetailsStep.js | 17 ++++------------- src/pages/EnablePayments/EnablePaymentsPage.js | 4 ++-- .../ReimbursementAccount/ACHContractStep.js | 13 ++----------- src/pages/ReimbursementAccount/CompanyStep.js | 18 +++++------------- .../ReimbursementAccountPage.js | 3 --- .../ReimbursementAccount/RequestorStep.js | 17 ++++------------- .../Profile/PersonalDetails/AddressPage.js | 7 ++----- src/pages/settings/Wallet/AddDebitCardPage.js | 15 +++------------ 10 files changed, 31 insertions(+), 73 deletions(-) create mode 100644 src/hooks/useGeographicalStateFromRoute.ts diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts new file mode 100644 index 00000000000..33866e8b0cf --- /dev/null +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -0,0 +1,7 @@ +import {useRoute} from '@react-navigation/native'; +import getStateFromRoute from '../libs/getStateFromRoute'; + +export default function useGeographicalStateFromRoute(stateParamName = 'state') { + const route = useRoute(); + return getStateFromRoute(route, stateParamName); +} diff --git a/src/libs/getStateFromRoute.ts b/src/libs/getStateFromRoute.ts index ff01068eae3..19d6de46014 100644 --- a/src/libs/getStateFromRoute.ts +++ b/src/libs/getStateFromRoute.ts @@ -3,7 +3,8 @@ import lodashGet from 'lodash/get'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; type RouteProps = { - params?: Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params?: Record; }; export default function getStateFromRoute(route: RouteProps, stateParamName = 'state'): string { diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js index 1d869133306..5ab59899b13 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.js @@ -24,7 +24,7 @@ import Form from '../../components/Form'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../components/withCurrentUserPersonalDetails'; import * as PersonalDetails from '../../libs/actions/PersonalDetails'; import OfflineIndicator from '../../components/OfflineIndicator'; -import getStateFromRoute from '../../libs/getStateFromRoute'; +import useGeographicalStateFromRoute from '../../hooks/useGeographicalStateFromRoute'; const propTypes = { ...withLocalizePropTypes, @@ -56,15 +56,6 @@ const propTypes = { /** Error code to determine additional behavior */ errorCode: PropTypes.string, }), - - /** Route from navigation */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** Currently selected state */ - state: PropTypes.string, - }), - }).isRequired, }; const defaultProps = { @@ -89,7 +80,7 @@ const fieldNameTranslationKeys = { ssnFull9: 'common.ssnFull9', }; -function AdditionalDetailsStep({walletAdditionalDetails, route, translate, currentUserPersonalDetails}) { +function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserPersonalDetails}) { const currentDate = new Date(); const minDate = subYears(currentDate, CONST.DATE_BIRTH.MAX_AGE); const maxDate = subYears(currentDate, CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); @@ -155,6 +146,8 @@ function AdditionalDetailsStep({walletAdditionalDetails, route, translate, curre Wallet.updatePersonalDetails(personalDetails); }; + const state = useGeographicalStateFromRoute(); + if (!_.isEmpty(walletAdditionalDetails.questions)) { return ( diff --git a/src/pages/EnablePayments/EnablePaymentsPage.js b/src/pages/EnablePayments/EnablePaymentsPage.js index 8feb8342ac8..f7ef2a17420 100644 --- a/src/pages/EnablePayments/EnablePaymentsPage.js +++ b/src/pages/EnablePayments/EnablePaymentsPage.js @@ -29,7 +29,7 @@ const defaultProps = { userWallet: {}, }; -function EnablePaymentsPage({userWallet, route}) { +function EnablePaymentsPage({userWallet}) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); @@ -72,7 +72,7 @@ function EnablePaymentsPage({userWallet, route}) { switch (currentStep) { case CONST.WALLET.STEP.ADDITIONAL_DETAILS: case CONST.WALLET.STEP.ADDITIONAL_DETAILS_KBA: - return ; + return ; case CONST.WALLET.STEP.ONFIDO: return ; case CONST.WALLET.STEP.TERMS: diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index 7209d6250a9..b619abd68a3 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -19,22 +19,13 @@ import Form from '../../components/Form'; import * as FormActions from '../../libs/actions/FormActions'; import ScreenWrapper from '../../components/ScreenWrapper'; import StepPropTypes from './StepPropTypes'; -import getStateFromRoute from '../../libs/getStateFromRoute'; +import useGeographicalStateFromRoute from '../../hooks/useGeographicalStateFromRoute'; const propTypes = { ...StepPropTypes, /** Name of the company */ companyName: PropTypes.string.isRequired, - - /** Route from navigation */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** Currently selected state */ - state: PropTypes.string, - }), - }).isRequired, }; function ACHContractStep(props) { @@ -154,7 +145,7 @@ function ACHContractStep(props) { }); }; - const state = getStateFromRoute(props.route); + const state = useGeographicalStateFromRoute(); return ( diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 2dc71f7b80f..c6f53c2474f 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -429,7 +429,6 @@ class ReimbursementAccountPage extends React.Component { if (currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY) { return ( { return errors; }; -function RequestorStep({reimbursementAccount, route, shouldShowOnfido, reimbursementAccountDraft, onBackButtonPress, getDefaultStateForField}) { +function RequestorStep({reimbursementAccount, shouldShowOnfido, reimbursementAccountDraft, onBackButtonPress, getDefaultStateForField}) { const {translate} = useLocalize(); const defaultValues = useMemo( @@ -117,6 +108,8 @@ function RequestorStep({reimbursementAccount, route, shouldShowOnfido, reimburse ); + const state = useGeographicalStateFromRoute(); + if (shouldShowOnfido) { return ( ; } - const stateFromUrl = getStateFromRoute(props.route); - return ( nameOnCardRef.current && nameOnCardRef.current.focus()} From 0f83cff67014fc53ffa0d005247a0cc29f1a8a3f Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 28 Oct 2023 02:35:21 +0530 Subject: [PATCH 05/57] move getStateFromRoute to useGeographicalStateFromRoute --- src/hooks/useGeographicalStateFromRoute.ts | 8 ++++++-- src/libs/getStateFromRoute.ts | 14 -------------- 2 files changed, 6 insertions(+), 16 deletions(-) delete mode 100644 src/libs/getStateFromRoute.ts diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index 33866e8b0cf..3726499edf8 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -1,7 +1,11 @@ import {useRoute} from '@react-navigation/native'; -import getStateFromRoute from '../libs/getStateFromRoute'; +// eslint-disable-next-line you-dont-need-lodash-underscore/get +import lodashGet from 'lodash/get'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; export default function useGeographicalStateFromRoute(stateParamName = 'state') { const route = useRoute(); - return getStateFromRoute(route, stateParamName); + const stateFromUrlTemp = lodashGet(route, `params.${stateParamName}`) as unknown as string; + // check if state is valid + return lodashGet(COMMON_CONST.STATES, stateFromUrlTemp) ? stateFromUrlTemp : ''; } diff --git a/src/libs/getStateFromRoute.ts b/src/libs/getStateFromRoute.ts deleted file mode 100644 index 19d6de46014..00000000000 --- a/src/libs/getStateFromRoute.ts +++ /dev/null @@ -1,14 +0,0 @@ -// eslint-disable-next-line you-dont-need-lodash-underscore/get -import lodashGet from 'lodash/get'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; - -type RouteProps = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params?: Record; -}; - -export default function getStateFromRoute(route: RouteProps, stateParamName = 'state'): string { - const stateFromUrlTemp = lodashGet(route, `params.${stateParamName}`) as unknown as string; - // check if state is valid - return lodashGet(COMMON_CONST.STATES, stateFromUrlTemp) ? stateFromUrlTemp : ''; -} From 0951d5d75b56a38009fe7bbd91d576bbd976cb25 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 28 Oct 2023 02:46:35 +0530 Subject: [PATCH 06/57] Add displayname to StateSelectorWithRef --- src/components/StateSelector.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index c004fb8bf37..ba770580298 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -78,10 +78,14 @@ StateSelector.propTypes = propTypes; StateSelector.defaultProps = defaultProps; StateSelector.displayName = 'StateSelector'; -export default React.forwardRef((props, ref) => ( +const StateSelectorWithRef = React.forwardRef((props, ref) => ( )); + +StateSelectorWithRef.displayName = 'StateSelectorWithRef'; + +export default StateSelectorWithRef; From 598c7c6c3378fe402e3b811a83f70b4cf16c38de Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 28 Oct 2023 03:57:15 +0530 Subject: [PATCH 07/57] Fix AddressPage: State not changing on autocomplete --- src/pages/settings/Profile/PersonalDetails/AddressPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index 1eec60d7d57..112372abebc 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -167,11 +167,11 @@ function AddressPage({privatePersonalDetails, route}) { }, [countryFromUrl, handleAddressChange]); useEffect(() => { - if (!stateFromUrl || stateFromUrl === state) { + if (!stateFromUrl) { return; } handleAddressChange(stateFromUrl, 'state'); - }, [state, handleAddressChange, stateFromUrl]); + }, [handleAddressChange, stateFromUrl]); return ( Date: Sat, 28 Oct 2023 04:56:23 +0530 Subject: [PATCH 08/57] Refactor StateSelector logic --- src/components/StateSelector.js | 30 +++++++++++++++---- .../EnablePayments/AdditionalDetailsStep.js | 4 --- .../ReimbursementAccount/ACHContractStep.js | 4 --- src/pages/ReimbursementAccount/CompanyStep.js | 3 +- .../ReimbursementAccount/RequestorStep.js | 4 --- src/pages/settings/Wallet/AddDebitCardPage.js | 8 +---- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index ba770580298..52df1f61d7e 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React, {useEffect, useState} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import _ from 'underscore'; @@ -9,6 +9,7 @@ import ROUTES from '../ROUTES'; import useLocalize from '../hooks/useLocalize'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import FormHelpMessage from './FormHelpMessage'; +import useGeographicalStateFromRoute from '../hooks/useGeographicalStateFromRoute'; const propTypes = { /** Form error text. e.g when no country is selected */ @@ -32,6 +33,9 @@ const propTypes = { /** Label to display on field */ label: PropTypes.string, + + /** whether to use state from url */ + useStateFromUrl: PropTypes.bool, }; const defaultProps = { @@ -40,20 +44,36 @@ const defaultProps = { forwardedRef: () => {}, label: undefined, paramName: 'state', + useStateFromUrl: true, }; -function StateSelector({errorText, value: stateCode, label, paramName, onInputChange, forwardedRef}) { +function StateSelector({errorText, useStateFromUrl, value: stateCode, label, paramName, onInputChange, forwardedRef}) { const {translate} = useLocalize(); - const title = stateCode && _.keys(COMMON_CONST.STATES).includes(stateCode) ? translate(`allStates.${stateCode}.stateName`) : ''; - const descStyle = title.length === 0 ? styles.textNormal : null; + const stateFromUrl = useGeographicalStateFromRoute(paramName); + + const [stateToDisplay, setStateToDisplay] = useState(''); + + useEffect(() => { + if (useStateFromUrl) { + // This will cause the form to revalidate and remove any error related to country name + stateFromUrl && onInputChange(stateFromUrl); + stateFromUrl && setStateToDisplay(stateFromUrl); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [stateFromUrl, useStateFromUrl]); useEffect(() => { // This will cause the form to revalidate and remove any error related to country name - onInputChange(stateCode); + stateCode && onInputChange(stateCode); + stateCode && setStateToDisplay(stateCode); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [stateCode]); + const title = stateToDisplay && _.keys(COMMON_CONST.STATES).includes(stateToDisplay) ? translate(`allStates.${stateToDisplay}.stateName`) : ''; + const descStyle = title.length === 0 ? styles.textNormal : null; + return ( diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index 875c884420a..86f1d5077ec 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -17,7 +17,6 @@ import Form from '../../components/Form'; import ScreenWrapper from '../../components/ScreenWrapper'; import useLocalize from '../../hooks/useLocalize'; import {reimbursementAccountPropTypes} from './reimbursementAccountPropTypes'; -import useGeographicalStateFromRoute from '../../hooks/useGeographicalStateFromRoute'; const propTypes = { onBackButtonPress: PropTypes.func.isRequired, @@ -106,8 +105,6 @@ function RequestorStep({reimbursementAccount, shouldShowOnfido, onBackButtonPres ); - const state = useGeographicalStateFromRoute(); - if (shouldShowOnfido) { return ( ; } @@ -185,10 +182,7 @@ function DebitCardPage(props) { containerStyles={[styles.mt4]} /> - + Date: Sat, 28 Oct 2023 05:02:05 +0530 Subject: [PATCH 09/57] Refactor AddressPage --- src/pages/settings/Profile/PersonalDetails/AddressPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index 112372abebc..1e21a210dd4 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -232,6 +232,7 @@ function AddressPage({privatePersonalDetails, route}) { {isUSAForm ? ( From 8a293b5ceeaec50be8bcd7d262a097b46b0b783f Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 28 Oct 2023 05:34:30 +0530 Subject: [PATCH 10/57] linting fixes StateSelector --- src/components/StateSelector.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index 52df1f61d7e..51714252755 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -55,18 +55,21 @@ function StateSelector({errorText, useStateFromUrl, value: stateCode, label, par const [stateToDisplay, setStateToDisplay] = useState(''); useEffect(() => { - if (useStateFromUrl) { - // This will cause the form to revalidate and remove any error related to country name - stateFromUrl && onInputChange(stateFromUrl); - stateFromUrl && setStateToDisplay(stateFromUrl); - } + if (!useStateFromUrl || !stateFromUrl) return; + + // This will cause the form to revalidate and remove any error related to country name + onInputChange(stateFromUrl); + setStateToDisplay(stateFromUrl); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [stateFromUrl, useStateFromUrl]); useEffect(() => { + if (!stateCode) return; + // This will cause the form to revalidate and remove any error related to country name - stateCode && onInputChange(stateCode); - stateCode && setStateToDisplay(stateCode); + onInputChange(stateCode); + setStateToDisplay(stateCode); // eslint-disable-next-line react-hooks/exhaustive-deps }, [stateCode]); From c946cbb839e04468d5c2acedaac81905bb9233a9 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 28 Oct 2023 09:07:58 +0530 Subject: [PATCH 11/57] linting --- src/components/StateSelector.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index 51714252755..aaa905d8940 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -55,7 +55,9 @@ function StateSelector({errorText, useStateFromUrl, value: stateCode, label, par const [stateToDisplay, setStateToDisplay] = useState(''); useEffect(() => { - if (!useStateFromUrl || !stateFromUrl) return; + if (!useStateFromUrl || !stateFromUrl) { + return; + } // This will cause the form to revalidate and remove any error related to country name onInputChange(stateFromUrl); @@ -65,7 +67,9 @@ function StateSelector({errorText, useStateFromUrl, value: stateCode, label, par }, [stateFromUrl, useStateFromUrl]); useEffect(() => { - if (!stateCode) return; + if (!stateCode) { + return; + } // This will cause the form to revalidate and remove any error related to country name onInputChange(stateCode); From 144eea9e55e6bc9190ab71da057a0d20e0b04e39 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sun, 29 Oct 2023 10:28:58 +0530 Subject: [PATCH 12/57] StateSelector and related files refactoring --- src/components/StateSelector.js | 2 +- src/pages/ReimbursementAccount/CompanyStep.js | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index aaa905d8940..c1ebdaefc49 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -34,7 +34,7 @@ const propTypes = { /** Label to display on field */ label: PropTypes.string, - /** whether to use state from url */ + /** whether to use state from url, for cases when url value is passed from parent */ useStateFromUrl: PropTypes.bool, }; diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 7e86ebf139b..03a222abe3d 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -25,7 +25,6 @@ import Form from '../../components/Form'; import ScreenWrapper from '../../components/ScreenWrapper'; import StepPropTypes from './StepPropTypes'; import StateSelector from '../../components/StateSelector'; -import useGeographicalStateFromRoute from '../../hooks/useGeographicalStateFromRoute'; const propTypes = { ...StepPropTypes, @@ -140,8 +139,6 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul const shouldDisableCompanyName = Boolean(bankAccountID && getDefaultStateForField('companyName')); const shouldDisableCompanyTaxID = Boolean(bankAccountID && getDefaultStateForField('companyTaxID')); - const incorporationState = useGeographicalStateFromRoute('incorporationState') || getDefaultStateForField('incorporationState'); - return ( Date: Sun, 29 Oct 2023 11:18:33 +0530 Subject: [PATCH 13/57] Fix incorrect merge --- .../Profile/PersonalDetails/AddressPage.js | 7 +------ .../PersonalDetails/StateSelectionPage.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index d3b9bdfba14..9c102e566b0 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -4,12 +4,6 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import AddressSearch from '@components/AddressSearch'; import CountrySelector from '@components/CountrySelector'; @@ -28,6 +22,7 @@ import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; const propTypes = { /* Onyx Props */ diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js index ea443edc581..37acb6e3839 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js @@ -3,14 +3,14 @@ import PropTypes from 'prop-types'; import _ from 'underscore'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import lodashGet from 'lodash/get'; -import Navigation from '../../../../libs/Navigation/Navigation'; -import ScreenWrapper from '../../../../components/ScreenWrapper'; -import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; -import SelectionList from '../../../../components/SelectionList'; -import searchCountryOptions from '../../../../libs/searchCountryOptions'; -import StringUtils from '../../../../libs/StringUtils'; -import useLocalize from '../../../../hooks/useLocalize'; -import styles from '../../../../styles/styles'; +import Navigation from '@libs/Navigation/Navigation'; +import ScreenWrapper from '@components/ScreenWrapper'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import SelectionList from '@components/SelectionList'; +import searchCountryOptions from '@libs/searchCountryOptions'; +import StringUtils from '@libs/StringUtils'; +import useLocalize from '@hooks/useLocalize'; +import styles from '@styles/styles'; const propTypes = { /** Route from navigation */ From 9fb4f72463d13aca2f897cbc0e17a4aa399c4b6a Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sun, 29 Oct 2023 11:22:35 +0530 Subject: [PATCH 14/57] Reverted scripts/shellCheck.sh to state at 33f15e8d85b3366b1a09bca914f61d5caa4a2fb5 --- scripts/shellCheck.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shellCheck.sh b/scripts/shellCheck.sh index 18206a41950..d148958900d 100755 --- a/scripts/shellCheck.sh +++ b/scripts/shellCheck.sh @@ -45,4 +45,4 @@ if [ $EXIT_CODE == 0 ]; then success "ShellCheck passed for all files!" fi -exit $EXIT_CODE \ No newline at end of file +exit $EXIT_CODE From d6a643c7b7f4db06243c420606afae458fb9ade8 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sun, 29 Oct 2023 11:27:55 +0530 Subject: [PATCH 15/57] Linting --- src/components/StateSelector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index b5b1c3903c8..c27c0e57e4c 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -7,9 +7,9 @@ import styles from '@styles/styles'; import Navigation from '@libs/Navigation/Navigation'; import ROUTES from '@src/ROUTES'; import useLocalize from '@hooks/useLocalize'; +import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import FormHelpMessage from './FormHelpMessage'; -import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; const propTypes = { /** Form error text. e.g when no country is selected */ From 9bb88ee2129ebebdc9274598a72ab69ba84ce3c4 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Tue, 31 Oct 2023 21:29:36 +0530 Subject: [PATCH 16/57] Comment formatting src/hooks/useGeographicalStateFromRoute.ts Co-authored-by: Rajat Parashar --- src/hooks/useGeographicalStateFromRoute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index 3726499edf8..d86b9df6ce2 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -6,6 +6,6 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; export default function useGeographicalStateFromRoute(stateParamName = 'state') { const route = useRoute(); const stateFromUrlTemp = lodashGet(route, `params.${stateParamName}`) as unknown as string; - // check if state is valid + // Check if state is valid return lodashGet(COMMON_CONST.STATES, stateFromUrlTemp) ? stateFromUrlTemp : ''; } From 5e3844785f3ef11d3bdb411078d2482490aa35bc Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 31 Oct 2023 22:00:09 +0530 Subject: [PATCH 17/57] Rename useStateFomrUrl to shouldUseStateFromUrl --- src/components/StateSelector.js | 10 +++++----- .../settings/Profile/PersonalDetails/AddressPage.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index c27c0e57e4c..fb4046ad848 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -35,7 +35,7 @@ const propTypes = { label: PropTypes.string, /** whether to use state from url, for cases when url value is passed from parent */ - useStateFromUrl: PropTypes.bool, + shouldUseStateFromUrl: PropTypes.bool, }; const defaultProps = { @@ -44,10 +44,10 @@ const defaultProps = { forwardedRef: () => {}, label: undefined, paramName: 'state', - useStateFromUrl: true, + shouldUseStateFromUrl: true, }; -function StateSelector({errorText, useStateFromUrl, value: stateCode, label, paramName, onInputChange, forwardedRef}) { +function StateSelector({errorText, shouldUseStateFromUrl, value: stateCode, label, paramName, onInputChange, forwardedRef}) { const {translate} = useLocalize(); const stateFromUrl = useGeographicalStateFromRoute(paramName); @@ -55,7 +55,7 @@ function StateSelector({errorText, useStateFromUrl, value: stateCode, label, par const [stateToDisplay, setStateToDisplay] = useState(''); useEffect(() => { - if (!useStateFromUrl || !stateFromUrl) { + if (!shouldUseStateFromUrl || !stateFromUrl) { return; } @@ -64,7 +64,7 @@ function StateSelector({errorText, useStateFromUrl, value: stateCode, label, par setStateToDisplay(stateFromUrl); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [stateFromUrl, useStateFromUrl]); + }, [stateFromUrl, shouldUseStateFromUrl]); useEffect(() => { if (!stateCode) { diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index 9c102e566b0..a9b64b2420e 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -232,7 +232,7 @@ function AddressPage({privatePersonalDetails, route}) { {isUSAForm ? ( From 470ec03d5e71e0580643a852a14fe2ee61e4f241 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 31 Oct 2023 22:07:00 +0530 Subject: [PATCH 18/57] forwardedRef type correction StateSelector --- src/components/StateSelector.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index fb4046ad848..59f97425114 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -10,6 +10,7 @@ import useLocalize from '@hooks/useLocalize'; import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import FormHelpMessage from './FormHelpMessage'; +import refPropTypes from './refPropTypes'; const propTypes = { /** Form error text. e.g when no country is selected */ @@ -26,7 +27,7 @@ const propTypes = { inputID: PropTypes.string.isRequired, /** React ref being forwarded to the MenuItemWithTopDescription */ - forwardedRef: PropTypes.func, + forwardedRef: refPropTypes, /** Label of state in the url */ paramName: PropTypes.string, From 8f28580d6dca027d9698a8510a0ebaf28a6ba38d Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 31 Oct 2023 22:08:46 +0530 Subject: [PATCH 19/57] StateSelector: rename paramName to queryParam --- src/components/StateSelector.js | 10 +++++----- src/pages/ReimbursementAccount/CompanyStep.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index 59f97425114..13202f9d79e 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -30,7 +30,7 @@ const propTypes = { forwardedRef: refPropTypes, /** Label of state in the url */ - paramName: PropTypes.string, + queryParam: PropTypes.string, /** Label to display on field */ label: PropTypes.string, @@ -44,14 +44,14 @@ const defaultProps = { value: undefined, forwardedRef: () => {}, label: undefined, - paramName: 'state', + queryParam: 'state', shouldUseStateFromUrl: true, }; -function StateSelector({errorText, shouldUseStateFromUrl, value: stateCode, label, paramName, onInputChange, forwardedRef}) { +function StateSelector({errorText, shouldUseStateFromUrl, value: stateCode, label, queryParam, onInputChange, forwardedRef}) { const {translate} = useLocalize(); - const stateFromUrl = useGeographicalStateFromRoute(paramName); + const stateFromUrl = useGeographicalStateFromRoute(queryParam); const [stateToDisplay, setStateToDisplay] = useState(''); @@ -92,7 +92,7 @@ function StateSelector({errorText, shouldUseStateFromUrl, value: stateCode, labe description={label || translate('common.state')} onPress={() => { const activeRoute = Navigation.getActiveRoute(); - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_STATE.getRoute(stateCode, activeRoute, label, paramName)); + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_STATE.getRoute(stateCode, activeRoute, label, queryParam)); }} /> diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index fa806521e5d..04010f1db0c 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -248,7 +248,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul label={translate('companyStep.incorporationState')} defaultValue={getDefaultStateForField('incorporationState')} shouldSaveDraft - paramName="incorporationState" + queryParam="incorporationState" /> Date: Wed, 1 Nov 2023 08:24:52 +0530 Subject: [PATCH 20/57] Remove lodashGet from useGeographicalStateFromRoute --- src/hooks/useGeographicalStateFromRoute.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index d86b9df6ce2..112e0e8db69 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -1,11 +1,14 @@ -import {useRoute} from '@react-navigation/native'; -// eslint-disable-next-line you-dont-need-lodash-underscore/get -import lodashGet from 'lodash/get'; +import {ParamListBase, RouteProp, useRoute} from '@react-navigation/native'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; +type CustomParamList = ParamListBase & Record>; + export default function useGeographicalStateFromRoute(stateParamName = 'state') { - const route = useRoute(); - const stateFromUrlTemp = lodashGet(route, `params.${stateParamName}`) as unknown as string; - // Check if state is valid - return lodashGet(COMMON_CONST.STATES, stateFromUrlTemp) ? stateFromUrlTemp : ''; + const route = useRoute>(); + const stateFromUrlTemp = route.params?.[stateParamName] as string | undefined; + + if (!stateFromUrlTemp) { + return ''; + } + return COMMON_CONST.STATES[stateFromUrlTemp as keyof typeof COMMON_CONST.STATES] ? stateFromUrlTemp : ''; } From 428e48c177adcf9a77f0067114781307be11ea62 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 1 Nov 2023 08:37:52 +0530 Subject: [PATCH 21/57] StateSelectionPage appendParam restructuring --- src/libs/Url.ts | 16 +++++++++- .../PersonalDetails/StateSelectionPage.js | 32 ++++--------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/libs/Url.ts b/src/libs/Url.ts index a21f007e846..7e028aded60 100644 --- a/src/libs/Url.ts +++ b/src/libs/Url.ts @@ -41,4 +41,18 @@ function hasSameExpensifyOrigin(url1: string, url2: string): boolean { } } -export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL}; +/** + * Appends or updates a query parameter in a given URL. + */ +function appendParam(url: string, paramName: string, paramValue: string) { + if (url.includes(`${paramName}=`)) { + // If parameter exists, replace it + const regex = new RegExp(`${paramName}=([^&]*)`); + return url.replace(regex, `${paramName}=${paramValue}`); + } + // If parameter doesn't exist, append it + const separator = url.includes('?') ? '&' : '?'; + return `${url}${separator}${paramName}=${paramValue}`; +} + +export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam}; diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js index 37acb6e3839..a506d7e6149 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.js @@ -1,15 +1,16 @@ -import React, {useState, useMemo, useCallback} from 'react'; -import PropTypes from 'prop-types'; -import _ from 'underscore'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import lodashGet from 'lodash/get'; -import Navigation from '@libs/Navigation/Navigation'; -import ScreenWrapper from '@components/ScreenWrapper'; +import PropTypes from 'prop-types'; +import React, {useCallback, useMemo, useState} from 'react'; +import _ from 'underscore'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; +import useLocalize from '@hooks/useLocalize'; +import Navigation from '@libs/Navigation/Navigation'; import searchCountryOptions from '@libs/searchCountryOptions'; import StringUtils from '@libs/StringUtils'; -import useLocalize from '@hooks/useLocalize'; +import {appendParam} from '@libs/Url'; import styles from '@styles/styles'; const propTypes = { @@ -32,25 +33,6 @@ const propTypes = { }).isRequired, }; -/** - * Appends or updates a query parameter in a given URL. - * - * @param {string} url - The original URL. - * @param {string} paramName - The name of the query parameter to append or update. - * @param {string} paramValue - The value of the query parameter to append or update. - * @returns {string} The updated URL with the appended or updated query parameter. - */ -function appendParam(url, paramName, paramValue) { - if (url.includes(`${paramName}=`)) { - // If parameter exists, replace it - const regex = new RegExp(`${paramName}=([^&]*)`); - return url.replace(regex, `${paramName}=${paramValue}`); - } - // If parameter doesn't exist, append it - const separator = url.includes('?') ? '&' : '?'; - return `${url}${separator}${paramName}=${paramValue}`; -} - function StateSelectionPage({route, navigation}) { const [searchValue, setSearchValue] = useState(''); const {translate} = useLocalize(); From 7cd8b6fb2c30a45999f521355e97fdadfe6f4b2f Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 1 Nov 2023 08:43:07 +0530 Subject: [PATCH 22/57] Prettier formatting --- src/components/StateSelector.js | 12 ++++++------ .../settings/Profile/PersonalDetails/AddressPage.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/StateSelector.js b/src/components/StateSelector.js index 13202f9d79e..121037d9137 100644 --- a/src/components/StateSelector.js +++ b/src/components/StateSelector.js @@ -1,15 +1,15 @@ -import React, {useEffect, useState} from 'react'; +import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import PropTypes from 'prop-types'; +import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import styles from '@styles/styles'; +import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; +import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; +import styles from '@styles/styles'; import ROUTES from '@src/ROUTES'; -import useLocalize from '@hooks/useLocalize'; -import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; -import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import FormHelpMessage from './FormHelpMessage'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import refPropTypes from './refPropTypes'; const propTypes = { diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index a9b64b2420e..752c3d2984a 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -13,6 +13,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import StateSelector from '@components/StateSelector'; import TextInput from '@components/TextInput'; +import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; import useLocalize from '@hooks/useLocalize'; import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails'; import Navigation from '@libs/Navigation/Navigation'; @@ -22,7 +23,6 @@ import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; const propTypes = { /* Onyx Props */ From ec0eb6f7ed0d3a0cd0884cd7999fb91472702797 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Tue, 20 Feb 2024 03:20:43 +0530 Subject: [PATCH 23/57] Comment typo fix src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 7c032024462..d5221c98b04 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -68,7 +68,7 @@ function StateSelectionPage() { Navigation.goBack(appendParam(backTo, 'state', option.value)); } else { // Otherwise, navigate to the specific route defined in "backTo" with a country parameter - // @ts-expect-error Navigation.navigate does take a paraml + // @ts-expect-error Navigation.navigate does take a param Navigation.navigate(appendParam(backTo, 'state', option.value)); } }, From 07f6f2160d2670a8155708a88e38a291f6cb6ca7 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Tue, 20 Feb 2024 03:21:40 +0530 Subject: [PATCH 24/57] Comment formatting src/pages/settings/Profile/PersonalDetails/AddressPage.js Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/pages/settings/Profile/PersonalDetails/AddressPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index 95b006828c3..87960d1f668 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -65,9 +65,9 @@ function AddressPage({privatePersonalDetails, route}) { usePrivatePersonalDetails(); const {translate} = useLocalize(); const address = useMemo(() => lodashGet(privatePersonalDetails, 'address') || {}, [privatePersonalDetails]); - const countryFromUrlTemp = lodashGet(route, 'params.country'); - // check if country is valid + + // Check if country is valid const countryFromUrl = lodashGet(CONST.ALL_COUNTRIES, countryFromUrlTemp) ? countryFromUrlTemp : ''; const stateFromUrl = useGeographicalStateFromRoute(); From 877740b9160d380890798ac2a6e42e52f2bc7bea Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 20 Feb 2024 17:01:43 +0530 Subject: [PATCH 25/57] Fix TS errors --- src/ROUTES.ts | 17 +++++++---------- src/components/StateSelector.tsx | 1 - .../PersonalDetails/StateSelectionPage.tsx | 11 ++++++----- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 3a4b92e9e52..1cf8eef0f4a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -133,16 +133,13 @@ const ROUTES = { }, SETTINGS_ADDRESS_STATE: { route: 'settings/profile/address/state', - getRoute: (state: string, backTo?: string, label?: string) => { - let route = `settings/profile/address/state?state=${state}`; - if (backTo) { - route += `&backTo=${encodeURIComponent(backTo)}`; - } - if (label) { - route += `&label=${encodeURIComponent(label)}`; - } - return route; - }, + + getRoute: (state?: string, backTo?: string, label?: string) => + `${getUrlWithBackToParam(`settings/profile/address/state${state ? `?state=${encodeURIComponent(state)}` : ''}`, backTo)}${ + // Nullish operator ?? doesnt seem to be a replacement for || here + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + label ? `${backTo || state ? '&' : '?'}label=${encodeURIComponent(label)}` : '' + }` as const, }, SETTINGS_CONTACT_METHODS: { route: 'settings/profile/contact-methods', diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 79c086057f8..9ddf2573f85 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -83,7 +83,6 @@ function StateSelector({errorText, shouldUseStateFromUrl = true, value: stateCod description={label || translate('common.state')} onPress={() => { const activeRoute = Navigation.getActiveRoute(); - // @ts-expect-error Navigation.navigate does take a param Navigation.navigate(ROUTES.SETTINGS_ADDRESS_STATE.getRoute(stateCode, activeRoute, label)); }} wrapperStyle={wrapperStyle} diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index d5221c98b04..532f4d3f97e 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -12,6 +12,7 @@ import searchCountryOptions from '@libs/searchCountryOptions'; import type {CountryData} from '@libs/searchCountryOptions'; import StringUtils from '@libs/StringUtils'; import {appendParam} from '@libs/Url'; +import type {Route} from '@src/ROUTES'; type State = keyof typeof COMMON_CONST.STATES; @@ -64,12 +65,12 @@ function StateSelectionPage() { Navigation.goBack(); } else if (!_.isEmpty(backTo) && navigation.getState().routes.length === 1) { // If "backTo" is not empty and there is only one route, go back to the specific route defined in "backTo" with a country parameter - // @ts-expect-error Navigation.goBack does take a param - Navigation.goBack(appendParam(backTo, 'state', option.value)); - } else { + Navigation.goBack(appendParam(backTo, 'state', option.value) as Route); + } else if (!_.isEmpty(backTo)) { // Otherwise, navigate to the specific route defined in "backTo" with a country parameter - // @ts-expect-error Navigation.navigate does take a param - Navigation.navigate(appendParam(backTo, 'state', option.value)); + Navigation.navigate(appendParam(backTo, 'state', option.value) as Route); + } else { + Navigation.goBack(); } }, [navigation, params?.backTo], From a6faef416ad62d6e0c58df96dce26ce1ca1ac7b7 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 26 Feb 2024 18:17:58 +0530 Subject: [PATCH 26/57] Refactor StateSelectionPage with more explanatory comments --- .../PersonalDetails/StateSelectionPage.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 7fe667f5b9e..804d3de6bf1 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -60,17 +60,22 @@ function StateSelectionPage() { (option: CountryData) => { const backTo = params?.backTo ?? ''; - // Check the navigation state and "backTo" parameter to decide navigation behavior - if (navigation.getState().routes.length === 1 && _.isEmpty(backTo)) { - // If there is only one route and "backTo" is empty, go back in navigation - Navigation.goBack(); - } else if (!_.isEmpty(backTo) && navigation.getState().routes.length === 1) { - // If "backTo" is not empty and there is only one route, go back to the specific route defined in "backTo" with a country parameter - Navigation.goBack(appendParam(backTo, 'state', option.value) as Route); + // Determine navigation action based on "backTo" presence and route stack length. + if (navigation.getState().routes.length === 1) { + // If this is the only page in the navigation stack (examples include direct navigation to this page via URL or page reload). + if (_.isEmpty(backTo)) { + // No "backTo": default back navigation. + Navigation.goBack(); + } else { + // "backTo" provided: navigate back to "backTo" with state parameter. + Navigation.goBack(appendParam(backTo, 'state', option.value) as Route); + } } else if (!_.isEmpty(backTo)) { - // Otherwise, navigate to the specific route defined in "backTo" with a country parameter + // Most common case: Navigation stack has multiple routes and "backTo" is defined: navigate to "backTo" with state parameter. Navigation.navigate(appendParam(backTo, 'state', option.value) as Route); } else { + // This is a fallback block and should never execute if StateSelector is correctly appending the "backTo" route. + // Navigation stack has multiple routes but no "backTo" defined: default back navigation. Navigation.goBack(); } }, From 7b153084d3214db17b0067e5797552eadacc10af Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Fri, 1 Mar 2024 12:03:54 +0530 Subject: [PATCH 27/57] Incorrect header space fix: src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx Co-authored-by: Rajat --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 804d3de6bf1..05746405adf 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -86,8 +86,6 @@ function StateSelectionPage() { Date: Fri, 1 Mar 2024 12:07:11 +0530 Subject: [PATCH 28/57] Remove unused code StateSelectionPage.tsx --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 05746405adf..b30c9c9967c 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -7,7 +7,6 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import searchCountryOptions from '@libs/searchCountryOptions'; import type {CountryData} from '@libs/searchCountryOptions'; @@ -26,7 +25,6 @@ type RouteParams = { function StateSelectionPage() { const route = useRoute(); const navigation = useNavigation(); - const styles = useThemeStyles(); const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); From 397278d3665de12a40768a1ee4e789ac1ee8c56b Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Thu, 7 Mar 2024 21:54:37 +0530 Subject: [PATCH 29/57] Prettier formatting --- src/pages/settings/Profile/PersonalDetails/AddressPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx index 9d9d16a6a38..77d551b283e 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx @@ -13,8 +13,8 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as PersonalDetails from '@userActions/PersonalDetails'; -import CONST from '@src/CONST'; import type {FormOnyxValues} from '@src/components/Form/types'; +import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; From 5d223d69037f3e89732c855bc66f8d631f3c5f85 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 9 Mar 2024 02:21:00 +0530 Subject: [PATCH 30/57] fix: handle potential 'undefined' object issue --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index b30c9c9967c..1c3d8dd650c 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -59,7 +59,7 @@ function StateSelectionPage() { const backTo = params?.backTo ?? ''; // Determine navigation action based on "backTo" presence and route stack length. - if (navigation.getState().routes.length === 1) { + if (navigation.getState()?.routes.length === 1) { // If this is the only page in the navigation stack (examples include direct navigation to this page via URL or page reload). if (_.isEmpty(backTo)) { // No "backTo": default back navigation. From 3a3c767746c199d53b2791ec1d786cb5fc7ff200 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Sat, 9 Mar 2024 02:25:20 +0530 Subject: [PATCH 31/57] Typo fix src/components/StateSelector.tsx Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/components/StateSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 9ddf2573f85..54a2066c52f 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -15,7 +15,7 @@ import MenuItemWithTopDescription from './MenuItemWithTopDescription'; type State = keyof typeof COMMON_CONST.STATES; type StateSelectorProps = { - /** Form error text. e.g when no country is selected */ + /** Form error text. e.g when no state is selected */ errorText?: MaybePhraseKey; /** Current selected state */ From c56f8d52e6e0627f114867e6f2e65c3b44e1983e Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Sat, 9 Mar 2024 02:26:20 +0530 Subject: [PATCH 32/57] Format src/libs/Url.ts Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/libs/Url.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Url.ts b/src/libs/Url.ts index 7e028aded60..1f402fdfc13 100644 --- a/src/libs/Url.ts +++ b/src/libs/Url.ts @@ -45,8 +45,8 @@ function hasSameExpensifyOrigin(url1: string, url2: string): boolean { * Appends or updates a query parameter in a given URL. */ function appendParam(url: string, paramName: string, paramValue: string) { + // If parameter exists, replace it if (url.includes(`${paramName}=`)) { - // If parameter exists, replace it const regex = new RegExp(`${paramName}=([^&]*)`); return url.replace(regex, `${paramName}=${paramValue}`); } From 470be54fc13bebb1596626989db54a3545c27688 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Sat, 9 Mar 2024 02:26:48 +0530 Subject: [PATCH 33/57] Format src/libs/Url.ts Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/libs/Url.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Url.ts b/src/libs/Url.ts index 1f402fdfc13..4e3282e7bdb 100644 --- a/src/libs/Url.ts +++ b/src/libs/Url.ts @@ -50,6 +50,7 @@ function appendParam(url: string, paramName: string, paramValue: string) { const regex = new RegExp(`${paramName}=([^&]*)`); return url.replace(regex, `${paramName}=${paramValue}`); } + // If parameter doesn't exist, append it const separator = url.includes('?') ? '&' : '?'; return `${url}${separator}${paramName}=${paramValue}`; From 1670b66448c4d92a2eba9f5025ef11bf258c1194 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Sat, 9 Mar 2024 02:27:25 +0530 Subject: [PATCH 34/57] Format src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 1c3d8dd650c..275400b27cc 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -28,9 +28,7 @@ function StateSelectionPage() { const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); - - const params = route.params as RouteParams | undefined; // Type assertion - + const params = route.params as RouteParams | undefined; const currentState = params?.state; const label = params?.label; From 5d2ff1eba9382350854d3512db85d099faca37eb Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Sat, 9 Mar 2024 02:27:53 +0530 Subject: [PATCH 35/57] Format src/pages/settings/Profile/PersonalDetails/AddressPage.tsx Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/pages/settings/Profile/PersonalDetails/AddressPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx index 77d551b283e..7706b839b18 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx @@ -48,6 +48,7 @@ function AddressPage({privatePersonalDetails, route}: AddressPageProps) { usePrivatePersonalDetails(); const {translate} = useLocalize(); const address = useMemo(() => privatePersonalDetails?.address, [privatePersonalDetails]); + const countryFromUrlTemp = route?.params?.country; // Check if country is valid const countryFromUrl = CONST.ALL_COUNTRIES[countryFromUrlTemp as keyof typeof CONST.ALL_COUNTRIES] ? countryFromUrlTemp : ''; From 18717ea0f1072ae10b5216481903a4415a84e9a5 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 9 Mar 2024 02:38:28 +0530 Subject: [PATCH 36/57] Comment for function useGeographicalStateFromRoute --- src/hooks/useGeographicalStateFromRoute.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index 39d3cab5f68..5015db12d45 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -5,6 +5,10 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; type CustomParamList = ParamListBase & Record>; type State = keyof typeof COMMON_CONST.STATES; +/** + * Extracts the 'state' (default) query parameter from the route/ url and validates it against COMMON_CONST.STATES, returning its ISO code or `undefined`. + * Example: const stateISO = useGeographicalStateFromRoute(); // Assuming 'state' param is 'CA' or another valid state, returns the corresponding ISO code or `undefined` if invalid. + */ export default function useGeographicalStateFromRoute(stateParamName = 'state'): State | undefined { const route = useRoute>(); const stateFromUrlTemp = route.params?.[stateParamName] as string | undefined; From 3ba97a550a07ce38adbf5e397bba3e56907552b1 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 9 Mar 2024 03:58:19 +0530 Subject: [PATCH 37/57] hash append for StateSelector edge cases --- src/components/StateSelector.tsx | 12 +++++++++++- src/hooks/useGeographicalStateFromRoute.ts | 7 ++++++- .../Profile/PersonalDetails/StateSelectionPage.tsx | 7 +++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 54a2066c52f..41dcc21bf47 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -1,3 +1,5 @@ +import {useRoute} from '@react-navigation/native'; +import type {ParamListBase, RouteProp} from '@react-navigation/native'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import React, {useEffect, useState} from 'react'; import type {ForwardedRef} from 'react'; @@ -12,6 +14,8 @@ import FormHelpMessage from './FormHelpMessage'; import type {MenuItemProps} from './MenuItem'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; +type CustomParamList = ParamListBase & Record>; + type State = keyof typeof COMMON_CONST.STATES; type StateSelectorProps = { @@ -40,6 +44,12 @@ function StateSelector({errorText, shouldUseStateFromUrl = true, value: stateCod const stateFromUrl = useGeographicalStateFromRoute(); const [stateToDisplay, setStateToDisplay] = useState(''); + /** + * See {@link module:src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx#withHash} for more information. + */ + const route = useRoute>(); + const rawStateFromUrl = route.params?.state as string | undefined; + useEffect(() => { if (!shouldUseStateFromUrl || !stateFromUrl) { return; @@ -52,7 +62,7 @@ function StateSelector({errorText, shouldUseStateFromUrl = true, value: stateCod setStateToDisplay(stateFromUrl); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [stateFromUrl, shouldUseStateFromUrl]); + }, [rawStateFromUrl, shouldUseStateFromUrl]); useEffect(() => { if (!stateCode) { diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index 5015db12d45..6ce67c0ef93 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -5,6 +5,11 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; type CustomParamList = ParamListBase & Record>; type State = keyof typeof COMMON_CONST.STATES; +/** + * See {@link module:src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx#withHash} for more information. + */ +const removeHash = (arg: string): string => arg.replace(/-hash-.*$/, ''); + /** * Extracts the 'state' (default) query parameter from the route/ url and validates it against COMMON_CONST.STATES, returning its ISO code or `undefined`. * Example: const stateISO = useGeographicalStateFromRoute(); // Assuming 'state' param is 'CA' or another valid state, returns the corresponding ISO code or `undefined` if invalid. @@ -16,5 +21,5 @@ export default function useGeographicalStateFromRoute(stateParamName = 'state'): if (!stateFromUrlTemp) { return; } - return COMMON_CONST.STATES[stateFromUrlTemp as State].stateISO; + return COMMON_CONST.STATES[removeHash(stateFromUrlTemp) as State].stateISO; } diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 275400b27cc..1a9a0fab906 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -56,6 +56,9 @@ function StateSelectionPage() { (option: CountryData) => { const backTo = params?.backTo ?? ''; + // Add hash to param for rare cases, such as in ReimbursementAccountPage (currentStep = CONST.BANK_ACCOUNT.STEP.COMPANY), when the URL on the form page (which has the StateSelector component) already includes a state param. If the form then updates the StateInput with state data from another source and the user attempts to use StateSelector to select the same state present in the URL, it will cause the StateSelector not to detect the state change, as the URL remains the same. + const withHash = (arg: string): string => `${arg}-hash-${Math.random().toString(36).substring(2, 8)}`; + // Determine navigation action based on "backTo" presence and route stack length. if (navigation.getState()?.routes.length === 1) { // If this is the only page in the navigation stack (examples include direct navigation to this page via URL or page reload). @@ -64,11 +67,11 @@ function StateSelectionPage() { Navigation.goBack(); } else { // "backTo" provided: navigate back to "backTo" with state parameter. - Navigation.goBack(appendParam(backTo, 'state', option.value) as Route); + Navigation.goBack(appendParam(backTo, 'state', withHash(option.value)) as Route); } } else if (!_.isEmpty(backTo)) { // Most common case: Navigation stack has multiple routes and "backTo" is defined: navigate to "backTo" with state parameter. - Navigation.navigate(appendParam(backTo, 'state', option.value) as Route); + Navigation.navigate(appendParam(backTo, 'state', withHash(option.value)) as Route); } else { // This is a fallback block and should never execute if StateSelector is correctly appending the "backTo" route. // Navigation stack has multiple routes but no "backTo" defined: default back navigation. From 0dc7f49ccd5d2169d387c1b2267d7463e0b35c6e Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 9 Mar 2024 04:32:25 +0530 Subject: [PATCH 38/57] Comment update useGeographicalStateFromRoute --- src/hooks/useGeographicalStateFromRoute.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index 6ce67c0ef93..69482b8c3a3 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -12,7 +12,10 @@ const removeHash = (arg: string): string => arg.replace(/-hash-.*$/, ''); /** * Extracts the 'state' (default) query parameter from the route/ url and validates it against COMMON_CONST.STATES, returning its ISO code or `undefined`. - * Example: const stateISO = useGeographicalStateFromRoute(); // Assuming 'state' param is 'CA' or another valid state, returns the corresponding ISO code or `undefined` if invalid. + * Example 1: Url: https://dev.new.expensify.com:8082/settings/profile/address?state=MO Returns: MO + * Example 2: Url: https://dev.new.expensify.com:8082/settings/profile/address?state=ASDF Returns: undefined + * Example 3: Url: https://dev.new.expensify.com:8082/settings/profile/address Returns: undefined + * Example 4: Url: https://dev.new.expensify.com:8082/settings/profile/address?state=MO-hash-a12341 Returns: MO */ export default function useGeographicalStateFromRoute(stateParamName = 'state'): State | undefined { const route = useRoute>(); From 57bcee217ff3a01861f0a0f8ac6a09bba7a4bfc3 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 9 Mar 2024 06:19:39 +0530 Subject: [PATCH 39/57] Add StateSelector to MoneyRequest Navigation stack to work for /send/new/add-debit-card --- src/ROUTES.ts | 10 ++++++++++ src/SCREENS.ts | 1 + src/components/StateSelector.tsx | 10 ++++++++-- .../Navigation/AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + src/pages/settings/Wallet/AddDebitCardPage.tsx | 5 +++++ 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 371691b7b0f..a1b28c33d51 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -423,6 +423,16 @@ const ROUTES = { getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/scan` as const, }, + MONEY_REQUEST_STATE_SELECTOR: { + route: 'moneyrequest/state', + + getRoute: (state?: string, backTo?: string, label?: string) => + `${getUrlWithBackToParam(`moneyrequest/state${state ? `?state=${encodeURIComponent(state)}` : ''}`, backTo)}${ + // Nullish operator ?? doesnt seem to be a replacement for || here + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + label ? `${backTo || state ? '&' : '?'}label=${encodeURIComponent(label)}` : '' + }` as const, + }, IOU_REQUEST: 'request/new', IOU_SEND: 'send/new', IOU_SEND_ADD_BANK_ACCOUNT: 'send/new/add-bank-account', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 5121068f273..f04b521b987 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -161,6 +161,7 @@ const SCREENS = { EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint', DISTANCE: 'Money_Request_Distance', RECEIPT: 'Money_Request_Receipt', + STATE_SELECTOR: 'Money_Request_State_Selector', }, IOU_SEND: { diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 41dcc21bf47..9de9c746c41 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -36,9 +36,15 @@ type StateSelectorProps = { /** whether to use state from url, for cases when url value is passed from parent */ shouldUseStateFromUrl?: boolean; + + /** object to get route details from */ + stateSelectorRoute?: typeof ROUTES.SETTINGS_ADDRESS_STATE | typeof ROUTES.MONEY_REQUEST_STATE_SELECTOR; }; -function StateSelector({errorText, shouldUseStateFromUrl = true, value: stateCode, label, onInputChange, wrapperStyle}: StateSelectorProps, ref: ForwardedRef) { +function StateSelector( + {errorText, shouldUseStateFromUrl = true, value: stateCode, label, onInputChange, wrapperStyle, stateSelectorRoute = ROUTES.SETTINGS_ADDRESS_STATE}: StateSelectorProps, + ref: ForwardedRef, +) { const styles = useThemeStyles(); const {translate} = useLocalize(); const stateFromUrl = useGeographicalStateFromRoute(); @@ -93,7 +99,7 @@ function StateSelector({errorText, shouldUseStateFromUrl = true, value: stateCod description={label || translate('common.state')} onPress={() => { const activeRoute = Navigation.getActiveRoute(); - Navigation.navigate(ROUTES.SETTINGS_ADDRESS_STATE.getRoute(stateCode, activeRoute, label)); + Navigation.navigate(stateSelectorRoute.getRoute(stateCode, activeRoute, label)); }} wrapperStyle={wrapperStyle} /> diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index b030165aca1..4ae6ddb0343 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -106,6 +106,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/MoneyRequestWaypointPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.DISTANCE]: () => require('../../../pages/iou/NewDistanceRequestPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.RECEIPT]: () => require('../../../pages/EditRequestReceiptPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.STATE_SELECTOR]: () => require('../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default as React.ComponentType, }); const SplitDetailsModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 3c00bd3c453..cc5ae141b5a 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -490,6 +490,7 @@ const config: LinkingOptions['config'] = { [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, + [SCREENS.MONEY_REQUEST.STATE_SELECTOR]: {path: ROUTES.MONEY_REQUEST_STATE_SELECTOR.route, exact: true}, }, }, [SCREENS.RIGHT_MODAL.SPLIT_DETAILS]: { diff --git a/src/pages/settings/Wallet/AddDebitCardPage.tsx b/src/pages/settings/Wallet/AddDebitCardPage.tsx index 99f2a90abd8..0beb3c16018 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.tsx +++ b/src/pages/settings/Wallet/AddDebitCardPage.tsx @@ -1,3 +1,4 @@ +import {useRoute} from '@react-navigation/native'; import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -22,6 +23,8 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; import type {AddDebitCardForm} from '@src/types/form'; import INPUT_IDS from '@src/types/form/AddDebitCardForm'; @@ -58,6 +61,7 @@ function DebitCardPage({formData}: DebitCardPageProps) { const {translate} = useLocalize(); const prevFormDataSetupComplete = usePrevious(!!formData?.setupComplete); const nameOnCardRef = useRef(null); + const route = useRoute(); /** * Reset the form values on the mount and unmount so that old errors don't show when this form is displayed again. @@ -200,6 +204,7 @@ function DebitCardPage({formData}: DebitCardPageProps) { /> From 9ffea05a449afe435ee0bee1ece0e8202b9f918a Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Sat, 9 Mar 2024 06:30:49 +0530 Subject: [PATCH 40/57] Formatting src/pages/settings/Profile/PersonalDetails/AddressPage.tsx Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/pages/settings/Profile/PersonalDetails/AddressPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx index 7706b839b18..ae724ce218a 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx @@ -48,8 +48,8 @@ function AddressPage({privatePersonalDetails, route}: AddressPageProps) { usePrivatePersonalDetails(); const {translate} = useLocalize(); const address = useMemo(() => privatePersonalDetails?.address, [privatePersonalDetails]); - const countryFromUrlTemp = route?.params?.country; + // Check if country is valid const countryFromUrl = CONST.ALL_COUNTRIES[countryFromUrlTemp as keyof typeof CONST.ALL_COUNTRIES] ? countryFromUrlTemp : ''; const stateFromUrl = useGeographicalStateFromRoute(); From d25904ca74e906302784150dd143bdbf7d0ebb33 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Sat, 9 Mar 2024 06:31:30 +0530 Subject: [PATCH 41/57] Commnet udpate src/ROUTES.ts Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/ROUTES.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a1b28c33d51..9c697f4f4cf 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -136,7 +136,7 @@ const ROUTES = { getRoute: (state?: string, backTo?: string, label?: string) => `${getUrlWithBackToParam(`settings/profile/address/state${state ? `?state=${encodeURIComponent(state)}` : ''}`, backTo)}${ - // Nullish operator ?? doesnt seem to be a replacement for || here + // the label param can be an empty string so we cannot use a nullish ?? operator // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing label ? `${backTo || state ? '&' : '?'}label=${encodeURIComponent(label)}` : '' }` as const, From 81f452c650071377cbaa17ab74b215ab79835f9a Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 9 Mar 2024 06:33:01 +0530 Subject: [PATCH 42/57] Comment update ROUTES.ts --- src/ROUTES.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9c697f4f4cf..de06c07a083 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -428,7 +428,7 @@ const ROUTES = { getRoute: (state?: string, backTo?: string, label?: string) => `${getUrlWithBackToParam(`moneyrequest/state${state ? `?state=${encodeURIComponent(state)}` : ''}`, backTo)}${ - // Nullish operator ?? doesnt seem to be a replacement for || here + // the label param can be an empty string so we cannot use a nullish ?? operator // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing label ? `${backTo || state ? '&' : '?'}label=${encodeURIComponent(label)}` : '' }` as const, From dc35055073b34f237c9d4d6a3c589bf53ac8f892 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Tue, 12 Mar 2024 03:11:46 +0530 Subject: [PATCH 43/57] Example update src/hooks/useGeographicalStateFromRoute.ts Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/hooks/useGeographicalStateFromRoute.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index 69482b8c3a3..bddc2b5c9a0 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -12,10 +12,10 @@ const removeHash = (arg: string): string => arg.replace(/-hash-.*$/, ''); /** * Extracts the 'state' (default) query parameter from the route/ url and validates it against COMMON_CONST.STATES, returning its ISO code or `undefined`. - * Example 1: Url: https://dev.new.expensify.com:8082/settings/profile/address?state=MO Returns: MO - * Example 2: Url: https://dev.new.expensify.com:8082/settings/profile/address?state=ASDF Returns: undefined - * Example 3: Url: https://dev.new.expensify.com:8082/settings/profile/address Returns: undefined - * Example 4: Url: https://dev.new.expensify.com:8082/settings/profile/address?state=MO-hash-a12341 Returns: MO + * Example 1: Url: https://new.expensify.com/settings/profile/address?state=MO Returns: MO + * Example 2: Url: https://new.expensify.com/settings/profile/address?state=ASDF Returns: undefined + * Example 3: Url: https://new.expensify.com/settings/profile/address Returns: undefined + * Example 4: Url: https://new.expensify.com/settings/profile/address?state=MO-hash-a12341 Returns: MO */ export default function useGeographicalStateFromRoute(stateParamName = 'state'): State | undefined { const route = useRoute>(); From 78afcd5da67d02e4a51518b2edc15319ed0bf967 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 12 Mar 2024 03:15:29 +0530 Subject: [PATCH 44/57] Refactor linking config StateSelector --- src/libs/Navigation/linkingConfig/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 34526091e47..787a6054db8 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -490,10 +490,10 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.CURRENCY]: ROUTES.MONEY_REQUEST_CURRENCY.route, [SCREENS.MONEY_REQUEST.RECEIPT]: ROUTES.MONEY_REQUEST_RECEIPT.route, [SCREENS.MONEY_REQUEST.DISTANCE]: ROUTES.MONEY_REQUEST_DISTANCE.route, + [SCREENS.MONEY_REQUEST.STATE_SELECTOR]: {path: ROUTES.MONEY_REQUEST_STATE_SELECTOR.route, exact: true}, [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, - [SCREENS.MONEY_REQUEST.STATE_SELECTOR]: {path: ROUTES.MONEY_REQUEST_STATE_SELECTOR.route, exact: true}, }, }, [SCREENS.RIGHT_MODAL.SPLIT_DETAILS]: { From ecc39530deb63835402a3163ac028d2da3fd5164 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 13 Mar 2024 02:59:18 +0530 Subject: [PATCH 45/57] Revert "hash append for StateSelector edge cases" This reverts commit 3ba97a550a07ce38adbf5e397bba3e56907552b1. --- src/components/StateSelector.tsx | 12 +----------- src/hooks/useGeographicalStateFromRoute.ts | 7 +------ .../Profile/PersonalDetails/StateSelectionPage.tsx | 7 ++----- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 9de9c746c41..3aff71cd9f9 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -1,5 +1,3 @@ -import {useRoute} from '@react-navigation/native'; -import type {ParamListBase, RouteProp} from '@react-navigation/native'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import React, {useEffect, useState} from 'react'; import type {ForwardedRef} from 'react'; @@ -14,8 +12,6 @@ import FormHelpMessage from './FormHelpMessage'; import type {MenuItemProps} from './MenuItem'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; -type CustomParamList = ParamListBase & Record>; - type State = keyof typeof COMMON_CONST.STATES; type StateSelectorProps = { @@ -50,12 +46,6 @@ function StateSelector( const stateFromUrl = useGeographicalStateFromRoute(); const [stateToDisplay, setStateToDisplay] = useState(''); - /** - * See {@link module:src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx#withHash} for more information. - */ - const route = useRoute>(); - const rawStateFromUrl = route.params?.state as string | undefined; - useEffect(() => { if (!shouldUseStateFromUrl || !stateFromUrl) { return; @@ -68,7 +58,7 @@ function StateSelector( setStateToDisplay(stateFromUrl); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [rawStateFromUrl, shouldUseStateFromUrl]); + }, [stateFromUrl, shouldUseStateFromUrl]); useEffect(() => { if (!stateCode) { diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index bddc2b5c9a0..3e85bfc2a36 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -5,11 +5,6 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; type CustomParamList = ParamListBase & Record>; type State = keyof typeof COMMON_CONST.STATES; -/** - * See {@link module:src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx#withHash} for more information. - */ -const removeHash = (arg: string): string => arg.replace(/-hash-.*$/, ''); - /** * Extracts the 'state' (default) query parameter from the route/ url and validates it against COMMON_CONST.STATES, returning its ISO code or `undefined`. * Example 1: Url: https://new.expensify.com/settings/profile/address?state=MO Returns: MO @@ -24,5 +19,5 @@ export default function useGeographicalStateFromRoute(stateParamName = 'state'): if (!stateFromUrlTemp) { return; } - return COMMON_CONST.STATES[removeHash(stateFromUrlTemp) as State].stateISO; + return COMMON_CONST.STATES[stateFromUrlTemp as State].stateISO; } diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 1a9a0fab906..275400b27cc 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -56,9 +56,6 @@ function StateSelectionPage() { (option: CountryData) => { const backTo = params?.backTo ?? ''; - // Add hash to param for rare cases, such as in ReimbursementAccountPage (currentStep = CONST.BANK_ACCOUNT.STEP.COMPANY), when the URL on the form page (which has the StateSelector component) already includes a state param. If the form then updates the StateInput with state data from another source and the user attempts to use StateSelector to select the same state present in the URL, it will cause the StateSelector not to detect the state change, as the URL remains the same. - const withHash = (arg: string): string => `${arg}-hash-${Math.random().toString(36).substring(2, 8)}`; - // Determine navigation action based on "backTo" presence and route stack length. if (navigation.getState()?.routes.length === 1) { // If this is the only page in the navigation stack (examples include direct navigation to this page via URL or page reload). @@ -67,11 +64,11 @@ function StateSelectionPage() { Navigation.goBack(); } else { // "backTo" provided: navigate back to "backTo" with state parameter. - Navigation.goBack(appendParam(backTo, 'state', withHash(option.value)) as Route); + Navigation.goBack(appendParam(backTo, 'state', option.value) as Route); } } else if (!_.isEmpty(backTo)) { // Most common case: Navigation stack has multiple routes and "backTo" is defined: navigate to "backTo" with state parameter. - Navigation.navigate(appendParam(backTo, 'state', withHash(option.value)) as Route); + Navigation.navigate(appendParam(backTo, 'state', option.value) as Route); } else { // This is a fallback block and should never execute if StateSelector is correctly appending the "backTo" route. // Navigation stack has multiple routes but no "backTo" defined: default back navigation. From 713a91ce5f252da0afe33a88a3ddf030504700bb Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 13 Mar 2024 03:31:40 +0530 Subject: [PATCH 46/57] Sync URL 'state' param with StateSelector value --- src/components/StateSelector.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 3aff71cd9f9..5e1e8591498 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -69,6 +69,7 @@ function StateSelector( // This will cause the form to revalidate and remove any error related to state name onInputChange(stateCode); } + Navigation.setParams({state: stateCode}); setStateToDisplay(stateCode); // eslint-disable-next-line react-hooks/exhaustive-deps From 2a76bed788590cad8f75e07a502b4ed5d745e5b9 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Thu, 14 Mar 2024 03:48:47 +0530 Subject: [PATCH 47/57] change moneyrequest/state url to request/state Co-authored-by: Rajat --- src/ROUTES.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 255afc50a86..f0be85bf553 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -431,7 +431,7 @@ const ROUTES = { }, MONEY_REQUEST_STATE_SELECTOR: { - route: 'moneyrequest/state', + route: 'request/state', getRoute: (state?: string, backTo?: string, label?: string) => `${getUrlWithBackToParam(`moneyrequest/state${state ? `?state=${encodeURIComponent(state)}` : ''}`, backTo)}${ From 49f2e467e50da30687754138f3da0f8e01e90e45 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Thu, 14 Mar 2024 03:48:59 +0530 Subject: [PATCH 48/57] change moneyrequest/state url to request/state Co-authored-by: Rajat --- src/ROUTES.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index f0be85bf553..b19ba22d2bf 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -434,7 +434,7 @@ const ROUTES = { route: 'request/state', getRoute: (state?: string, backTo?: string, label?: string) => - `${getUrlWithBackToParam(`moneyrequest/state${state ? `?state=${encodeURIComponent(state)}` : ''}`, backTo)}${ + `${getUrlWithBackToParam(`request/state${state ? `?state=${encodeURIComponent(state)}` : ''}`, backTo)}${ // the label param can be an empty string so we cannot use a nullish ?? operator // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing label ? `${backTo || state ? '&' : '?'}label=${encodeURIComponent(label)}` : '' From 58d92f15b129ea7580164e60ef0e970c432f89e5 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 20 Mar 2024 18:39:39 +0530 Subject: [PATCH 49/57] StateSelector bugfix - for issue causing url to not update in browser url bar --- src/components/StateSelector.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 5e1e8591498..97c8cffc3c4 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -69,7 +69,9 @@ function StateSelector( // This will cause the form to revalidate and remove any error related to state name onInputChange(stateCode); } - Navigation.setParams({state: stateCode}); + + // This enforces the state selector to use the state from address instead of the state from URL + Navigation.setParams({state: undefined}); setStateToDisplay(stateCode); // eslint-disable-next-line react-hooks/exhaustive-deps From af6c3c5dd094bdd4657eb5df943822e7e1a67d64 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 20 Mar 2024 23:36:41 +0530 Subject: [PATCH 50/57] useGeographicalStateFromRoute - check for state before accessing stateISO --- src/hooks/useGeographicalStateFromRoute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts index 3e85bfc2a36..13936ee78f5 100644 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ b/src/hooks/useGeographicalStateFromRoute.ts @@ -19,5 +19,5 @@ export default function useGeographicalStateFromRoute(stateParamName = 'state'): if (!stateFromUrlTemp) { return; } - return COMMON_CONST.STATES[stateFromUrlTemp as State].stateISO; + return COMMON_CONST.STATES[stateFromUrlTemp as State]?.stateISO; } From 2619a29d6ef297a1c357aadfa9dc0ad06cd68272 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 23 Mar 2024 17:45:46 +0530 Subject: [PATCH 51/57] Fix: Inconsistent validation error for required field in home address --- src/components/StateSelector.tsx | 45 ++++++++++++++------------------ 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 97c8cffc3c4..1dbbdcadbe3 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -1,5 +1,6 @@ +import {useIsFocused} from '@react-navigation/native'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import React, {useEffect, useState} from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; @@ -19,7 +20,7 @@ type StateSelectorProps = { errorText?: MaybePhraseKey; /** Current selected state */ - value?: State; + value?: State | ''; /** Callback to call when the input changes */ onInputChange?: (value: string) => void; @@ -30,54 +31,47 @@ type StateSelectorProps = { /** Any additional styles to apply */ wrapperStyle?: MenuItemProps['wrapperStyle']; - /** whether to use state from url, for cases when url value is passed from parent */ - shouldUseStateFromUrl?: boolean; + /** Callback to call when the picker modal is dismissed */ + onBlur?: () => void; /** object to get route details from */ stateSelectorRoute?: typeof ROUTES.SETTINGS_ADDRESS_STATE | typeof ROUTES.MONEY_REQUEST_STATE_SELECTOR; }; function StateSelector( - {errorText, shouldUseStateFromUrl = true, value: stateCode, label, onInputChange, wrapperStyle, stateSelectorRoute = ROUTES.SETTINGS_ADDRESS_STATE}: StateSelectorProps, + {errorText, onBlur, value: stateCode, label, onInputChange, wrapperStyle, stateSelectorRoute = ROUTES.SETTINGS_ADDRESS_STATE}: StateSelectorProps, ref: ForwardedRef, ) { const styles = useThemeStyles(); const {translate} = useLocalize(); const stateFromUrl = useGeographicalStateFromRoute(); - const [stateToDisplay, setStateToDisplay] = useState(''); - useEffect(() => { - if (!shouldUseStateFromUrl || !stateFromUrl) { - return; - } + const didOpenStateSelector = useRef(false); + const isFocused = useIsFocused(); - // This will cause the form to revalidate and remove any error related to country name - if (onInputChange) { - onInputChange(stateFromUrl); + useEffect(() => { + if (isFocused && didOpenStateSelector.current) { + didOpenStateSelector.current = false; + if (!stateFromUrl) { + onBlur?.(); + } } - setStateToDisplay(stateFromUrl); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [stateFromUrl, shouldUseStateFromUrl]); - useEffect(() => { - if (!stateCode) { + if (!stateFromUrl) { return; } + // This will cause the form to revalidate and remove any error related to country name if (onInputChange) { - // This will cause the form to revalidate and remove any error related to state name - onInputChange(stateCode); + onInputChange(stateFromUrl); } - // This enforces the state selector to use the state from address instead of the state from URL Navigation.setParams({state: undefined}); - setStateToDisplay(stateCode); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [stateCode]); + }, [stateFromUrl, onBlur, isFocused]); - const title = stateToDisplay && Object.keys(COMMON_CONST.STATES).includes(stateToDisplay) ? translate(`allStates.${stateToDisplay}.stateName`) : ''; + const title = stateCode && Object.keys(COMMON_CONST.STATES).includes(stateCode) ? translate(`allStates.${stateCode}.stateName`) : ''; const descStyle = title.length === 0 ? styles.textNormal : null; return ( @@ -92,6 +86,7 @@ function StateSelector( description={label || translate('common.state')} onPress={() => { const activeRoute = Navigation.getActiveRoute(); + didOpenStateSelector.current = true; Navigation.navigate(stateSelectorRoute.getRoute(stateCode, activeRoute, label)); }} wrapperStyle={wrapperStyle} From f48a7fca7872d85b34d2de1978bcba2780b6edde Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Sat, 23 Mar 2024 17:59:47 +0530 Subject: [PATCH 52/57] Remove unused code: StateSelector --- src/components/StateSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 1dbbdcadbe3..4d278eddc09 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -1,6 +1,6 @@ import {useIsFocused} from '@react-navigation/native'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import React, {useEffect, useRef, useState} from 'react'; +import React, {useEffect, useRef} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; From 4efccc0d43af2788a24b767165a483bb21ea213e Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 25 Mar 2024 06:26:45 +0530 Subject: [PATCH 53/57] StateSelectionPage: Fix issue of page crash when country is changed multiple times --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 275400b27cc..d78fd35aa0a 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -2,6 +2,7 @@ import {useNavigation, useRoute} from '@react-navigation/native'; import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import _ from 'lodash'; import React, {useCallback, useMemo, useState} from 'react'; +import {View} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; @@ -100,6 +101,8 @@ function StateSelectionPage() { Navigation.goBack(backToRoute); }} /> + {/* This empty, non-harmful view fixes the issue with SelectionList scrolling and shouldUseDynamicMaxToRenderPerBatch. It can be removed without consequences if a solution for SelectionList is found. See comment https://github.com/Expensify/App/pull/36770#issuecomment-2017028096*/} + Date: Mon, 25 Mar 2024 06:42:13 +0530 Subject: [PATCH 54/57] es lint comment formatting --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index d78fd35aa0a..c294fba7574 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -101,7 +101,7 @@ function StateSelectionPage() { Navigation.goBack(backToRoute); }} /> - {/* This empty, non-harmful view fixes the issue with SelectionList scrolling and shouldUseDynamicMaxToRenderPerBatch. It can be removed without consequences if a solution for SelectionList is found. See comment https://github.com/Expensify/App/pull/36770#issuecomment-2017028096*/} + { /* This empty, non-harmful view fixes the issue with SelectionList scrolling and shouldUseDynamicMaxToRenderPerBatch. It can be removed without consequences if a solution for SelectionList is found. See comment https://github.com/Expensify/App/pull/36770#issuecomment-2017028096*/} Date: Mon, 25 Mar 2024 19:58:38 +0530 Subject: [PATCH 55/57] eslint Formatting fix --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index c294fba7574..30db854e1b1 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -101,7 +101,7 @@ function StateSelectionPage() { Navigation.goBack(backToRoute); }} /> - { /* This empty, non-harmful view fixes the issue with SelectionList scrolling and shouldUseDynamicMaxToRenderPerBatch. It can be removed without consequences if a solution for SelectionList is found. See comment https://github.com/Expensify/App/pull/36770#issuecomment-2017028096*/} + { /* This empty, non-harmful view fixes the issue with SelectionList scrolling and shouldUseDynamicMaxToRenderPerBatch. It can be removed without consequences if a solution for SelectionList is found. See comment https://github.com/Expensify/App/pull/36770#issuecomment-2017028096 */} Date: Mon, 25 Mar 2024 20:11:55 +0530 Subject: [PATCH 56/57] prettier --- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 30db854e1b1..4e6339d9d86 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -101,7 +101,7 @@ function StateSelectionPage() { Navigation.goBack(backToRoute); }} /> - { /* This empty, non-harmful view fixes the issue with SelectionList scrolling and shouldUseDynamicMaxToRenderPerBatch. It can be removed without consequences if a solution for SelectionList is found. See comment https://github.com/Expensify/App/pull/36770#issuecomment-2017028096 */} + {/* This empty, non-harmful view fixes the issue with SelectionList scrolling and shouldUseDynamicMaxToRenderPerBatch. It can be removed without consequences if a solution for SelectionList is found. See comment https://github.com/Expensify/App/pull/36770#issuecomment-2017028096 */} Date: Sat, 30 Mar 2024 04:56:43 +0530 Subject: [PATCH 57/57] Add explanatory comments to StateSelector --- src/components/StateSelector.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 4d278eddc09..aa458834c8a 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -50,6 +50,7 @@ function StateSelector( const isFocused = useIsFocused(); useEffect(() => { + // Check if the state selector was opened and no value was selected, triggering onBlur to display an error if (isFocused && didOpenStateSelector.current) { didOpenStateSelector.current = false; if (!stateFromUrl) { @@ -57,15 +58,18 @@ function StateSelector( } } + // If no state is selected from the URL, exit the effect early to avoid further processing. if (!stateFromUrl) { return; } - // This will cause the form to revalidate and remove any error related to country name + // If a state is selected, invoke `onInputChange` to update the form and clear any validation errors related to the state selection. if (onInputChange) { onInputChange(stateFromUrl); } + // Clears the `state` parameter from the URL to ensure the component state is driven by the parent component rather than URL parameters. + // This helps prevent issues where the component might not update correctly if the state is controlled by both the parent and the URL. Navigation.setParams({state: undefined}); // eslint-disable-next-line react-hooks/exhaustive-deps