Skip to content

Commit

Permalink
Merge pull request #42970 from nkdengineer/fix/42539
Browse files Browse the repository at this point in the history
  • Loading branch information
francoisl authored Jun 17, 2024
2 parents 6a3e941 + 1ef917b commit 67ec334
Show file tree
Hide file tree
Showing 136 changed files with 876 additions and 823 deletions.
32 changes: 16 additions & 16 deletions src/components/AddPaymentCard/PaymentCardForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,35 +138,35 @@ function PaymentCardForm({
const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState(false);
const [currency, setCurrency] = useState<keyof typeof CONST.CURRENCY>(CONST.CURRENCY.USD);

const validate = (formValues: FormOnyxValues<typeof ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM> => {
const errors = ValidationUtils.getFieldRequiredErrors(formValues, REQUIRED_FIELDS);
const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM> => {
const errors = ValidationUtils.getFieldRequiredErrors(values, REQUIRED_FIELDS);

if (formValues.nameOnCard && !ValidationUtils.isValidLegalName(formValues.nameOnCard)) {
errors.nameOnCard = label.error.nameOnCard;
if (values.nameOnCard && !ValidationUtils.isValidLegalName(values.nameOnCard)) {
errors.nameOnCard = translate('addDebitCardPage.error.invalidName');
}

if (formValues.cardNumber && !ValidationUtils.isValidDebitCard(formValues.cardNumber.replace(/ /g, ''))) {
errors.cardNumber = label.error.cardNumber;
if (values.cardNumber && !ValidationUtils.isValidDebitCard(values.cardNumber.replace(/ /g, ''))) {
errors.cardNumber = translate('addDebitCardPage.error.debitCardNumber');
}

if (formValues.expirationDate && !ValidationUtils.isValidExpirationDate(formValues.expirationDate)) {
errors.expirationDate = label.error.expirationDate;
if (values.expirationDate && !ValidationUtils.isValidExpirationDate(values.expirationDate)) {
errors.expirationDate = translate('addDebitCardPage.error.expirationDate');
}

if (formValues.securityCode && !ValidationUtils.isValidSecurityCode(formValues.securityCode)) {
errors.securityCode = label.error.securityCode;
if (values.securityCode && !ValidationUtils.isValidSecurityCode(values.securityCode)) {
errors.securityCode = translate('addDebitCardPage.error.securityCode');
}

if (formValues.addressStreet && !ValidationUtils.isValidAddress(formValues.addressStreet)) {
errors.addressStreet = label.error.addressStreet;
if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) {
errors.addressStreet = translate('addDebitCardPage.error.addressStreet');
}

if (formValues.addressZipCode && !ValidationUtils.isValidZipCode(formValues.addressZipCode)) {
errors.addressZipCode = label.error.addressZipCode;
if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) {
errors.addressZipCode = translate('addDebitCardPage.error.addressZipCode');
}

if (!formValues.acceptTerms) {
errors.acceptTerms = 'common.error.acceptTerms';
if (!values.acceptTerms) {
errors.acceptTerms = translate('common.error.acceptTerms');
}

return errors;
Expand Down
1 change: 1 addition & 0 deletions src/components/AddPlaidBankAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ function AddPlaidBankAccount({
}));
const {icon, iconSize, iconStyles} = getBankIcon({styles});
const plaidErrors = plaidData?.errors;
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
const plaidDataErrorMessage = !isEmptyObject(plaidErrors) ? (Object.values(plaidErrors)[0] as string) : '';
const bankName = plaidData?.bankName;

Expand Down
76 changes: 39 additions & 37 deletions src/components/AddressForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ErrorUtils from '@libs/ErrorUtils';
import type {MaybePhraseKey} from '@libs/Localize';
import * as ValidationUtils from '@libs/ValidationUtils';
import CONST from '@src/CONST';
import type {Country} from '@src/CONST';
Expand Down Expand Up @@ -76,7 +75,7 @@ function AddressForm({

const zipSampleFormat = (country && (CONST.COUNTRY_ZIP_REGEX_DATA[country] as CountryZipRegex)?.samples) ?? '';

const zipFormat: MaybePhraseKey = ['common.zipCodeExampleFormat', {zipSampleFormat}];
const zipFormat = translate('common.zipCodeExampleFormat', {zipSampleFormat});

const isUSAForm = country === CONST.COUNTRY.US;

Expand All @@ -87,50 +86,53 @@ function AddressForm({
* @returns - An object containing the errors for each inputID
*/

const validator = useCallback((values: FormOnyxValues<typeof ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM | typeof ONYXKEYS.FORMS.HOME_ADDRESS_FORM>): Errors => {
const errors: Errors & {
zipPostCode?: string | string[];
} = {};
const requiredFields = ['addressLine1', 'city', 'country', 'state'] as const;

// Check "State" dropdown is a valid state if selected Country is USA
if (values.country === CONST.COUNTRY.US && !values.state) {
errors.state = 'common.error.fieldRequired';
}

// Add "Field required" errors if any required field is empty
requiredFields.forEach((fieldKey) => {
const fieldValue = values[fieldKey] ?? '';
if (ValidationUtils.isRequiredFulfilled(fieldValue)) {
return;
const validator = useCallback(
(values: FormOnyxValues<typeof ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM | typeof ONYXKEYS.FORMS.HOME_ADDRESS_FORM>): Errors => {
const errors: Errors & {
zipPostCode?: string | string[];
} = {};
const requiredFields = ['addressLine1', 'city', 'country', 'state'] as const;

// Check "State" dropdown is a valid state if selected Country is USA
if (values.country === CONST.COUNTRY.US && !values.state) {
errors.state = translate('common.error.fieldRequired');
}

errors[fieldKey] = 'common.error.fieldRequired';
});
// Add "Field required" errors if any required field is empty
requiredFields.forEach((fieldKey) => {
const fieldValue = values[fieldKey] ?? '';
if (ValidationUtils.isRequiredFulfilled(fieldValue)) {
return;
}

errors[fieldKey] = translate('common.error.fieldRequired');
});

// If no country is selected, default value is an empty string and there's no related regex data so we default to an empty object
const countryRegexDetails = (values.country ? CONST.COUNTRY_ZIP_REGEX_DATA?.[values.country] : {}) as CountryZipRegex;
// If no country is selected, default value is an empty string and there's no related regex data so we default to an empty object
const countryRegexDetails = (values.country ? CONST.COUNTRY_ZIP_REGEX_DATA?.[values.country] : {}) as CountryZipRegex;

// The postal code system might not exist for a country, so no regex either for them.
const countrySpecificZipRegex = countryRegexDetails?.regex;
const countryZipFormat = countryRegexDetails?.samples ?? '';
// The postal code system might not exist for a country, so no regex either for them.
const countrySpecificZipRegex = countryRegexDetails?.regex;
const countryZipFormat = countryRegexDetails?.samples ?? '';

ErrorUtils.addErrorMessage(errors, 'firstName', 'bankAccount.error.firstName');
ErrorUtils.addErrorMessage(errors, 'firstName', translate('bankAccount.error.firstName'));

if (countrySpecificZipRegex) {
if (!countrySpecificZipRegex.test(values.zipPostCode?.trim().toUpperCase())) {
if (ValidationUtils.isRequiredFulfilled(values.zipPostCode?.trim())) {
errors.zipPostCode = ['privatePersonalDetails.error.incorrectZipFormat', countryZipFormat];
} else {
errors.zipPostCode = 'common.error.fieldRequired';
if (countrySpecificZipRegex) {
if (!countrySpecificZipRegex.test(values.zipPostCode?.trim().toUpperCase())) {
if (ValidationUtils.isRequiredFulfilled(values.zipPostCode?.trim())) {
errors.zipPostCode = translate('privatePersonalDetails.error.incorrectZipFormat', countryZipFormat);
} else {
errors.zipPostCode = translate('common.error.fieldRequired');
}
}
} else if (!CONST.GENERIC_ZIP_CODE_REGEX.test(values?.zipPostCode?.trim()?.toUpperCase() ?? '')) {
errors.zipPostCode = translate('privatePersonalDetails.error.incorrectZipFormat');
}
} else if (!CONST.GENERIC_ZIP_CODE_REGEX.test(values?.zipPostCode?.trim()?.toUpperCase() ?? '')) {
errors.zipPostCode = 'privatePersonalDetails.error.incorrectZipFormat';
}

return errors;
}, []);
return errors;
},
[translate],
);

return (
<FormProvider
Expand Down
3 changes: 1 addition & 2 deletions src/components/AddressSearch/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type {RefObject} from 'react';
import type {NativeSyntheticEvent, StyleProp, TextInputFocusEventData, View, ViewStyle} from 'react-native';
import type {Place} from 'react-native-google-places-autocomplete';
import type {MaybePhraseKey} from '@libs/Localize';
import type Locale from '@src/types/onyx/Locale';
import type {Address} from '@src/types/onyx/PrivatePersonalDetails';

Expand Down Expand Up @@ -35,7 +34,7 @@ type AddressSearchProps = {
onBlur?: () => void;

/** Error text to display */
errorText?: MaybePhraseKey;
errorText?: string;

/** Hint text to display */
hint?: string;
Expand Down
3 changes: 1 addition & 2 deletions src/components/AmountPicker/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type {AmountFormProps} from '@components/AmountForm';
import type {MenuItemBaseProps} from '@components/MenuItem';
import type {MaybePhraseKey} from '@libs/Localize';

type AmountSelectorModalProps = {
/** Whether the modal is visible */
Expand All @@ -24,7 +23,7 @@ type AmountPickerProps = {
title?: string | ((value?: string) => string);

/** Form Error description */
errorText?: MaybePhraseKey;
errorText?: string;

/** Callback to call when the input changes */
onInputChange?: (value: string | undefined) => void;
Expand Down
3 changes: 1 addition & 2 deletions src/components/CheckboxWithLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React, {useState} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import type {MaybePhraseKey} from '@libs/Localize';
import variables from '@styles/variables';
import Checkbox from './Checkbox';
import FormHelpMessage from './FormHelpMessage';
Expand Down Expand Up @@ -41,7 +40,7 @@ type CheckboxWithLabelProps = RequiredLabelProps & {
style?: StyleProp<ViewStyle>;

/** Error text to display */
errorText?: MaybePhraseKey;
errorText?: string;

/** Value for checkbox. This prop is intended to be set by FormProvider only */
value?: boolean;
Expand Down
3 changes: 1 addition & 2 deletions src/components/CountrySelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type {ForwardedRef} from 'react';
import type {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import type {MaybePhraseKey} from '@libs/Localize';
import Navigation from '@libs/Navigation/Navigation';
import CONST from '@src/CONST';
import type {Country} from '@src/CONST';
Expand All @@ -13,7 +12,7 @@ import MenuItemWithTopDescription from './MenuItemWithTopDescription';

type CountrySelectorProps = {
/** Form error text. e.g when no country is selected */
errorText?: MaybePhraseKey;
errorText?: string;

/** Callback called when the country changes. */
onInputChange?: (value?: string) => void;
Expand Down
11 changes: 5 additions & 6 deletions src/components/DotIndicatorMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {isReceiptError} from '@libs/ErrorUtils';
import fileDownload from '@libs/fileDownload';
import type {MaybePhraseKey} from '@libs/Localize';
import * as Localize from '@libs/Localize';
import type {ReceiptError} from '@src/types/onyx/Transaction';
import Icon from './Icon';
Expand All @@ -23,7 +22,7 @@ type DotIndicatorMessageProps = {
* timestamp: 'message',
* }
*/
messages: Record<string, Localize.MaybePhraseKey | ReceiptError>;
messages: Record<string, string | ReceiptError | null>;

/** The type of message, 'error' shows a red dot, 'success' shows a green dot */
type: 'error' | 'success';
Expand All @@ -45,12 +44,12 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica
}

// Fetch the keys, sort them, and map through each key to get the corresponding message
const sortedMessages: Array<MaybePhraseKey | ReceiptError> = Object.keys(messages)
const sortedMessages: Array<string | ReceiptError> = Object.keys(messages)
.sort()
.map((key) => messages[key]);

.map((key) => messages[key])
.filter((message): message is string | ReceiptError => message !== null);
// Removing duplicates using Set and transforming the result into an array
const uniqueMessages: Array<ReceiptError | string> = [...new Set(sortedMessages)].map((message) => (isReceiptError(message) ? message : Localize.translateIfPhraseKey(message)));
const uniqueMessages: Array<ReceiptError | string> = [...new Set(sortedMessages)].map((message) => message);

const isErrorMessage = type === 'error';

Expand Down
4 changes: 2 additions & 2 deletions src/components/Form/FormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,10 @@ function FormProvider(

const errorFields = formState?.errorFields?.[inputID] ?? {};
const fieldErrorMessage =
(Object.keys(errorFields)
Object.keys(errorFields)
.sort()
.map((key) => errorFields[key])
.at(-1) as string) ?? '';
.at(-1) ?? '';

const inputRef = inputProps.ref;

Expand Down
3 changes: 1 addition & 2 deletions src/components/Form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import type StateSelector from '@components/StateSelector';
import type TextInput from '@components/TextInput';
import type TextPicker from '@components/TextPicker';
import type ValuePicker from '@components/ValuePicker';
import type {MaybePhraseKey} from '@libs/Localize';
import type BusinessTypePicker from '@pages/ReimbursementAccount/BusinessInfo/substeps/TypeBusiness/BusinessTypePicker';
import type {Country} from '@src/CONST';
import type {OnyxFormKey, OnyxValues} from '@src/ONYXKEYS';
Expand Down Expand Up @@ -139,7 +138,7 @@ type FormRef<TFormID extends OnyxFormKey = OnyxFormKey> = {

type InputRefs = Record<string, MutableRefObject<InputComponentBaseProps>>;

type FormInputErrors<TFormID extends OnyxFormKey = OnyxFormKey> = Partial<Record<FormOnyxKeys<TFormID>, MaybePhraseKey>>;
type FormInputErrors<TFormID extends OnyxFormKey = OnyxFormKey> = Partial<Record<FormOnyxKeys<TFormID>, string | undefined>>;

export type {
FormProps,
Expand Down
3 changes: 1 addition & 2 deletions src/components/FormAlertWithSubmitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import type {MaybePhraseKey} from '@libs/Localize';
import Button from './Button';
import FormAlertWrapper from './FormAlertWrapper';

type FormAlertWithSubmitButtonProps = {
/** Error message to display above button */
message?: MaybePhraseKey;
message?: string;

/** Whether the button is disabled */
isDisabled?: boolean;
Expand Down
3 changes: 1 addition & 2 deletions src/components/FormAlertWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import type {MaybePhraseKey} from '@libs/Localize';
import type Network from '@src/types/onyx/Network';
import FormHelpMessage from './FormHelpMessage';
import {withNetwork} from './OnyxProvider';
Expand All @@ -29,7 +28,7 @@ type FormAlertWrapperProps = {
isMessageHtml?: boolean;

/** Error message to display above button */
message?: MaybePhraseKey;
message?: string;

/** Props to detect online status */
network: Network;
Expand Down
7 changes: 2 additions & 5 deletions src/components/FormHelpMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Localize from '@libs/Localize';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import Text from './Text';

type FormHelpMessageProps = {
/** Error or hint text. Ignored when children is not empty */
message?: Localize.MaybePhraseKey;
message?: string;

/** Children to render next to dot indicator */
children?: React.ReactNode;
Expand All @@ -33,8 +32,6 @@ function FormHelpMessage({message = '', children, isError = true, style, shouldS
return null;
}

const translatedMessage = Localize.translateIfPhraseKey(message);

return (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mt2, styles.mb1, style]}>
{isError && shouldShowRedDotIndicator && (
Expand All @@ -44,7 +41,7 @@ function FormHelpMessage({message = '', children, isError = true, style, shouldS
/>
)}
<View style={[styles.flex1, isError && shouldShowRedDotIndicator ? styles.ml2 : {}]}>
{children ?? <Text style={[isError ? styles.formError : styles.formHelp, styles.mb0]}>{translatedMessage}</Text>}
{children ?? <Text style={[isError ? styles.formError : styles.formHelp, styles.mb0]}>{message}</Text>}
</View>
</View>
);
Expand Down
3 changes: 1 addition & 2 deletions src/components/MagicCodeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import useNetwork from '@hooks/useNetwork';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Browser from '@libs/Browser';
import type {MaybePhraseKey} from '@libs/Localize';
import * as ValidationUtils from '@libs/ValidationUtils';
import CONST from '@src/CONST';
import FormHelpMessage from './FormHelpMessage';
Expand All @@ -33,7 +32,7 @@ type MagicCodeInputProps = {
shouldDelayFocus?: boolean;

/** Error text to display */
errorText?: MaybePhraseKey;
errorText?: string;

/** Specifies autocomplete hints for the system, so it can provide autofill */
autoComplete: AutoCompleteVariant;
Expand Down
Loading

0 comments on commit 67ec334

Please sign in to comment.