From dcd834b94b6c62510b1f36c054af85950b37212f Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 26 Jan 2024 12:03:42 +0100 Subject: [PATCH 01/13] ref: started working on migration of Profile pages --- .../Profile/PersonalDetails/{AddressPage.js => AddressPage.tsx} | 0 .../{CountrySelectionPage.js => CountrySelectionPage.tsx} | 0 .../PersonalDetails/{DateOfBirthPage.js => DateOfBirthPage.tsx} | 0 .../PersonalDetails/{LegalNamePage.js => LegalNamePage.tsx} | 0 ...rsonalDetailsInitialPage.js => PersonalDetailsInitialPage.tsx} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename src/pages/settings/Profile/PersonalDetails/{AddressPage.js => AddressPage.tsx} (100%) rename src/pages/settings/Profile/PersonalDetails/{CountrySelectionPage.js => CountrySelectionPage.tsx} (100%) rename src/pages/settings/Profile/PersonalDetails/{DateOfBirthPage.js => DateOfBirthPage.tsx} (100%) rename src/pages/settings/Profile/PersonalDetails/{LegalNamePage.js => LegalNamePage.tsx} (100%) rename src/pages/settings/Profile/PersonalDetails/{PersonalDetailsInitialPage.js => PersonalDetailsInitialPage.tsx} (100%) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx similarity index 100% rename from src/pages/settings/Profile/PersonalDetails/AddressPage.js rename to src/pages/settings/Profile/PersonalDetails/AddressPage.tsx diff --git a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.js b/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx similarity index 100% rename from src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.js rename to src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx diff --git a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.js b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx similarity index 100% rename from src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.js rename to src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.js b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx similarity index 100% rename from src/pages/settings/Profile/PersonalDetails/LegalNamePage.js rename to src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx diff --git a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.tsx similarity index 100% rename from src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js rename to src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.tsx From 7d88436edb3bbabdc4d4f9ab388f59a0c915f1ab Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 26 Jan 2024 19:56:36 +0100 Subject: [PATCH 02/13] ref: migrate AddressPage --- src/libs/Navigation/types.ts | 8 +- .../Profile/PersonalDetails/AddressPage.tsx | 82 +++++++------------ src/types/onyx/PrivatePersonalDetails.ts | 5 ++ 3 files changed, 42 insertions(+), 53 deletions(-) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 2371c764f42a..26f31c0e76d4 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -54,8 +54,12 @@ type SettingsNavigatorParamList = { [SCREENS.SETTINGS.PROFILE.PERSONAL_DETAILS.INITIAL]: undefined; [SCREENS.SETTINGS.PROFILE.PERSONAL_DETAILS.LEGAL_NAME]: undefined; [SCREENS.SETTINGS.PROFILE.PERSONAL_DETAILS.DATE_OF_BIRTH]: undefined; - [SCREENS.SETTINGS.PROFILE.PERSONAL_DETAILS.ADDRESS]: undefined; - [SCREENS.SETTINGS.PROFILE.PERSONAL_DETAILS.ADDRESS_COUNTRY]: undefined; + [SCREENS.SETTINGS.PROFILE.PERSONAL_DETAILS.ADDRESS]: { + country: string; + }; + [SCREENS.SETTINGS.PROFILE.PERSONAL_DETAILS.ADDRESS_COUNTRY]: { + country: string; + }; [SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: undefined; [SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS]: undefined; [SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD]: undefined; diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx index 6c883e7fa9d8..13e3f6513a80 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx @@ -1,6 +1,6 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +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'; @@ -10,67 +10,48 @@ import useLocalize from '@hooks/useLocalize'; import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails'; 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 ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {PrivatePersonalDetails} from '@src/types/onyx'; +import type {Address} from '@src/types/onyx/PrivatePersonalDetails'; -const propTypes = { - /* Onyx Props */ - +type AddressPageOnyxProps = { /** User's private personal details */ - privatePersonalDetails: PropTypes.shape({ - /** User's home address */ - address: PropTypes.shape({ - street: PropTypes.string, - city: PropTypes.string, - state: PropTypes.string, - zip: PropTypes.string, - country: PropTypes.string, - }), - }), - - /** Route from navigation */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** Currently selected country */ - country: PropTypes.string, - }), - }).isRequired, + privatePersonalDetails: OnyxEntry; }; -const defaultProps = { - privatePersonalDetails: { - address: { - street: '', - city: '', - state: '', - zip: '', - country: '', - }, - }, -}; +type AddressPageProps = StackScreenProps & AddressPageOnyxProps; /** * Submit form to update user's first and last legal name - * @param {Object} values - form input values + * @param values - form input values */ -function updateAddress(values) { - PersonalDetails.updateAddress(values.addressLine1.trim(), values.addressLine2.trim(), values.city.trim(), values.state.trim(), values.zipPostCode.trim().toUpperCase(), values.country); +function updateAddress(values: Address) { + PersonalDetails.updateAddress( + values.addressLine1?.trim() ?? '', + values.addressLine2?.trim() ?? '', + values.city.trim(), + values.state.trim(), + values?.zipPostCode?.trim().toUpperCase() ?? '', + values.country, + ); } -function AddressPage({privatePersonalDetails, route}) { +function AddressPage({privatePersonalDetails = {address: {street: '', city: '', state: '', zip: '', country: ''}}, route}: AddressPageProps) { const styles = useThemeStyles(); usePrivatePersonalDetails(); const {translate} = useLocalize(); - const address = useMemo(() => lodashGet(privatePersonalDetails, 'address') || {}, [privatePersonalDetails]); - const countryFromUrl = lodashGet(route, 'params.country'); - const [currentCountry, setCurrentCountry] = useState(address.country); - const isLoadingPersonalDetails = lodashGet(privatePersonalDetails, 'isLoading', true); - 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); + const address = useMemo(() => privatePersonalDetails?.address, [privatePersonalDetails]); + const countryFromUrl = route.params?.country; + const [currentCountry, setCurrentCountry] = useState(address?.country); + const isLoadingPersonalDetails = privatePersonalDetails?.isLoading ?? true; + 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) { @@ -82,7 +63,8 @@ function AddressPage({privatePersonalDetails, route}) { setZipcode(address.zip); }, [address]); - const handleAddressChange = useCallback((value, key) => { + const handleAddressChange = useCallback((value: string, key: keyof Address) => { + // TODO check if there is key zipPostCode if (key !== 'country' && key !== 'state' && key !== 'city' && key !== 'zipPostCode') { return; } @@ -144,11 +126,9 @@ function AddressPage({privatePersonalDetails, route}) { ); } -AddressPage.propTypes = propTypes; -AddressPage.defaultProps = defaultProps; AddressPage.displayName = 'AddressPage'; -export default withOnyx({ +export default withOnyx({ privatePersonalDetails: { key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, }, diff --git a/src/types/onyx/PrivatePersonalDetails.ts b/src/types/onyx/PrivatePersonalDetails.ts index 6ef5b75c4a0f..62e653fc83aa 100644 --- a/src/types/onyx/PrivatePersonalDetails.ts +++ b/src/types/onyx/PrivatePersonalDetails.ts @@ -4,6 +4,9 @@ type Address = { state: string; zip: string; country: string; + zipPostCode?: string; + addressLine1?: string; + addressLine2?: string; }; type PrivatePersonalDetails = { @@ -19,3 +22,5 @@ type PrivatePersonalDetails = { }; export default PrivatePersonalDetails; + +export type {Address}; From 75fb85f3afeda43370717fd89218f1cc2dc1c6b1 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 29 Jan 2024 12:19:18 +0100 Subject: [PATCH 03/13] ref: migrate DateOfBirthPage to TS --- src/components/Form/types.ts | 3 + src/libs/Navigation/types.ts | 1 + .../PersonalDetails/CountrySelectionPage.tsx | 57 +++++++------------ .../PersonalDetails/DateOfBirthPage.tsx | 54 ++++++------------ src/types/onyx/OnyxCommon.ts | 2 +- 5 files changed, 43 insertions(+), 74 deletions(-) diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 447f3205ad68..ff0823f2d67e 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -40,6 +40,9 @@ type BaseInputProps = { isFocused?: boolean; measureLayout?: (ref: unknown, callback: MeasureLayoutOnSuccessCallback) => void; focus?: () => void; + label?: string; + minDate?: Date; + maxDate?: Date; }; type InputWrapperProps = Omit & diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 2ea208e2b4d5..2c9b25c75b8f 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -58,6 +58,7 @@ type SettingsNavigatorParamList = { country: string; }; [SCREENS.SETTINGS.PROFILE.PERSONAL_DETAILS.ADDRESS_COUNTRY]: { + backTo?: string; country: string; }; [SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: undefined; diff --git a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx index 38c4d1eac449..4b38f2edfe6c 100644 --- a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx @@ -1,45 +1,30 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import type {StackScreenProps} from '@react-navigation/stack'; 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 type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import type {CountryData} from '@libs/searchCountryOptions'; import searchCountryOptions from '@libs/searchCountryOptions'; import StringUtils from '@libs/StringUtils'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import type {Route} from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; -const propTypes = { - /** Route from navigation */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** Currently selected country */ - country: PropTypes.string, +type CountrySelectionPageProps = StackScreenProps; - /** 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, -}; - -function CountrySelectionPage({route, navigation}) { +function CountrySelectionPage({route, navigation}: CountrySelectionPageProps) { const [searchValue, setSearchValue] = useState(''); const {translate} = useLocalize(); - const currentCountry = lodashGet(route, 'params.country'); + const currentCountry = route.params.country; const countries = useMemo( () => - _.map(_.keys(CONST.ALL_COUNTRIES), (countryISO) => { - const countryName = translate(`allCountries.${countryISO}`); + Object.keys(CONST.ALL_COUNTRIES).map((countryISO) => { + const countryName = translate(`allCountries.${countryISO}` as TranslationPaths); return { value: countryISO, keyForList: countryISO, @@ -55,19 +40,18 @@ function CountrySelectionPage({route, navigation}) { const headerMessage = searchValue.trim() && !searchResults.length ? translate('common.noResultsFound') : ''; const selectCountry = useCallback( - (option) => { - const backTo = lodashGet(route, 'params.backTo', ''); - + (option: CountryData) => { + const backTo = route.params.backTo ?? ''; // Check the navigation state and "backTo" parameter to decide navigation behavior - if (navigation.getState().routes.length === 1 && _.isEmpty(backTo)) { + if (navigation.getState().routes.length === 1 && !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) { + } else if (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(`${route.params.backTo}?country=${option.value}`); + Navigation.goBack(`${route.params.backTo}?country=${option.value}` as Route); } else { // Otherwise, navigate to the specific route defined in "backTo" with a country parameter - Navigation.navigate(`${route.params.backTo}?country=${option.value}`); + Navigation.navigate(`${route.params.backTo}?country=${option.value}` as Route); } }, [route, navigation], @@ -82,9 +66,9 @@ function CountrySelectionPage({route, navigation}) { title={translate('common.country')} shouldShowBackButton onBackButtonPress={() => { - const backTo = lodashGet(route, 'params.backTo', ''); - const backToRoute = backTo ? `${backTo}?country=${currentCountry}` : ''; - Navigation.goBack(backToRoute); + const backTo = route.params.backTo ?? ''; + const backToRoute = backTo ? (`${backTo}?country=${currentCountry}` as const) : undefined; + Navigation.goBack(backToRoute as Route); }} /> @@ -103,6 +87,5 @@ function CountrySelectionPage({route, navigation}) { } CountrySelectionPage.displayName = 'CountrySelectionPage'; -CountrySelectionPage.propTypes = propTypes; export default CountrySelectionPage; diff --git a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx index 9ca8daa69d04..8b561ca73831 100644 --- a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx @@ -1,7 +1,6 @@ import {subYears} from 'date-fns'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import DatePicker from '@components/DatePicker'; import FormProvider from '@components/Form/FormProvider'; @@ -9,51 +8,39 @@ import InputWrapper from '@components/Form/InputWrapper'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {PrivatePersonalDetails} from '@src/types/onyx'; -const propTypes = { - /* Onyx Props */ - +type DateOfBirthPageOnyxProps = { /** User's private personal details */ - privatePersonalDetails: PropTypes.shape({ - dob: PropTypes.string, - }), - - ...withLocalizePropTypes, -}; - -const defaultProps = { - privatePersonalDetails: { - dob: '', - }, + privatePersonalDetails: OnyxEntry; }; +type DateOfBirthPageProps = DateOfBirthPageOnyxProps; -function DateOfBirthPage({translate, privatePersonalDetails}) { +function DateOfBirthPage({privatePersonalDetails = {dob: ''}}: DateOfBirthPageProps) { + const {translate} = useLocalize(); const styles = useThemeStyles(); usePrivatePersonalDetails(); - const isLoadingPersonalDetails = lodashGet(privatePersonalDetails, 'isLoading', true); + const isLoadingPersonalDetails = privatePersonalDetails?.isLoading ?? true; /** - * @param {Object} values - * @param {String} values.dob - date of birth - * @returns {Object} - An object containing the errors for each inputID + * @returns An object containing the errors for each inputID */ - const validate = useCallback((values) => { + const validate = useCallback((values: PrivatePersonalDetails) => { const requiredFields = ['dob']; const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); const minimumAge = CONST.DATE_BIRTH.MIN_AGE; const maximumAge = CONST.DATE_BIRTH.MAX_AGE; - const dateError = ValidationUtils.getAgeRequirementError(values.dob, minimumAge, maximumAge); + const dateError = ValidationUtils.getAgeRequirementError(values.dob ?? '', minimumAge, maximumAge); if (values.dob && dateError) { errors.dob = dateError; @@ -86,7 +73,7 @@ function DateOfBirthPage({translate, privatePersonalDetails}) { InputComponent={DatePicker} inputID="dob" label={translate('common.date')} - defaultValue={privatePersonalDetails.dob || ''} + defaultValue={privatePersonalDetails?.dob ?? ''} minDate={subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE)} maxDate={subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE)} /> @@ -96,15 +83,10 @@ function DateOfBirthPage({translate, privatePersonalDetails}) { ); } -DateOfBirthPage.propTypes = propTypes; -DateOfBirthPage.defaultProps = defaultProps; DateOfBirthPage.displayName = 'DateOfBirthPage'; -export default compose( - withLocalize, - withOnyx({ - privatePersonalDetails: { - key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, - }, - }), -)(DateOfBirthPage); +export default withOnyx({ + privatePersonalDetails: { + key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, + }, +})(DateOfBirthPage); diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index b26dc167ed44..a7ecd9e36a74 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -8,7 +8,7 @@ type PendingFields = Record = Record; -type Errors = Record; +type Errors = Record | Array>>; type AvatarType = typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; From 5d83e8ed45b7f285e4305188c6858f255c7a0acd Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 29 Jan 2024 16:32:32 +0100 Subject: [PATCH 04/13] ref: move PersonalDetailInitialPage to TS, fix Errors type --- src/libs/ErrorUtils.ts | 12 +-- src/libs/PersonalDetailsUtils.ts | 12 ++- src/libs/ReportUtils.ts | 4 +- src/libs/ValidationUtils.ts | 4 +- .../PersonalDetails/DateOfBirthPage.tsx | 3 +- .../Profile/PersonalDetails/LegalNamePage.tsx | 94 +++++++++---------- .../PersonalDetailsInitialPage.tsx | 78 +++++---------- src/types/onyx/OnyxCommon.ts | 7 +- 8 files changed, 92 insertions(+), 122 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 2466a262b4b9..e0c74bd98fd3 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -1,6 +1,6 @@ import CONST from '@src/CONST'; import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; -import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; +import type {Error, ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; import type Response from '@src/types/onyx/Response'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; @@ -54,7 +54,7 @@ type OnyxDataWithErrors = { errors?: Errors | null; }; -function getLatestErrorMessage(onyxData: TOnyxData): string { +function getLatestErrorMessage(onyxData: TOnyxData): Error { const errors = onyxData.errors ?? {}; if (Object.keys(errors).length === 0) { @@ -70,7 +70,7 @@ type OnyxDataWithErrorFields = { errorFields?: ErrorFields; }; -function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Record { +function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Errors { const errorsForField = onyxData.errorFields?.[fieldName] ?? {}; if (Object.keys(errorsForField).length === 0) { @@ -82,7 +82,7 @@ function getLatestErrorField(onyxData return {[key]: errorsForField[key]}; } -function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Record { +function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Errors { const errorsForField = onyxData.errorFields?.[fieldName] ?? {}; if (Object.keys(errorsForField).length === 0) { @@ -94,14 +94,12 @@ function getEarliestErrorField(onyxDa return {[key]: errorsForField[key]}; } -type ErrorsList = Record; - /** * Method used to generate error message for given inputID * @param errors - An object containing current errors in the form * @param message - Message to assign to the inputID errors */ -function addErrorMessage(errors: ErrorsList, inputID?: string, message?: TKey) { +function addErrorMessage(errors: Errors, inputID?: string, message?: TKey) { if (!message || !inputID) { return; } diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 78cde95fb0e4..5cc5608f9ae8 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -173,11 +173,15 @@ function getStreetLines(street = '') { * @param privatePersonalDetails - details object * @returns - formatted address */ -function getFormattedAddress(privatePersonalDetails: PrivatePersonalDetails): string { - const {address} = privatePersonalDetails; - const [street1, street2] = getStreetLines(address?.street); +function getFormattedAddress(privatePersonalDetails: OnyxEntry): string { + const [street1, street2] = getStreetLines(privatePersonalDetails?.address?.street); const formattedAddress = - formatPiece(street1) + formatPiece(street2) + formatPiece(address?.city) + formatPiece(address?.state) + formatPiece(address?.zip) + formatPiece(address?.country); + formatPiece(street1) + + formatPiece(street2) + + formatPiece(privatePersonalDetails?.address?.city) + + formatPiece(privatePersonalDetails?.address?.state) + + formatPiece(privatePersonalDetails?.address?.zip) + + formatPiece(privatePersonalDetails?.address?.country); // Remove the last comma of the address return formattedAddress.trim().replace(/,$/, ''); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index be447cf2ea9d..84005383d664 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -194,7 +194,7 @@ type ReportRouteParams = { type ReportOfflinePendingActionAndErrors = { addWorkspaceRoomOrChatPendingAction: PendingAction | undefined; - addWorkspaceRoomOrChatErrors: Record | null | undefined; + addWorkspaceRoomOrChatErrors: Errors | null | undefined; }; type OptimisticApprovedReportAction = Pick< @@ -4118,7 +4118,7 @@ function isValidReportIDFromPath(reportIDFromPath: string): boolean { /** * Return the errors we have when creating a chat or a workspace room */ -function getAddWorkspaceRoomOrChatReportErrors(report: OnyxEntry): Record | null | undefined { +function getAddWorkspaceRoomOrChatReportErrors(report: OnyxEntry): Errors | null | undefined { // We are either adding a workspace room, or we're creating a chat, it isn't possible for both of these to have errors for the same report at the same time, so // simply looking up the first truthy value will get the relevant property if it's set. return report?.errorFields?.addWorkspaceRoom ?? report?.errorFields?.createChat; diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index 7eff51c354df..7ba43f1f5c98 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -73,7 +73,7 @@ function isValidPastDate(date: string | Date): boolean { /** * Used to validate a value that is "required". */ -function isRequiredFulfilled(value: string | Date | unknown[] | Record): boolean { +function isRequiredFulfilled(value: string | boolean | Date | unknown[] | Record): boolean { if (typeof value === 'string') { return !StringUtils.isEmptyString(value); } @@ -174,7 +174,7 @@ function meetsMaximumAgeRequirement(date: string): boolean { /** * Validate that given date is in a specified range of years before now. */ -function getAgeRequirementError(date: string, minimumAge: number, maximumAge: number): string | Array> { +function getAgeRequirementError(date: string, minimumAge: number, maximumAge: number): string | [string, Record] { const currentDate = startOfDay(new Date()); const testDate = parse(date, CONST.DATE.FNS_FORMAT_STRING, currentDate); diff --git a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx index 8b561ca73831..7269017feca0 100644 --- a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx @@ -18,6 +18,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {PrivatePersonalDetails} from '@src/types/onyx'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; type DateOfBirthPageOnyxProps = { /** User's private personal details */ @@ -36,7 +37,7 @@ function DateOfBirthPage({privatePersonalDetails = {dob: ''}}: DateOfBirthPagePr */ const validate = useCallback((values: PrivatePersonalDetails) => { const requiredFields = ['dob']; - const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); + const errors = ValidationUtils.getFieldRequiredErrors(values as Errors, requiredFields); const minimumAge = CONST.DATE_BIRTH.MIN_AGE; const maximumAge = CONST.DATE_BIRTH.MAX_AGE; diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx index 365ea62184ab..34ebe7af3c42 100644 --- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx @@ -1,76 +1,71 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {PrivatePersonalDetails} from '@src/types/onyx'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; -const propTypes = { - /* Onyx Props */ - +type LegalNamePageOnyxProps = { /** User's private personal details */ - privatePersonalDetails: PropTypes.shape({ - legalFirstName: PropTypes.string, - legalLastName: PropTypes.string, - }), - - ...withLocalizePropTypes, + privatePersonalDetails: OnyxEntry; }; -const defaultProps = { - privatePersonalDetails: { - legalFirstName: '', - legalLastName: '', - }, -}; +type LegalNamePageProps = LegalNamePageOnyxProps; -const updateLegalName = (values) => { - PersonalDetails.updateLegalName(values.legalFirstName.trim(), values.legalLastName.trim()); +const updateLegalName = (values: PrivatePersonalDetails) => { + PersonalDetails.updateLegalName(values.legalFirstName?.trim() ?? '', values.legalLastName?.trim() ?? ''); }; -function LegalNamePage(props) { +function LegalNamePage({privatePersonalDetails = {legalFirstName: '', legalLastName: ''}}: LegalNamePageProps) { const styles = useThemeStyles(); + const {translate} = useLocalize(); usePrivatePersonalDetails(); - const legalFirstName = lodashGet(props.privatePersonalDetails, 'legalFirstName', ''); - const legalLastName = lodashGet(props.privatePersonalDetails, 'legalLastName', ''); - const isLoadingPersonalDetails = lodashGet(props.privatePersonalDetails, 'isLoading', true); + const legalFirstName = privatePersonalDetails?.legalFirstName ?? ''; + const legalLastName = privatePersonalDetails?.legalLastName ?? ''; + const isLoadingPersonalDetails = privatePersonalDetails?.isLoading ?? true; - const validate = useCallback((values) => { - const errors = {}; + const validate = useCallback((values: PrivatePersonalDetails) => { + const errors: Errors = {}; - if (!ValidationUtils.isValidLegalName(values.legalFirstName)) { + if (!ValidationUtils.isValidLegalName(values.legalFirstName ?? '')) { ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'privatePersonalDetails.error.hasInvalidCharacter'); - } else if (_.isEmpty(values.legalFirstName)) { + } else if (values.legalFirstName === '') { errors.legalFirstName = 'common.error.fieldRequired'; } - if (values.legalFirstName.length > CONST.LEGAL_NAME.MAX_LENGTH) { - ErrorUtils.addErrorMessage(errors, 'legalFirstName', ['common.error.characterLimitExceedCounter', {length: values.legalFirstName.length, limit: CONST.LEGAL_NAME.MAX_LENGTH}]); + if ((values.legalFirstName?.length ?? 0) > CONST.LEGAL_NAME.MAX_LENGTH) { + ErrorUtils.addErrorMessage(errors, 'legalFirstName', [ + 'common.error.characterLimitExceedCounter', + {length: values.legalFirstName?.length, limit: CONST.LEGAL_NAME.MAX_LENGTH}, + ] as unknown as TranslationPaths); } - if (!ValidationUtils.isValidLegalName(values.legalLastName)) { + if (!ValidationUtils.isValidLegalName(values.legalLastName ?? '')) { ErrorUtils.addErrorMessage(errors, 'legalLastName', 'privatePersonalDetails.error.hasInvalidCharacter'); - } else if (_.isEmpty(values.legalLastName)) { + } else if (values.legalLastName === '') { errors.legalLastName = 'common.error.fieldRequired'; } - if (values.legalLastName.length > CONST.LEGAL_NAME.MAX_LENGTH) { - ErrorUtils.addErrorMessage(errors, 'legalLastName', ['common.error.characterLimitExceedCounter', {length: values.legalLastName.length, limit: CONST.LEGAL_NAME.MAX_LENGTH}]); + if ((values.legalLastName?.length ?? 0) > CONST.LEGAL_NAME.MAX_LENGTH) { + ErrorUtils.addErrorMessage(errors, 'legalLastName', [ + 'common.error.characterLimitExceedCounter', + {length: values.legalLastName?.length, limit: CONST.LEGAL_NAME.MAX_LENGTH}, + ] as unknown as TranslationPaths); } return errors; @@ -83,7 +78,7 @@ function LegalNamePage(props) { testID={LegalNamePage.displayName} > Navigation.goBack(ROUTES.SETTINGS_PERSONAL_DETAILS)} /> {isLoadingPersonalDetails ? ( @@ -94,7 +89,7 @@ function LegalNamePage(props) { formID={ONYXKEYS.FORMS.LEGAL_NAME_FORM} validate={validate} onSubmit={updateLegalName} - submitButtonText={props.translate('common.save')} + submitButtonText={translate('common.save')} enabledWhenOffline > @@ -102,8 +97,8 @@ function LegalNamePage(props) { InputComponent={TextInput} inputID="legalFirstName" name="lfname" - label={props.translate('privatePersonalDetails.legalFirstName')} - aria-label={props.translate('privatePersonalDetails.legalFirstName')} + label={translate('privatePersonalDetails.legalFirstName')} + aria-label={translate('privatePersonalDetails.legalFirstName')} role={CONST.ROLE.PRESENTATION} defaultValue={legalFirstName} maxLength={CONST.LEGAL_NAME.MAX_LENGTH + CONST.SEARCH_MAX_LENGTH} @@ -115,8 +110,8 @@ function LegalNamePage(props) { InputComponent={TextInput} inputID="legalLastName" name="llname" - label={props.translate('privatePersonalDetails.legalLastName')} - aria-label={props.translate('privatePersonalDetails.legalLastName')} + label={translate('privatePersonalDetails.legalLastName')} + aria-label={translate('privatePersonalDetails.legalLastName')} role={CONST.ROLE.PRESENTATION} defaultValue={legalLastName} maxLength={CONST.LEGAL_NAME.MAX_LENGTH + CONST.SEARCH_MAX_LENGTH} @@ -129,15 +124,10 @@ function LegalNamePage(props) { ); } -LegalNamePage.propTypes = propTypes; -LegalNamePage.defaultProps = defaultProps; LegalNamePage.displayName = 'LegalNamePage'; -export default compose( - withLocalize, - withOnyx({ - privatePersonalDetails: { - key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, - }, - }), -)(LegalNamePage); +export default withOnyx({ + privatePersonalDetails: { + key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, + }, +})(LegalNamePage); diff --git a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.tsx b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.tsx index cf6887b7e04c..de3c754c3af1 100644 --- a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.tsx @@ -1,72 +1,52 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; import {ScrollView, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import {withNetwork} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {PrivatePersonalDetails} from '@src/types/onyx'; -const propTypes = { - /* Onyx Props */ - +type PersonalDetailsInitialPageOnyxProps = { /** User's private personal details */ - privatePersonalDetails: PropTypes.shape({ - legalFirstName: PropTypes.string, - legalLastName: PropTypes.string, - dob: PropTypes.string, - - /** User's home address */ - address: PropTypes.shape({ - street: PropTypes.string, - city: PropTypes.string, - state: PropTypes.string, - zip: PropTypes.string, - country: PropTypes.string, - }), - }), - - ...withLocalizePropTypes, + privatePersonalDetails: OnyxEntry; }; -const defaultProps = { - privatePersonalDetails: { +type PersonalDetailsInitialPageProps = PersonalDetailsInitialPageOnyxProps; + +function PersonalDetailsInitialPage({ + privatePersonalDetails = { legalFirstName: '', legalLastName: '', dob: '', address: { street: '', - street2: '', city: '', state: '', zip: '', country: '', }, }, -}; - -function PersonalDetailsInitialPage(props) { +}: PersonalDetailsInitialPageProps) { const styles = useThemeStyles(); + const {translate} = useLocalize(); usePrivatePersonalDetails(); - const privateDetails = props.privatePersonalDetails || {}; - const legalName = `${privateDetails.legalFirstName || ''} ${privateDetails.legalLastName || ''}`.trim(); - const isLoadingPersonalDetails = lodashGet(props.privatePersonalDetails, 'isLoading', true); + const legalName = `${privatePersonalDetails?.legalFirstName ?? ''} ${privatePersonalDetails?.legalLastName ?? ''}`.trim(); + const isLoadingPersonalDetails = privatePersonalDetails?.isLoading ?? true; return ( Navigation.goBack(ROUTES.SETTINGS_PROFILE)} /> {isLoadingPersonalDetails ? ( @@ -75,24 +55,24 @@ function PersonalDetailsInitialPage(props) { - {props.translate('privatePersonalDetails.privateDataMessage')} + {translate('privatePersonalDetails.privateDataMessage')} Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_LEGAL_NAME)} /> Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH)} - titleStyle={[styles.flex1]} + titleStyle={styles.flex1} /> Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS)} /> @@ -103,16 +83,10 @@ function PersonalDetailsInitialPage(props) { ); } -PersonalDetailsInitialPage.propTypes = propTypes; -PersonalDetailsInitialPage.defaultProps = defaultProps; PersonalDetailsInitialPage.displayName = 'PersonalDetailsInitialPage'; -export default compose( - withLocalize, - withOnyx({ - privatePersonalDetails: { - key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, - }, - }), - withNetwork(), -)(PersonalDetailsInitialPage); +export default withOnyx({ + privatePersonalDetails: { + key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, + }, +})(PersonalDetailsInitialPage); diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index a7ecd9e36a74..8b95824aeae7 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -1,4 +1,5 @@ import type {ValueOf} from 'type-fest'; +import type {MaybePhraseKey} from '@libs/Localize'; import type {AvatarSource} from '@libs/UserUtils'; import type CONST from '@src/CONST'; @@ -8,7 +9,9 @@ type PendingFields = Record = Record; -type Errors = Record | Array>>; +type Error = string | MaybePhraseKey; + +type Errors = Record; type AvatarType = typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; @@ -29,4 +32,4 @@ type Icon = { fallbackIcon?: AvatarSource; }; -export type {Icon, PendingAction, PendingFields, ErrorFields, Errors, AvatarType}; +export type {Icon, PendingAction, PendingFields, ErrorFields, Errors, AvatarType, Error}; From 71889c661466150f842a8308068af602b28bf732 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 30 Jan 2024 14:20:13 +0100 Subject: [PATCH 05/13] fix: typecheck --- src/libs/ErrorUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index e0c74bd98fd3..a4aa899e0e71 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -99,7 +99,7 @@ function getEarliestErrorField(onyxDa * @param errors - An object containing current errors in the form * @param message - Message to assign to the inputID errors */ -function addErrorMessage(errors: Errors, inputID?: string, message?: TKey) { +function addErrorMessage(errors: Errors, inputID?: string, message?: TKey | Localize.MaybePhraseKey) { if (!message || !inputID) { return; } From 9e8793528f8a31a260b8fb2a62b626f2f7864c88 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 30 Jan 2024 15:05:34 +0100 Subject: [PATCH 06/13] fix: typecheck --- src/components/Form/FormWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index d5b47761e4c0..c6efb539e490 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -105,7 +105,7 @@ function FormWrapper({ buttonText={submitButtonText} isAlertVisible={((!isEmptyObject(errors) || !isEmptyObject(formState?.errorFields)) && !shouldHideFixErrorsAlert) || !!errorMessage} isLoading={!!formState?.isLoading} - message={isEmptyObject(formState?.errorFields) ? errorMessage : undefined} + message={typeof errorMessage === 'string' && isEmptyObject(formState?.errorFields) ? errorMessage : undefined} onSubmit={onSubmit} footerContent={footerContent} onFixTheErrorsLinkPressed={onFixTheErrorsLinkPressed} From ff249fd99aa17d41d4f4d18275097a7e531f98bd Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 30 Jan 2024 15:55:00 +0100 Subject: [PATCH 07/13] fix: remove comment --- src/pages/settings/Profile/PersonalDetails/AddressPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx index 13e3f6513a80..fac17ba6e3c9 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx @@ -64,7 +64,6 @@ function AddressPage({privatePersonalDetails = {address: {street: '', city: '', }, [address]); const handleAddressChange = useCallback((value: string, key: keyof Address) => { - // TODO check if there is key zipPostCode if (key !== 'country' && key !== 'state' && key !== 'city' && key !== 'zipPostCode') { return; } From d7fa1f4b982c596c12721da168efcc727468e4ed Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 5 Feb 2024 11:07:05 +0100 Subject: [PATCH 08/13] fix: resolve comments --- src/ONYXKEYS.ts | 6 ++++-- src/components/OfflineWithFeedback.tsx | 2 +- src/libs/ErrorUtils.ts | 2 +- src/libs/ValidationUtils.ts | 2 +- .../Profile/PersonalDetails/LegalNamePage.tsx | 9 +++++---- .../PersonalDetailsInitialPage.tsx | 15 +-------------- src/types/onyx/Form.ts | 3 +++ src/types/onyx/index.ts | 2 ++ 8 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 7328fb2543ad..1a7a5950f333 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -367,6 +367,8 @@ const ONYXKEYS = { REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft', PERSONAL_BANK_ACCOUNT: 'personalBankAccountForm', PERSONAL_BANK_ACCOUNT_DRAFT: 'personalBankAccountFormDraft', + // LEGAL_NAME: 'legalName', + // LEGAL_NAME_DRAFT: 'legalNameDraft', }, } as const; @@ -503,8 +505,8 @@ type OnyxValues = { [ONYXKEYS.FORMS.ROOM_NAME_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.WELCOME_MESSAGE_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.WELCOME_MESSAGE_FORM_DRAFT]: OnyxTypes.Form; - [ONYXKEYS.FORMS.LEGAL_NAME_FORM]: OnyxTypes.Form; - [ONYXKEYS.FORMS.LEGAL_NAME_FORM_DRAFT]: OnyxTypes.Form; + [ONYXKEYS.FORMS.LEGAL_NAME_FORM]: OnyxTypes.LegalNameForm; + [ONYXKEYS.FORMS.LEGAL_NAME_FORM_DRAFT]: OnyxTypes.LegalNameForm; [ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.DATE_OF_BIRTH_FORM]: OnyxTypes.DateOfBirthForm; diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 2fad21fb54ef..b60fde8c8765 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -85,7 +85,7 @@ function OfflineWithFeedback({ const hasErrors = !isEmptyObject(errors ?? {}); // Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages. - const errorMessages = omitBy(errors, (e: string | ReceiptError) => e === null); + const errorMessages = omitBy(errors, (e: OnyxCommon.Error | ReceiptError) => e === null); const hasErrorMessages = !isEmptyObject(errorMessages); const isOfflinePendingAction = !!isOffline && !!pendingAction; const isUpdateOrDeleteError = hasErrors && (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index eb43f0ccf710..b142c4faead8 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -66,7 +66,7 @@ function getLatestErrorMessage(onyxData: T return errors[key]; } -function getLatestErrorMessageField(onyxData: TOnyxData): Record { +function getLatestErrorMessageField(onyxData: TOnyxData): Record { const errors = onyxData.errors ?? {}; if (Object.keys(errors).length === 0) { diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index a66a34f891eb..7b587eee05ac 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -345,7 +345,7 @@ function isValidPersonName(value: string) { /** * Checks if the provided string includes any of the provided reserved words */ -function doesContainReservedWord(value: string, reservedWords: string[]): boolean { +function doesContainReservedWord(value: string, reservedWords: typeof CONST.DISPLAY_NAME.RESERVED_NAMES): boolean { const valueToCheck = value.trim().toLowerCase(); return reservedWords.some((reservedWord) => valueToCheck.includes(reservedWord.toLowerCase())); } diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx index 58f3ccd56f33..195d80585b85 100644 --- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx @@ -4,6 +4,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {OnyxFormValuesFields} from '@components/Form/types'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -32,7 +33,7 @@ const updateLegalName = (values: PrivatePersonalDetails) => { PersonalDetails.updateLegalName(values.legalFirstName?.trim() ?? '', values.legalLastName?.trim() ?? ''); }; -function LegalNamePage({privatePersonalDetails = {legalFirstName: '', legalLastName: ''}}: LegalNamePageProps) { +function LegalNamePage({privatePersonalDetails}: LegalNamePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); usePrivatePersonalDetails(); @@ -40,7 +41,7 @@ function LegalNamePage({privatePersonalDetails = {legalFirstName: '', legalLastN const legalLastName = privatePersonalDetails?.legalLastName ?? ''; const isLoadingPersonalDetails = privatePersonalDetails?.isLoading ?? true; - const validate = useCallback((values: PrivatePersonalDetails) => { + const validate = useCallback((values: OnyxFormValuesFields) => { const errors: Errors = {}; if (!ValidationUtils.isValidLegalName(values.legalFirstName ?? '')) { @@ -48,7 +49,7 @@ function LegalNamePage({privatePersonalDetails = {legalFirstName: '', legalLastN } else if (values.legalFirstName === '') { errors.legalFirstName = 'common.error.fieldRequired'; } - if (ValidationUtils.doesContainReservedWord(values.legalFirstName ?? '', CONST.DISPLAY_NAME.RESERVED_NAMES as unknown as string[])) { + if (ValidationUtils.doesContainReservedWord(values.legalFirstName ?? '', CONST.DISPLAY_NAME.RESERVED_NAMES)) { ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'personalDetails.error.containsReservedWord'); } if ((values.legalFirstName?.length ?? 0) > CONST.LEGAL_NAME.MAX_LENGTH) { @@ -60,7 +61,7 @@ function LegalNamePage({privatePersonalDetails = {legalFirstName: '', legalLastN } else if (values.legalLastName === '') { errors.legalLastName = 'common.error.fieldRequired'; } - if (ValidationUtils.doesContainReservedWord(values.legalLastName ?? '', CONST.DISPLAY_NAME.RESERVED_NAMES as unknown as string[])) { + if (ValidationUtils.doesContainReservedWord(values.legalLastName ?? '', CONST.DISPLAY_NAME.RESERVED_NAMES)) { ErrorUtils.addErrorMessage(errors, 'legalLastName', 'personalDetails.error.containsReservedWord'); } if ((values.legalLastName?.length ?? 0) > CONST.LEGAL_NAME.MAX_LENGTH) { diff --git a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.tsx b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.tsx index 3c19f81dc9dc..1981b3fbec81 100644 --- a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.tsx @@ -23,20 +23,7 @@ type PersonalDetailsInitialPageOnyxProps = { type PersonalDetailsInitialPageProps = PersonalDetailsInitialPageOnyxProps; -function PersonalDetailsInitialPage({ - privatePersonalDetails = { - legalFirstName: '', - legalLastName: '', - dob: '', - address: { - street: '', - city: '', - state: '', - zip: '', - country: '', - }, - }, -}: PersonalDetailsInitialPageProps) { +function PersonalDetailsInitialPage({privatePersonalDetails}: PersonalDetailsInitialPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); usePrivatePersonalDetails(); diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts index 48f386afcbb0..1ab42c0f494d 100644 --- a/src/types/onyx/Form.ts +++ b/src/types/onyx/Form.ts @@ -63,6 +63,8 @@ type WorkspaceSettingsForm = Form<{ type ReportFieldEditForm = Form>; +type LegalNameForm = Form<{legalFirstName?: string; legalLastName?: string}>; + type CloseAccountForm = Form<{ reasonForLeaving: string; phoneOrEmail: string; @@ -84,4 +86,5 @@ export type { WorkspaceSettingsForm, ReportFieldEditForm, CloseAccountForm, + LegalNameForm, }; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index d0ac2ce395fa..79a33b936b1e 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -16,6 +16,7 @@ import type { DisplayNameForm, IKnowATeacherForm, IntroSchoolPrincipalForm, + LegalNameForm, NewRoomForm, PrivateNotesForm, ReportFieldEditForm, @@ -165,4 +166,5 @@ export type { IntroSchoolPrincipalForm, PrivateNotesForm, ReportFieldEditForm, + LegalNameForm, }; From 7d667afd07d82f9616c03159614480fcfa7cfa88 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 8 Feb 2024 16:25:25 +0100 Subject: [PATCH 09/13] fix: resolved comments --- src/ONYXKEYS.ts | 2 -- src/libs/ValidationUtils.ts | 3 ++- src/pages/settings/Profile/PersonalDetails/AddressPage.tsx | 2 +- .../settings/Profile/PersonalDetails/CountrySelectionPage.tsx | 2 +- .../settings/Profile/PersonalDetails/DateOfBirthPage.tsx | 2 +- src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx | 4 ++-- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 198023be8148..eb108127a280 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -367,8 +367,6 @@ const ONYXKEYS = { REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft', PERSONAL_BANK_ACCOUNT: 'personalBankAccountForm', PERSONAL_BANK_ACCOUNT_DRAFT: 'personalBankAccountFormDraft', - // LEGAL_NAME: 'legalName', - // LEGAL_NAME_DRAFT: 'legalNameDraft', }, } as const; diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index 5a0a064af6de..4a70817fa4c6 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -10,6 +10,7 @@ import type {Report} from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import * as CardUtils from './CardUtils'; import DateUtils from './DateUtils'; +import type {MaybePhraseKey} from './Localize'; import * as LoginUtils from './LoginUtils'; import {parsePhoneNumber} from './PhoneNumber'; import StringUtils from './StringUtils'; @@ -176,7 +177,7 @@ function meetsMaximumAgeRequirement(date: string): boolean { /** * Validate that given date is in a specified range of years before now. */ -function getAgeRequirementError(date: string, minimumAge: number, maximumAge: number): string | [string, Record] { +function getAgeRequirementError(date: string, minimumAge: number, maximumAge: number): MaybePhraseKey { const currentDate = startOfDay(new Date()); const testDate = parse(date, CONST.DATE.FNS_FORMAT_STRING, currentDate); diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx index b988f4013c6a..adc721ea0ea1 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx @@ -39,7 +39,7 @@ function updateAddress(values: Address) { ); } -function AddressPage({privatePersonalDetails = {address: {street: '', city: '', state: '', zip: '', country: ''}}, route}: AddressPageProps) { +function AddressPage({privatePersonalDetails, route}: AddressPageProps) { const styles = useThemeStyles(); usePrivatePersonalDetails(); const {translate} = useLocalize(); diff --git a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx index 4b38f2edfe6c..2173e1c15487 100644 --- a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx @@ -46,7 +46,7 @@ function CountrySelectionPage({route, navigation}: CountrySelectionPageProps) { if (navigation.getState().routes.length === 1 && !backTo) { // If there is only one route and "backTo" is empty, go back in navigation Navigation.goBack(); - } else if (backTo !== '' && navigation.getState().routes.length === 1) { + } else if (!!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(`${route.params.backTo}?country=${option.value}` as Route); } else { diff --git a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx index 46cfa19d0e7e..ea01b08291bc 100644 --- a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx @@ -25,7 +25,7 @@ type DateOfBirthPageOnyxProps = { }; type DateOfBirthPageProps = DateOfBirthPageOnyxProps; -function DateOfBirthPage({privatePersonalDetails = {dob: ''}}: DateOfBirthPageProps) { +function DateOfBirthPage({privatePersonalDetails}: DateOfBirthPageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); usePrivatePersonalDetails(); diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx index 5829025965c2..22ab56d9f481 100644 --- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx @@ -45,7 +45,7 @@ function LegalNamePage({privatePersonalDetails}: LegalNamePageProps) { if (!ValidationUtils.isValidLegalName(values.legalFirstName ?? '')) { ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'privatePersonalDetails.error.hasInvalidCharacter'); - } else if (values.legalFirstName === '') { + } else if (!values.legalFirstName) { errors.legalFirstName = 'common.error.fieldRequired'; } if (ValidationUtils.doesContainReservedWord(values.legalFirstName ?? '', CONST.DISPLAY_NAME.RESERVED_NAMES)) { @@ -57,7 +57,7 @@ function LegalNamePage({privatePersonalDetails}: LegalNamePageProps) { if (!ValidationUtils.isValidLegalName(values.legalLastName ?? '')) { ErrorUtils.addErrorMessage(errors, 'legalLastName', 'privatePersonalDetails.error.hasInvalidCharacter'); - } else if (values.legalLastName === '') { + } else if (!values.legalLastName) { errors.legalLastName = 'common.error.fieldRequired'; } if (ValidationUtils.doesContainReservedWord(values.legalLastName ?? '', CONST.DISPLAY_NAME.RESERVED_NAMES)) { From 20c98fd73530ba2f0e98316c1bb4fa40b461601d Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 9 Feb 2024 16:39:53 +0100 Subject: [PATCH 10/13] fix: typecheck --- .../settings/Profile/PersonalDetails/CountrySelectionPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx index 2173e1c15487..1cb797be933b 100644 --- a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx @@ -14,7 +14,7 @@ import type {TranslationPaths} from '@src/languages/types'; import type {Route} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -type CountrySelectionPageProps = StackScreenProps; +type CountrySelectionPageProps = StackScreenProps; function CountrySelectionPage({route, navigation}: CountrySelectionPageProps) { const [searchValue, setSearchValue] = useState(''); From c627a1aadc25fc5c71fe290efe60b5beb64289d6 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 15 Feb 2024 16:03:04 +0100 Subject: [PATCH 11/13] fix: typecheck --- src/components/Form/types.ts | 8 ++-- .../PersonalDetails/DateOfBirthPage.tsx | 11 +++-- .../Profile/PersonalDetails/LegalNamePage.tsx | 43 +++++++++++-------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index f4db6c0c8862..caf6ab1a1063 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -6,12 +6,13 @@ import type AmountForm from '@components/AmountForm'; import type AmountTextInput from '@components/AmountTextInput'; import type CheckboxWithLabel from '@components/CheckboxWithLabel'; import type CountrySelector from '@components/CountrySelector'; +import type DatePicker from '@components/DatePicker'; import type Picker from '@components/Picker'; import type SingleChoiceQuestion from '@components/SingleChoiceQuestion'; import type StatePicker from '@components/StatePicker'; import type TextInput from '@components/TextInput'; +import type {MaybePhraseKey} from '@libs/Localize'; import type BusinessTypePicker from '@pages/ReimbursementAccount/BusinessInfo/substeps/TypeBusiness/BusinessTypePicker'; -import type {TranslationPaths} from '@src/languages/types'; import type {OnyxFormKey, OnyxValues} from '@src/ONYXKEYS'; import type {BaseForm} from '@src/types/form/Form'; @@ -32,7 +33,8 @@ type ValidInputs = | typeof CountrySelector | typeof AmountForm | typeof BusinessTypePicker - | typeof StatePicker; + | typeof StatePicker + | typeof DatePicker; type ValueTypeKey = 'string' | 'boolean' | 'date'; type ValueTypeMap = { @@ -120,6 +122,6 @@ type FormProps = { type InputRefs = Record>; -type FormInputErrors = Partial, TranslationPaths>>; +type FormInputErrors = Partial, MaybePhraseKey>>; export type {FormProps, ValidInputs, InputComponentValueProps, FormValue, ValueTypeKey, FormOnyxValues, FormOnyxKeys, FormInputErrors, InputRefs, InputComponentBaseProps, ValueTypeMap}; diff --git a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx index 9b6ee686ab7f..f00bc9322a2d 100644 --- a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx @@ -5,6 +5,7 @@ import {withOnyx} from 'react-native-onyx'; import DatePicker from '@components/DatePicker'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormOnyxValues} from '@components/Form/types'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -17,7 +18,7 @@ import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/DateOfBirthForm'; -import {PrivatePersonalDetails} from '@src/types/onyx'; +import type {PrivatePersonalDetails} from '@src/types/onyx'; type DateOfBirthPageOnyxProps = { /** User's private personal details */ @@ -34,9 +35,9 @@ function DateOfBirthPage({privatePersonalDetails}: DateOfBirthPageProps) { /** * @returns An object containing the errors for each inputID */ - const validate = useCallback((values: PrivatePersonalDetails) => { - const requiredFields = ['dob']; - const errors = ValidationUtils.getFieldRequiredErrors(values as Error, requiredFields); + const validate = useCallback((values: FormOnyxValues) => { + const requiredFields = ['dob' as const]; + const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); const minimumAge = CONST.DATE_BIRTH.MIN_AGE; const maximumAge = CONST.DATE_BIRTH.MAX_AGE; @@ -76,6 +77,8 @@ function DateOfBirthPage({privatePersonalDetails}: DateOfBirthPageProps) { defaultValue={privatePersonalDetails?.dob ?? ''} minDate={subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE)} maxDate={subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE)} + onInputChange={() => {}} + onTouched={() => {}} /> )} diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx index a77698adac20..5e8e4ea493a0 100644 --- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx @@ -44,26 +44,33 @@ function LegalNamePage({privatePersonalDetails}: LegalNamePageProps) { const validate = useCallback((values: FormOnyxValues) => { const errors: Errors = {}; - if (!ValidationUtils.isValidLegalName(values.legalFirstName ?? '')) { - ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'privatePersonalDetails.error.hasInvalidCharacter'); - } else if (!values.legalFirstName) { - errors.legalFirstName = 'common.error.fieldRequired'; - } else if (values.legalFirstName.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'legalFirstName', ['common.error.characterLimitExceedCounter', {length: values.legalFirstName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); - } - if (ValidationUtils.doesContainReservedWord(values.legalFirstName ?? '', CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'personalDetails.error.containsReservedWord'); + if (typeof values.legalFirstName === 'string') { + if (!ValidationUtils.isValidLegalName(values.legalFirstName)) { + ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'privatePersonalDetails.error.hasInvalidCharacter'); + } else if (!values.legalFirstName) { + errors.legalFirstName = 'common.error.fieldRequired'; + } else if (values.legalFirstName.length > CONST.TITLE_CHARACTER_LIMIT) { + ErrorUtils.addErrorMessage(errors, 'legalFirstName', [ + 'common.error.characterLimitExceedCounter', + {length: values.legalFirstName.length, limit: CONST.TITLE_CHARACTER_LIMIT}, + ]); + } + if (ValidationUtils.doesContainReservedWord(values.legalFirstName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { + ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'personalDetails.error.containsReservedWord'); + } } - if (!ValidationUtils.isValidLegalName(values.legalLastName ?? '')) { - ErrorUtils.addErrorMessage(errors, 'legalLastName', 'privatePersonalDetails.error.hasInvalidCharacter'); - } else if (!values.legalLastName) { - errors.legalLastName = 'common.error.fieldRequired'; - } else if (values.legalLastName.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'legalLastName', ['common.error.characterLimitExceedCounter', {length: values.legalLastName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); - } - if (ValidationUtils.doesContainReservedWord(values.legalLastName ?? '', CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, 'legalLastName', 'personalDetails.error.containsReservedWord'); + if (typeof values.legalLastName === 'string') { + if (!ValidationUtils.isValidLegalName(values.legalLastName)) { + ErrorUtils.addErrorMessage(errors, 'legalLastName', 'privatePersonalDetails.error.hasInvalidCharacter'); + } else if (!values.legalLastName) { + errors.legalLastName = 'common.error.fieldRequired'; + } else if (values.legalLastName.length > CONST.TITLE_CHARACTER_LIMIT) { + ErrorUtils.addErrorMessage(errors, 'legalLastName', ['common.error.characterLimitExceedCounter', {length: values.legalLastName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + } + if (ValidationUtils.doesContainReservedWord(values.legalLastName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { + ErrorUtils.addErrorMessage(errors, 'legalLastName', 'personalDetails.error.containsReservedWord'); + } } return errors; From 296c724bb385b8e75bd11abcc4a4016711efc662 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 15 Feb 2024 16:28:26 +0100 Subject: [PATCH 12/13] fix: resolve comments --- src/libs/GetPhysicalCardUtils.ts | 10 +++++++++- src/libs/Navigation/types.ts | 2 +- src/libs/PersonalDetailsUtils.ts | 10 +++------- .../settings/Profile/PersonalDetails/AddressPage.tsx | 4 ++-- .../Profile/PersonalDetails/CountrySelectionPage.tsx | 2 +- .../settings/Wallet/Card/GetPhysicalCardConfirm.tsx | 2 ++ src/types/onyx/PrivatePersonalDetails.ts | 4 ++-- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/libs/GetPhysicalCardUtils.ts b/src/libs/GetPhysicalCardUtils.ts index cf49ba03f287..33e24d042d92 100644 --- a/src/libs/GetPhysicalCardUtils.ts +++ b/src/libs/GetPhysicalCardUtils.ts @@ -83,7 +83,15 @@ function getUpdatedPrivatePersonalDetails(draftValues: OnyxEntry): string { - const [street1, street2] = getStreetLines(privatePersonalDetails?.address?.street); + const {address} = privatePersonalDetails ?? {}; + const [street1, street2] = getStreetLines(address?.street); const formattedAddress = - formatPiece(street1) + - formatPiece(street2) + - formatPiece(privatePersonalDetails?.address?.city) + - formatPiece(privatePersonalDetails?.address?.state) + - formatPiece(privatePersonalDetails?.address?.zip) + - formatPiece(privatePersonalDetails?.address?.country); + formatPiece(street1) + formatPiece(street2) + formatPiece(address?.city) + formatPiece(address?.state) + formatPiece(address?.zip) + formatPiece(address?.country); // Remove the last comma of the address return formattedAddress.trim().replace(/,$/, ''); diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx index adc721ea0ea1..cd151ffe6cb2 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx @@ -30,11 +30,11 @@ type AddressPageProps = StackScreenProps { const backTo = route.params.backTo ?? ''; - const backToRoute = backTo ? (`${backTo}?country=${currentCountry}` as const) : undefined; + const backToRoute = backTo ? (`${backTo}?country=${currentCountry}` as const) : ''; Navigation.goBack(backToRoute as Route); }} /> diff --git a/src/pages/settings/Wallet/Card/GetPhysicalCardConfirm.tsx b/src/pages/settings/Wallet/Card/GetPhysicalCardConfirm.tsx index c6b4bc2272d1..693561a01b52 100644 --- a/src/pages/settings/Wallet/Card/GetPhysicalCardConfirm.tsx +++ b/src/pages/settings/Wallet/Card/GetPhysicalCardConfirm.tsx @@ -83,6 +83,8 @@ function GetPhysicalCardConfirm({ state, zip: zipPostCode, country, + addressLine1: addressLine1 ?? '', + zipPostCode: zipPostCode ?? '', }, })} /> diff --git a/src/types/onyx/PrivatePersonalDetails.ts b/src/types/onyx/PrivatePersonalDetails.ts index 5d5d9faded50..3626b73aebab 100644 --- a/src/types/onyx/PrivatePersonalDetails.ts +++ b/src/types/onyx/PrivatePersonalDetails.ts @@ -4,8 +4,8 @@ type Address = { state: string; zip: string; country: string; - zipPostCode?: string; - addressLine1?: string; + zipPostCode: string; + addressLine1: string; addressLine2?: string; }; From 60f0c397847f2e03fdd70a3bb91ce8ee123730a7 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 22 Feb 2024 12:00:51 +0100 Subject: [PATCH 13/13] fix: typecheck --- src/pages/settings/Wallet/WalletPage/CardDetails.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/settings/Wallet/WalletPage/CardDetails.tsx b/src/pages/settings/Wallet/WalletPage/CardDetails.tsx index 77458384c214..78fe03d1e65c 100644 --- a/src/pages/settings/Wallet/WalletPage/CardDetails.tsx +++ b/src/pages/settings/Wallet/WalletPage/CardDetails.tsx @@ -23,6 +23,8 @@ const defaultPrivatePersonalDetails: PrivatePersonalDetails = { state: '', zip: '', country: '', + addressLine1: '', + zipPostCode: '', }, };