From 3b9cc3bc5dd5381ca859c0220282ba382cd70da3 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Mon, 10 Jun 2024 11:54:29 +0200 Subject: [PATCH 1/9] Bring back refactored AddressPage --- src/components/AddressForm.tsx | 1 + src/components/CountrySelector.tsx | 28 +++- src/components/StateSelector.tsx | 4 +- ...useGeographicalStateAndCountryFromRoute.ts | 27 ++++ src/hooks/useGeographicalStateFromRoute.ts | 23 --- .../ModalStackNavigators/index.tsx | 4 +- src/pages/AddressPage.tsx | 107 ++++++++++++ .../Profile/PersonalDetails/AddressPage.tsx | 153 ------------------ .../PersonalDetails/PersonalAddressPage.tsx | 61 +++++++ .../workspace/WorkspaceProfileAddressPage.tsx | 117 +++----------- 10 files changed, 248 insertions(+), 277 deletions(-) create mode 100644 src/hooks/useGeographicalStateAndCountryFromRoute.ts delete mode 100644 src/hooks/useGeographicalStateFromRoute.ts create mode 100644 src/pages/AddressPage.tsx delete mode 100644 src/pages/settings/Profile/PersonalDetails/AddressPage.tsx create mode 100644 src/pages/settings/Profile/PersonalDetails/PersonalAddressPage.tsx diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index 9ad4643e834a..89456ae944fa 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -183,6 +183,7 @@ function AddressForm({ InputComponent={CountrySelector} inputID={INPUT_IDS.COUNTRY} value={country} + onValueChange={onAddressChanged} shouldSaveDraft={shouldSaveDraft} /> diff --git a/src/components/CountrySelector.tsx b/src/components/CountrySelector.tsx index 002c0c6d4b0a..b8558c6bd92b 100644 --- a/src/components/CountrySelector.tsx +++ b/src/components/CountrySelector.tsx @@ -2,6 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import React, {forwardRef, useEffect, useRef} from 'react'; import type {ForwardedRef} from 'react'; import type {View} from 'react-native'; +import useGeographicalStateAndCountryFromRoute from '@hooks/useGeographicalStateAndCountryFromRoute'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import type {MaybePhraseKey} from '@libs/Localize'; @@ -32,6 +33,7 @@ type CountrySelectorProps = { function CountrySelector({errorText = '', value: countryCode, onInputChange = () => {}, onBlur}: CountrySelectorProps, ref: ForwardedRef) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const {country: countryFromUrl} = useGeographicalStateAndCountryFromRoute(); const title = countryCode ? translate(`allCountries.${countryCode}`) : ''; const countryTitleDescStyle = title.length === 0 ? styles.textNormal : null; @@ -39,12 +41,30 @@ function CountrySelector({errorText = '', value: countryCode, onInputChange = () const didOpenContrySelector = useRef(false); const isFocused = useIsFocused(); useEffect(() => { - if (!isFocused || !didOpenContrySelector.current) { + // Check if the country selector was opened and no value was selected, triggering onBlur to display an error + if (isFocused && didOpenContrySelector.current) { + didOpenContrySelector.current = false; + if (!countryFromUrl) { + onBlur?.(); + } + } + + // If no country is selected from the URL, exit the effect early to avoid further processing. + if (!countryFromUrl) { return; } - didOpenContrySelector.current = false; - onBlur?.(); - }, [isFocused, onBlur]); + + // If a country is selected, invoke `onInputChange` to update the form and clear any validation errors related to the country selection. + if (onInputChange) { + onInputChange(countryFromUrl); + } + + // Clears the `country` parameter from the URL to ensure the component country is driven by the parent component rather than URL parameters. + // This helps prevent issues where the component might not update correctly if the country is controlled by both the parent and the URL. + Navigation.setParams({country: undefined}); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [countryFromUrl, isFocused, onBlur]); useEffect(() => { // This will cause the form to revalidate and remove any error related to country name diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 67ba80c13ef8..8a1d85106cf3 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -3,7 +3,7 @@ import {CONST as COMMON_CONST} from 'expensify-common'; import React, {useEffect, useRef} from 'react'; import type {ForwardedRef} from 'react'; import type {View} from 'react-native'; -import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; +import useGeographicalStateAndCountryFromRoute from '@hooks/useGeographicalStateAndCountryFromRoute'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import type {MaybePhraseKey} from '@libs/Localize'; @@ -44,7 +44,7 @@ function StateSelector( ) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const stateFromUrl = useGeographicalStateFromRoute(); + const {state: stateFromUrl} = useGeographicalStateAndCountryFromRoute(); const didOpenStateSelector = useRef(false); const isFocused = useIsFocused(); diff --git a/src/hooks/useGeographicalStateAndCountryFromRoute.ts b/src/hooks/useGeographicalStateAndCountryFromRoute.ts new file mode 100644 index 000000000000..b94644bdd287 --- /dev/null +++ b/src/hooks/useGeographicalStateAndCountryFromRoute.ts @@ -0,0 +1,27 @@ +import {useRoute} from '@react-navigation/native'; +import {CONST as COMMON_CONST} from 'expensify-common'; +import CONST from '@src/CONST'; + +type State = keyof typeof COMMON_CONST.STATES; +type Country = keyof typeof CONST.ALL_COUNTRIES; +type StateAndCountry = {state?: State; country?: Country}; + +/** + * Extracts the 'state' and 'country' query parameters from the route/ url and validates it against COMMON_CONST.STATES and CONST.ALL_COUNTRIES. + * Example 1: Url: https://new.expensify.com/settings/profile/address?state=MO Returns: state=MO + * Example 2: Url: https://new.expensify.com/settings/profile/address?state=ASDF Returns: state=undefined + * Example 3: Url: https://new.expensify.com/settings/profile/address Returns: state=undefined + * Example 4: Url: https://new.expensify.com/settings/profile/address?state=MO-hash-a12341 Returns: state=MO + * Similarly for country parameter. + */ +export default function useGeographicalStateAndCountryFromRoute(stateParamName = 'state', countryParamName = 'country'): StateAndCountry { + const routeParams = useRoute().params as Record; + + const stateFromUrlTemp = routeParams?.[stateParamName] as string | undefined; + const countryFromUrlTemp = routeParams?.[countryParamName] as string | undefined; + + return { + state: COMMON_CONST.STATES[stateFromUrlTemp as State]?.stateISO, + country: Object.keys(CONST.ALL_COUNTRIES).find((country) => country === countryFromUrlTemp) as Country, + }; +} diff --git a/src/hooks/useGeographicalStateFromRoute.ts b/src/hooks/useGeographicalStateFromRoute.ts deleted file mode 100644 index 434d4c534d61..000000000000 --- a/src/hooks/useGeographicalStateFromRoute.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {useRoute} from '@react-navigation/native'; -import type {ParamListBase, RouteProp} from '@react-navigation/native'; -import {CONST as COMMON_CONST} from 'expensify-common'; - -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 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>(); - const stateFromUrlTemp = route.params?.[stateParamName] as string | undefined; - - if (!stateFromUrlTemp) { - return; - } - return COMMON_CONST.STATES[stateFromUrlTemp as State]?.stateISO; -} diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 807c938e21dd..6ab8398c11d7 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -181,7 +181,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Profile/TimezoneSelectPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.LEGAL_NAME]: () => require('../../../../pages/settings/Profile/PersonalDetails/LegalNamePage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.DATE_OF_BIRTH]: () => require('../../../../pages/settings/Profile/PersonalDetails/DateOfBirthPage').default as React.ComponentType, - [SCREENS.SETTINGS.PROFILE.ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/AddressPage').default as React.ComponentType, + [SCREENS.SETTINGS.PROFILE.ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/PersonalAddressPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY]: () => require('../../../../pages/settings/Profile/PersonalDetails/CountrySelectionPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.ADDRESS_STATE]: () => require('../../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: () => require('../../../../pages/settings/Profile/Contacts/ContactMethodsPage').default as React.ComponentType, @@ -195,7 +195,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/AppDownloadLinks').default as React.ComponentType, [SCREENS.SETTINGS.CONSOLE]: () => require('../../../../pages/settings/AboutPage/ConsolePage').default as React.ComponentType, [SCREENS.SETTINGS.SHARE_LOG]: () => require('../../../../pages/settings/AboutPage/ShareLogPage').default as React.ComponentType, - [SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/AddressPage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/PersonalAddressPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET.DOMAIN_CARD]: () => require('../../../../pages/settings/Wallet/ExpensifyCardPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD]: () => require('../../../../pages/settings/Wallet/ReportVirtualCardFraudPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET.CARD_ACTIVATE]: () => require('../../../../pages/settings/Wallet/ActivatePhysicalCardPage').default as React.ComponentType, diff --git a/src/pages/AddressPage.tsx b/src/pages/AddressPage.tsx new file mode 100644 index 000000000000..90711ebbab92 --- /dev/null +++ b/src/pages/AddressPage.tsx @@ -0,0 +1,107 @@ +import React, {useCallback, useEffect, useState} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import AddressForm from '@components/AddressForm'; +import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {FormOnyxValues} from '@src/components/Form/types'; +import type {Country} from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Address} from '@src/types/onyx/PrivatePersonalDetails'; + +type AddressPageProps = { + /** User's private personal details */ + address?: Address; + /** Whether app is loading */ + isLoadingApp: OnyxEntry; + /** Function to call when address form is submitted */ + updateAddress: (values: FormOnyxValues) => void; + /** Title of address page */ + title: string; +}; + +function AddressPage({title, address, updateAddress, isLoadingApp = true}: AddressPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + // Check if country is valid + const {street, street2} = address ?? {}; + const [currentCountry, setCurrentCountry] = useState(address?.country); + const [state, setState] = useState(address?.state); + const [city, setCity] = useState(address?.city); + const [zipcode, setZipcode] = useState(address?.zip); + + useEffect(() => { + if (!address) { + return; + } + setState(address.state); + setCurrentCountry(address.country); + setCity(address.city); + setZipcode(address.zip); + }, [address]); + + const handleAddressChange = useCallback((value: unknown, key: unknown) => { + const addressPart = value as string; + const addressPartKey = key as keyof Address; + + if (addressPartKey !== 'country' && addressPartKey !== 'state' && addressPartKey !== 'city' && addressPartKey !== 'zipPostCode') { + return; + } + if (addressPartKey === 'country') { + setCurrentCountry(addressPart as Country | ''); + setState(''); + setCity(''); + setZipcode(''); + return; + } + if (addressPartKey === 'state') { + setState(addressPart); + setCity(''); + setZipcode(''); + return; + } + if (addressPartKey === 'city') { + setCity(addressPart); + setZipcode(''); + return; + } + setZipcode(addressPart); + }, []); + + return ( + + Navigation.goBack()} + /> + {isLoadingApp ? ( + + ) : ( + + )} + + ); +} + +AddressPage.displayName = 'AddressPage'; + +export default AddressPage; diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx deleted file mode 100644 index fcb018348b72..000000000000 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; -import AddressForm from '@components/AddressForm'; -import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; -import useLocalize from '@hooks/useLocalize'; -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 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'; -import type {PrivatePersonalDetails} from '@src/types/onyx'; -import type {Address} from '@src/types/onyx/PrivatePersonalDetails'; - -type AddressPageOnyxProps = { - /** User's private personal details */ - privatePersonalDetails: OnyxEntry; - /** Whether app is loading */ - isLoadingApp: OnyxEntry; -}; - -type AddressPageProps = StackScreenProps & AddressPageOnyxProps; - -/** - * Submit form to update user's first and last legal name - * @param values - form input values - */ -function updateAddress(values: FormOnyxValues) { - PersonalDetails.updateAddress( - values.addressLine1?.trim() ?? '', - values.addressLine2?.trim() ?? '', - values.city.trim(), - values.state.trim(), - values?.zipPostCode?.trim().toUpperCase() ?? '', - values.country, - ); -} - -function AddressPage({privatePersonalDetails, route, isLoadingApp = true}: AddressPageProps) { - const styles = useThemeStyles(); - 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(); - const [currentCountry, setCurrentCountry] = useState(address?.country); - const [street1, street2] = (address?.street ?? '').split('\n'); - const [state, setState] = useState(address?.state); - const [city, setCity] = useState(address?.city); - const [zipcode, setZipcode] = useState(address?.zip); - - useEffect(() => { - if (!address) { - return; - } - setState(address.state); - setCurrentCountry(address.country); - setCity(address.city); - setZipcode(address.zip); - }, [address]); - - const handleAddressChange = useCallback((value: unknown, key: unknown) => { - const countryValue = value as Country | ''; - const addressKey = key as keyof Address; - - if (addressKey !== 'country' && addressKey !== 'state' && addressKey !== 'city' && addressKey !== 'zipPostCode') { - return; - } - if (addressKey === 'country') { - setCurrentCountry(countryValue); - setState(''); - setCity(''); - setZipcode(''); - return; - } - if (addressKey === 'state') { - setState(countryValue); - setCity(''); - setZipcode(''); - return; - } - if (addressKey === 'city') { - setCity(countryValue); - setZipcode(''); - return; - } - setZipcode(countryValue); - }, []); - - useEffect(() => { - if (!countryFromUrl) { - return; - } - handleAddressChange(countryFromUrl, 'country'); - }, [countryFromUrl, handleAddressChange]); - - useEffect(() => { - if (!stateFromUrl) { - return; - } - handleAddressChange(stateFromUrl, 'state'); - }, [handleAddressChange, stateFromUrl]); - - return ( - - Navigation.goBack()} - /> - {isLoadingApp ? ( - - ) : ( - - )} - - ); -} - -AddressPage.displayName = 'AddressPage'; - -export default withOnyx({ - privatePersonalDetails: { - key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, - }, - isLoadingApp: { - key: ONYXKEYS.IS_LOADING_APP, - }, -})(AddressPage); diff --git a/src/pages/settings/Profile/PersonalDetails/PersonalAddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/PersonalAddressPage.tsx new file mode 100644 index 000000000000..85402137fe6d --- /dev/null +++ b/src/pages/settings/Profile/PersonalDetails/PersonalAddressPage.tsx @@ -0,0 +1,61 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useMemo} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import useLocalize from '@hooks/useLocalize'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import AddressPage from '@pages/AddressPage'; +import * as PersonalDetails from '@userActions/PersonalDetails'; +import type {FormOnyxValues} from '@src/components/Form/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import type {PrivatePersonalDetails} from '@src/types/onyx'; + +type PersonalAddressPageOnyxProps = { + /** User's private personal details */ + privatePersonalDetails: OnyxEntry; + /** Whether app is loading */ + isLoadingApp: OnyxEntry; +}; + +type PersonalAddressPageProps = StackScreenProps & PersonalAddressPageOnyxProps; + +/** + * Submit form to update user's first and last legal name + * @param values - form input values + */ +function updateAddress(values: FormOnyxValues) { + PersonalDetails.updateAddress( + values.addressLine1?.trim() ?? '', + values.addressLine2?.trim() ?? '', + values.city.trim(), + values.state.trim(), + values?.zipPostCode?.trim().toUpperCase() ?? '', + values.country, + ); +} + +function PersonalAddressPage({privatePersonalDetails, isLoadingApp = true}: PersonalAddressPageProps) { + const {translate} = useLocalize(); + const address = useMemo(() => privatePersonalDetails?.address, [privatePersonalDetails]); + + return ( + + ); +} + +PersonalAddressPage.displayName = 'PersonalAddressPage'; + +export default withOnyx({ + privatePersonalDetails: { + key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, + }, + isLoadingApp: { + key: ONYXKEYS.IS_LOADING_APP, + }, +})(PersonalAddressPage); diff --git a/src/pages/workspace/WorkspaceProfileAddressPage.tsx b/src/pages/workspace/WorkspaceProfileAddressPage.tsx index c7cf00efb798..47793f7fb810 100644 --- a/src/pages/workspace/WorkspaceProfileAddressPage.tsx +++ b/src/pages/workspace/WorkspaceProfileAddressPage.tsx @@ -1,21 +1,14 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import {View} from 'react-native'; -import AddressForm from '@components/AddressForm'; +import React, {useMemo} from 'react'; import type {FormOnyxValues} from '@components/Form/types'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import AddressPage from '@pages/AddressPage'; import {updateAddress} from '@userActions/Policy/Policy'; -import type {Country} from '@src/CONST'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; +import type ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -import type {CompanyAddress} from '@src/types/onyx/Policy'; +import type {Address} from '@src/types/onyx/PrivatePersonalDetails'; import type {WithPolicyProps} from './withPolicy'; import withPolicy from './withPolicy'; @@ -23,18 +16,21 @@ type WorkspaceProfileAddressPagePolicyProps = WithPolicyProps; type WorkspaceProfileAddressPageProps = StackScreenProps & WorkspaceProfileAddressPagePolicyProps; -function WorkspaceProfileAddressPage({policy, route}: WorkspaceProfileAddressPageProps) { - const styles = useThemeStyles(); +function WorkspaceProfileAddressPage({policy}: WorkspaceProfileAddressPageProps) { const {translate} = useLocalize(); - const address = useMemo(() => policy?.address, [policy]); - const [currentCountry, setCurrentCountry] = useState(address?.country); - const [[street1, street2], setStreets] = useState((address?.addressStreet ?? '').split('\n')); - const [state, setState] = useState(address?.state); - const [city, setCity] = useState(address?.city); - const [zipcode, setZipcode] = useState(address?.zipCode); - - const countryFromUrlTemp = route?.params?.country; - const countryFromUrl = CONST.ALL_COUNTRIES[countryFromUrlTemp as keyof typeof CONST.ALL_COUNTRIES] ? countryFromUrlTemp : ''; + const address: Address = useMemo(() => { + const tempAddress = policy?.address; + const [street1, street2] = (tempAddress?.addressStreet ?? '').split('\n'); + const result = { + street: street1?.trim() ?? '', + street2: street2?.trim() ?? '', + city: tempAddress?.city?.trim() ?? '', + state: tempAddress?.state?.trim() ?? '', + zip: tempAddress?.zipCode?.trim().toUpperCase() ?? '', + country: tempAddress?.country ?? '', + }; + return result; + }, [policy]); const updatePolicyAddress = (values: FormOnyxValues) => { if (!policy) { @@ -50,78 +46,13 @@ function WorkspaceProfileAddressPage({policy, route}: WorkspaceProfileAddressPag Navigation.goBack(); }; - const handleAddressChange = useCallback((value: unknown, key: unknown) => { - const countryValue = value as Country | ''; - const addressKey = key as keyof CompanyAddress; - - if (addressKey !== 'country' && addressKey !== 'state' && addressKey !== 'city' && addressKey !== 'zipCode') { - return; - } - if (addressKey === 'country') { - setCurrentCountry(countryValue); - setState(''); - setCity(''); - setZipcode(''); - return; - } - if (addressKey === 'state') { - setState(countryValue); - setCity(''); - setZipcode(''); - return; - } - if (addressKey === 'city') { - setCity(countryValue); - setZipcode(''); - return; - } - setZipcode(countryValue); - }, []); - - useEffect(() => { - if (!address) { - return; - } - setStreets((address?.addressStreet ?? '').split('\n')); - setState(address.state); - setCurrentCountry(address.country); - setCity(address.city); - setZipcode(address.zipCode); - }, [address]); - - useEffect(() => { - if (!countryFromUrl) { - return; - } - handleAddressChange(countryFromUrl, 'country'); - }, [countryFromUrl, handleAddressChange]); - return ( - - Navigation.goBack()} - /> - - {translate('workspace.editor.addressContext')} - - - + ); } From 8b40d840e99c33792c79b83291ff646b3c99553b Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Thu, 13 Jun 2024 23:17:11 +0200 Subject: [PATCH 2/9] Remove file that was readded during main merge --- .../Profile/PersonalDetails/AddressPage.tsx | 153 ------------------ 1 file changed, 153 deletions(-) delete mode 100644 src/pages/settings/Profile/PersonalDetails/AddressPage.tsx diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx deleted file mode 100644 index 91a8b94537ab..000000000000 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; -import AddressForm from '@components/AddressForm'; -import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; -import useLocalize from '@hooks/useLocalize'; -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 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'; -import type {PrivatePersonalDetails} from '@src/types/onyx'; -import type {Address} from '@src/types/onyx/PrivatePersonalDetails'; - -type AddressPageOnyxProps = { - /** User's private personal details */ - privatePersonalDetails: OnyxEntry; - /** Whether app is loading */ - isLoadingApp: OnyxEntry; -}; - -type AddressPageProps = StackScreenProps & AddressPageOnyxProps; - -/** - * Submit form to update user's first and last legal name - * @param values - form input values - */ -function updateAddress(values: FormOnyxValues) { - PersonalDetails.updateAddress( - values.addressLine1?.trim() ?? '', - values.addressLine2?.trim() ?? '', - values.city.trim(), - values.state.trim(), - values?.zipPostCode?.trim().toUpperCase() ?? '', - values.country, - ); -} - -function AddressPage({privatePersonalDetails, route, isLoadingApp = true}: AddressPageProps) { - const styles = useThemeStyles(); - 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(); - const [currentCountry, setCurrentCountry] = useState(address?.country); - const [street1, street2] = (address?.street ?? '').split('\n'); - const [state, setState] = useState(address?.state); - const [city, setCity] = useState(address?.city); - const [zipcode, setZipcode] = useState(address?.zip); - - useEffect(() => { - if (!address) { - return; - } - setState(address.state); - setCurrentCountry(address.country); - setCity(address.city); - setZipcode(address.zip); - }, [address]); - - const handleAddressChange = useCallback((value: unknown, key: unknown) => { - const addressPart = value as string; - const addressPartKey = key as keyof Address; - - if (addressPartKey !== 'country' && addressPartKey !== 'state' && addressPartKey !== 'city' && addressPartKey !== 'zipPostCode') { - return; - } - if (addressPartKey === 'country') { - setCurrentCountry(addressPart as Country | ''); - setState(''); - setCity(''); - setZipcode(''); - return; - } - if (addressPartKey === 'state') { - setState(addressPart); - setCity(''); - setZipcode(''); - return; - } - if (addressPartKey === 'city') { - setCity(addressPart); - setZipcode(''); - return; - } - setZipcode(addressPart); - }, []); - - useEffect(() => { - if (!countryFromUrl) { - return; - } - handleAddressChange(countryFromUrl, 'country'); - }, [countryFromUrl, handleAddressChange]); - - useEffect(() => { - if (!stateFromUrl) { - return; - } - handleAddressChange(stateFromUrl, 'state'); - }, [handleAddressChange, stateFromUrl]); - - return ( - - Navigation.goBack()} - /> - {isLoadingApp ? ( - - ) : ( - - )} - - ); -} - -AddressPage.displayName = 'AddressPage'; - -export default withOnyx({ - privatePersonalDetails: { - key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, - }, - isLoadingApp: { - key: ONYXKEYS.IS_LOADING_APP, - }, -})(AddressPage); From d017bf560a9742c413271c03b2bb5c895b29416a Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Fri, 14 Jun 2024 12:03:50 +0200 Subject: [PATCH 3/9] Reintroduce old fix --- src/pages/AddressPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/AddressPage.tsx b/src/pages/AddressPage.tsx index 90711ebbab92..852c57595b70 100644 --- a/src/pages/AddressPage.tsx +++ b/src/pages/AddressPage.tsx @@ -42,7 +42,8 @@ function AddressPage({title, address, updateAddress, isLoadingApp = true}: Addre setCurrentCountry(address.country); setCity(address.city); setZipcode(address.zip); - }, [address]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [address?.state, address?.country, address?.city, address?.zip]); const handleAddressChange = useCallback((value: unknown, key: unknown) => { const addressPart = value as string; From fb48d1ffaec4d59498164c70f6d58528e518302b Mon Sep 17 00:00:00 2001 From: zfurtak Date: Fri, 21 Jun 2024 15:15:25 +0200 Subject: [PATCH 4/9] Fix deleting state --- src/components/CountrySelector.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/CountrySelector.tsx b/src/components/CountrySelector.tsx index b8558c6bd92b..f4fe811e229c 100644 --- a/src/components/CountrySelector.tsx +++ b/src/components/CountrySelector.tsx @@ -66,12 +66,6 @@ function CountrySelector({errorText = '', value: countryCode, onInputChange = () // eslint-disable-next-line react-hooks/exhaustive-deps }, [countryFromUrl, isFocused, onBlur]); - useEffect(() => { - // This will cause the form to revalidate and remove any error related to country name - onInputChange(countryCode); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [countryCode]); - return ( Date: Thu, 4 Jul 2024 08:23:48 +0200 Subject: [PATCH 5/9] Change path to PersonalAddressPage component --- .../Navigation/AppNavigator/ModalStackNavigators/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 4fd6251ec644..f447c5f0f19f 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -183,7 +183,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Profile/TimezoneSelectPage').default, [SCREENS.SETTINGS.PROFILE.LEGAL_NAME]: () => require('../../../../pages/settings/Profile/PersonalDetails/LegalNamePage').default, [SCREENS.SETTINGS.PROFILE.DATE_OF_BIRTH]: () => require('../../../../pages/settings/Profile/PersonalDetails/DateOfBirthPage').default, - [SCREENS.SETTINGS.PROFILE.ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/AddressPage').default, + [SCREENS.SETTINGS.PROFILE.ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/PersonalAddressPage').default, [SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY]: () => require('../../../../pages/settings/Profile/PersonalDetails/CountrySelectionPage').default, [SCREENS.SETTINGS.PROFILE.ADDRESS_STATE]: () => require('../../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default, [SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: () => require('../../../../pages/settings/Profile/Contacts/ContactMethodsPage').default, @@ -197,7 +197,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/AppDownloadLinks').default, [SCREENS.SETTINGS.CONSOLE]: () => require('../../../../pages/settings/AboutPage/ConsolePage').default, [SCREENS.SETTINGS.SHARE_LOG]: () => require('../../../../pages/settings/AboutPage/ShareLogPage').default, - [SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/AddressPage').default, + [SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/PersonalAddressPage').default, [SCREENS.SETTINGS.WALLET.DOMAIN_CARD]: () => require('../../../../pages/settings/Wallet/ExpensifyCardPage').default, [SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD]: () => require('../../../../pages/settings/Wallet/ReportVirtualCardFraudPage').default, [SCREENS.SETTINGS.WALLET.CARD_ACTIVATE]: () => require('../../../../pages/settings/Wallet/ActivatePhysicalCardPage').default, From f3ab72dd4432a0bb4b37fbf0506774b698a25a85 Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Thu, 4 Jul 2024 10:27:29 -0700 Subject: [PATCH 6/9] remove GBP as a currency for the subscription page --- src/CONST.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 9bba8d509642..dd996ff00d32 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5037,7 +5037,6 @@ const CONST = { PAYMENT_CARD_CURRENCY: { USD: 'USD', AUD: 'AUD', - GBP: 'GBP', NZD: 'NZD', }, From 0376bae063fabdba1bfbded858d66175f316901d Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Thu, 4 Jul 2024 10:36:01 -0700 Subject: [PATCH 7/9] remove instances where we use GBP --- .../useSubscriptionPossibleCostSavings.ts | 4 --- src/hooks/useSubscriptionPrice.ts | 10 -------- src/libs/actions/PaymentMethods.ts | 25 +++++-------------- 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/src/hooks/useSubscriptionPossibleCostSavings.ts b/src/hooks/useSubscriptionPossibleCostSavings.ts index ef92009549fe..059445ce002d 100644 --- a/src/hooks/useSubscriptionPossibleCostSavings.ts +++ b/src/hooks/useSubscriptionPossibleCostSavings.ts @@ -13,10 +13,6 @@ const POSSIBLE_COST_SAVINGS = { [CONST.POLICY.TYPE.TEAM]: 1400, [CONST.POLICY.TYPE.CORPORATE]: 3000, }, - [CONST.PAYMENT_CARD_CURRENCY.GBP]: { - [CONST.POLICY.TYPE.TEAM]: 800, - [CONST.POLICY.TYPE.CORPORATE]: 1400, - }, [CONST.PAYMENT_CARD_CURRENCY.NZD]: { [CONST.POLICY.TYPE.TEAM]: 1600, [CONST.POLICY.TYPE.CORPORATE]: 3200, diff --git a/src/hooks/useSubscriptionPrice.ts b/src/hooks/useSubscriptionPrice.ts index 0b71fe62c7c8..9279ff94757d 100644 --- a/src/hooks/useSubscriptionPrice.ts +++ b/src/hooks/useSubscriptionPrice.ts @@ -25,16 +25,6 @@ const SUBSCRIPTION_PRICES = { [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 1400, }, }, - [CONST.PAYMENT_CARD_CURRENCY.GBP]: { - [CONST.POLICY.TYPE.CORPORATE]: { - [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 700, - [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 1400, - }, - [CONST.POLICY.TYPE.TEAM]: { - [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 400, - [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 800, - }, - }, [CONST.PAYMENT_CARD_CURRENCY.NZD]: { [CONST.POLICY.TYPE.CORPORATE]: { [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 1600, diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts index d4713e580b64..3a5ac3d17c03 100644 --- a/src/libs/actions/PaymentMethods.ts +++ b/src/libs/actions/PaymentMethods.ts @@ -253,25 +253,12 @@ function addSubscriptionPaymentCard(cardData: { }, ]; - if (currency === CONST.PAYMENT_CARD_CURRENCY.GBP) { - // eslint-disable-next-line rulesdir/no-api-side-effects-method - API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.ADD_PAYMENT_CARD_GBR, parameters, {optimisticData, successData, failureData}).then((response) => { - if (response?.jsonCode !== CONST.JSON_CODE.SUCCESS) { - return; - } - // TODO 3ds flow will be done as a part https://github.com/Expensify/App/issues/42432 - // We will use this onyx key to open Modal and preview iframe. Potentially we can save the whole object which come from side effect - Onyx.set(ONYXKEYS.VERIFY_3DS_SUBSCRIPTION, (response as {authenticationLink: string}).authenticationLink); - }); - } else { - // eslint-disable-next-line rulesdir/no-multiple-api-calls - API.write(WRITE_COMMANDS.ADD_PAYMENT_CARD, parameters, { - optimisticData, - successData, - failureData, - }); - Navigation.goBack(); - } + API.write(WRITE_COMMANDS.ADD_PAYMENT_CARD, parameters, { + optimisticData, + successData, + failureData, + }); + Navigation.goBack(); } /** From db42a76a714b21c2658ba7c7ccd5224c7b6dceba Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Thu, 4 Jul 2024 10:43:29 -0700 Subject: [PATCH 8/9] lint --- src/libs/actions/PaymentMethods.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts index 3a5ac3d17c03..80c9b39141d8 100644 --- a/src/libs/actions/PaymentMethods.ts +++ b/src/libs/actions/PaymentMethods.ts @@ -13,7 +13,7 @@ import type { TransferWalletBalanceParams, UpdateBillingCurrencyParams, } from '@libs/API/parameters'; -import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as CardUtils from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; From 6c2372cf08dde2f65df6ea767d964ed6051944b0 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 4 Jul 2024 21:29:26 +0200 Subject: [PATCH 9/9] Fix eslint on main --- .../NetSuiteTokenInput/NetSuiteTokenInputPage.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx index f885d2dc9b70..41d428257163 100644 --- a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx +++ b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx @@ -1,5 +1,5 @@ import React, {useRef} from 'react'; -import type {ForwardedRef} from 'react'; +import type {ComponentType, ForwardedRef} from 'react'; import {View} from 'react-native'; import ConnectionLayout from '@components/ConnectionLayout'; import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; @@ -14,8 +14,10 @@ import CONST from '@src/CONST'; import NetSuiteTokenInputForm from './substeps/NetSuiteTokenInputForm'; import NetSuiteTokenSetupContent from './substeps/NetSuiteTokenSetupContent'; -const staticContentSteps = Array(4).fill(NetSuiteTokenSetupContent); -const tokenInputSteps: Array> = [...staticContentSteps, NetSuiteTokenInputForm]; +type SubStepWithPolicy = SubStepProps & {policyID: string}; + +const staticContentSteps = Array>(4).fill(NetSuiteTokenSetupContent); +const tokenInputSteps: Array> = [...staticContentSteps, NetSuiteTokenInputForm]; function NetSuiteTokenInputPage({policy}: WithPolicyConnectionsProps) { const policyID = policy?.id ?? '-1'; @@ -33,7 +35,7 @@ function NetSuiteTokenInputPage({policy}: WithPolicyConnectionsProps) { prevScreen, screenIndex, moveTo, - } = useSubStep({bodyContent: tokenInputSteps, startFrom: 0, onFinished: submit}); + } = useSubStep({bodyContent: tokenInputSteps, startFrom: 0, onFinished: submit}); const handleBackButtonPress = () => { if (screenIndex === 0) {