From ce9d3895206bb74f6ebf8f750fc580f8820c19c1 Mon Sep 17 00:00:00 2001 From: dominictb Date: Mon, 27 May 2024 09:42:19 +0700 Subject: [PATCH 001/127] fix: keep the Android keyboard visible when pasting in Composer component env: Android Chrome Signed-off-by: dominictb --- src/components/Composer/index.tsx | 2 +- src/hooks/useHtmlPaste/index.ts | 11 +++++++---- src/hooks/useHtmlPaste/types.ts | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index f7bf277050a2..a7635db58472 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -250,7 +250,7 @@ function Composer( // eslint-disable-next-line react-hooks/exhaustive-deps }, [isComposerFullSize]); - useHtmlPaste(textInput, handlePaste, true); + useHtmlPaste(textInput, handlePaste, true, false); useEffect(() => { if (typeof ref === 'function') { diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 925a3db518ae..b6ee5ab122fc 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -18,8 +18,7 @@ const insertAtCaret = (target: HTMLElement, text: string) => { // Move caret to the end of the newly inserted text node. range.setStart(node, node.length); range.setEnd(node, node.length); - selection.removeAllRanges(); - selection.addRange(range); + selection.setBaseAndExtent(range.startContainer, range.startOffset, range.endContainer, range.endOffset); // Dispatch paste event to simulate real browser behavior target.dispatchEvent(new Event('paste', {bubbles: true})); @@ -30,7 +29,7 @@ const insertAtCaret = (target: HTMLElement, text: string) => { } }; -const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false) => { +const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false, shouldRefocusAfterPaste = true) => { const navigation = useNavigation(); /** @@ -47,7 +46,11 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi } // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. - textInputRef.current?.blur(); + // If shouldRefocusAfterPaste = false, we want to call `focus()` only as it won't change the focus state of the input since it is already focused + if (shouldRefocusAfterPaste) { + textInputRef.current?.blur(); + } + textInputRef.current?.focus(); // eslint-disable-next-line no-empty } catch (e) {} diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index 305ebe5fbd0f..f2dd41eb2488 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -5,6 +5,7 @@ type UseHtmlPaste = ( textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput) | TextInput | null>, preHtmlPasteCallback?: (event: ClipboardEvent) => boolean, removeListenerOnScreenBlur?: boolean, + shouldRefocusAfterPaste?: boolean, ) => void; export default UseHtmlPaste; From 851501831b6fda411a83487fb8c458ec56734425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 29 May 2024 11:59:21 +0200 Subject: [PATCH 002/127] fix autoreporting frequency on toggle --- src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 5b17a4e26051..18ef99c7a758 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -105,11 +105,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr subtitle: translate('workflowsPage.delaySubmissionDescription'), switchAccessibilityLabel: translate('workflowsPage.delaySubmissionDescription'), onToggle: (isEnabled: boolean) => { - const frequency = - policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT || !policy?.autoReportingFrequency - ? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY - : policy.autoReportingFrequency; - Policy.setWorkspaceAutoReporting(route.params.policyID, isEnabled, frequency); + Policy.setWorkspaceAutoReportingFrequency(route.params.policyID, isEnabled ? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY : CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT); }, subMenuItems: ( Date: Wed, 29 May 2024 12:01:14 +0200 Subject: [PATCH 003/127] fix style --- src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 18ef99c7a758..28c70d76b9b0 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -105,7 +105,10 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr subtitle: translate('workflowsPage.delaySubmissionDescription'), switchAccessibilityLabel: translate('workflowsPage.delaySubmissionDescription'), onToggle: (isEnabled: boolean) => { - Policy.setWorkspaceAutoReportingFrequency(route.params.policyID, isEnabled ? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY : CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT); + Policy.setWorkspaceAutoReportingFrequency( + route.params.policyID, + isEnabled ? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY : CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT, + ); }, subMenuItems: ( Date: Mon, 3 Jun 2024 00:07:57 +0700 Subject: [PATCH 004/127] get rid of missing translation FormHelpMessage --- src/components/AddressForm.tsx | 6 ++-- src/components/AmountPicker/types.ts | 3 +- src/components/CheckboxWithLabel.tsx | 3 +- src/components/CountrySelector.tsx | 3 +- src/components/DotIndicatorMessage.tsx | 2 +- src/components/FormAlertWithSubmitButton.tsx | 3 +- src/components/FormAlertWrapper.tsx | 3 +- src/components/FormHelpMessage.tsx | 7 ++-- src/components/MagicCodeInput.tsx | 3 +- src/components/MenuItem.tsx | 5 ++- src/components/MessagesRow.tsx | 4 +-- .../MoneyRequestConfirmationList.tsx | 31 ++++------------ src/components/PDFView/PDFPasswordForm.tsx | 2 +- src/components/Picker/types.ts | 3 +- src/components/RadioButtonWithLabel.tsx | 3 +- src/components/RadioButtons.tsx | 3 +- src/components/SingleChoiceQuestion.tsx | 3 +- src/components/StateSelector.tsx | 3 +- .../TextInput/BaseTextInput/types.ts | 5 ++- src/components/TextPicker/types.ts | 3 +- src/components/TimePicker/TimePicker.tsx | 6 ++-- src/components/ValuePicker/types.ts | 3 +- src/libs/DateUtils.ts | 4 +-- src/libs/ErrorUtils.ts | 21 ++++++----- src/libs/ValidationUtils.ts | 10 +++--- src/libs/actions/TaxRate.ts | 5 +-- src/pages/EditReportFieldDate.tsx | 4 +-- .../EnablePayments/AdditionalDetailsStep.tsx | 14 ++++---- .../FeesAndTerms/substeps/TermsStep.tsx | 2 +- .../PersonalInfo/substeps/DateOfBirthStep.tsx | 28 +++++++-------- src/pages/EnablePayments/TermsStep.tsx | 2 +- .../BaseOnboardingPurpose.tsx | 10 +++--- .../AddressFormFields.tsx | 6 ++-- .../DateOfBirthUBO.tsx | 4 +-- .../substeps/ConfirmationBusiness.tsx | 20 +++++------ .../substeps/IncorporationDateBusiness.tsx | 24 ++++++------- .../substeps/ConfirmAgreements.tsx | 35 +++++++++---------- .../PersonalInfo/substeps/DateOfBirth.tsx | 28 +++++++-------- src/pages/Travel/TravelTerms.tsx | 2 +- src/pages/iou/MoneyRequestAmountForm.tsx | 9 +++-- .../MoneyRequestParticipantsSelector.tsx | 2 +- .../TwoFactorAuth/Steps/CodesStep.tsx | 2 +- .../Wallet/ActivatePhysicalCardPage.tsx | 2 +- .../settings/Wallet/AddDebitCardPage.tsx | 14 ++++---- .../settings/Wallet/ReportCardLostPage.tsx | 4 +-- src/pages/signin/LoginForm/BaseLoginForm.tsx | 11 +++--- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 4 +-- src/pages/tasks/NewTaskPage.tsx | 6 ++-- src/pages/workspace/WorkspaceInvitePage.tsx | 2 +- src/pages/workspace/WorkspaceMembersPage.tsx | 2 +- src/pages/workspace/WorkspaceNewRoomPage.tsx | 10 +++--- src/stories/Form.stories.tsx | 5 ++- src/types/onyx/OnyxCommon.ts | 4 +-- 53 files changed, 181 insertions(+), 222 deletions(-) diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index 9ad4643e834a..bd3ad7ea0888 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -96,7 +96,7 @@ function AddressForm({ // 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'; + errors.state = translate('common.error.fieldRequired'); } // Add "Field required" errors if any required field is empty @@ -106,7 +106,7 @@ function AddressForm({ return; } - errors[fieldKey] = 'common.error.fieldRequired'; + 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 @@ -131,7 +131,7 @@ function AddressForm({ } return errors; - }, []); + }, [translate]); return ( string); /** Form Error description */ - errorText?: MaybePhraseKey; + errorText?: string; /** Callback to call when the input changes */ onInputChange?: (value: string | undefined) => void; diff --git a/src/components/CheckboxWithLabel.tsx b/src/components/CheckboxWithLabel.tsx index dd169576186e..db62aa9e1441 100644 --- a/src/components/CheckboxWithLabel.tsx +++ b/src/components/CheckboxWithLabel.tsx @@ -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'; @@ -41,7 +40,7 @@ type CheckboxWithLabelProps = RequiredLabelProps & { style?: StyleProp; /** Error text to display */ - errorText?: MaybePhraseKey; + errorText?: string; /** Value for checkbox. This prop is intended to be set by FormProvider only */ value?: boolean; diff --git a/src/components/CountrySelector.tsx b/src/components/CountrySelector.tsx index 002c0c6d4b0a..62fdc85687e1 100644 --- a/src/components/CountrySelector.tsx +++ b/src/components/CountrySelector.tsx @@ -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'; @@ -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; diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index 3f72bbf429aa..f1aa8243b327 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -23,7 +23,7 @@ type DotIndicatorMessageProps = { * timestamp: 'message', * } */ - messages: Record; + messages: Record; /** The type of message, 'error' shows a red dot, 'success' shows a green dot */ type: 'error' | 'success'; diff --git a/src/components/FormAlertWithSubmitButton.tsx b/src/components/FormAlertWithSubmitButton.tsx index 137012478549..cd177a1d77a3 100644 --- a/src/components/FormAlertWithSubmitButton.tsx +++ b/src/components/FormAlertWithSubmitButton.tsx @@ -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; diff --git a/src/components/FormAlertWrapper.tsx b/src/components/FormAlertWrapper.tsx index d8b379208a29..525182070095 100644 --- a/src/components/FormAlertWrapper.tsx +++ b/src/components/FormAlertWrapper.tsx @@ -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'; @@ -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; diff --git a/src/components/FormHelpMessage.tsx b/src/components/FormHelpMessage.tsx index 4f1d784788bf..01a5a1eaf3a8 100644 --- a/src/components/FormHelpMessage.tsx +++ b/src/components/FormHelpMessage.tsx @@ -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; @@ -33,8 +32,6 @@ function FormHelpMessage({message = '', children, isError = true, style, shouldS return null; } - const translatedMessage = Localize.translateIfPhraseKey(message); - return ( {isError && shouldShowRedDotIndicator && ( @@ -44,7 +41,7 @@ function FormHelpMessage({message = '', children, isError = true, style, shouldS /> )} - {children ?? {translatedMessage}} + {children ?? {message}} ); diff --git a/src/components/MagicCodeInput.tsx b/src/components/MagicCodeInput.tsx index deff56a534ee..6239243cb5ab 100644 --- a/src/components/MagicCodeInput.tsx +++ b/src/components/MagicCodeInput.tsx @@ -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'; @@ -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; diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index b6f378763659..dd3cf462f89e 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -14,7 +14,6 @@ import ControlSelection from '@libs/ControlSelection'; import convertToLTR from '@libs/convertToLTR'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import getButtonState from '@libs/getButtonState'; -import type {MaybePhraseKey} from '@libs/Localize'; import type {AvatarSource} from '@libs/UserUtils'; import variables from '@styles/variables'; import * as Session from '@userActions/Session'; @@ -151,13 +150,13 @@ type MenuItemBaseProps = { shouldShowDescriptionOnTop?: boolean; /** Error to display at the bottom of the component */ - errorText?: MaybePhraseKey; + errorText?: string; /** Any additional styles to pass to error text. */ errorTextStyle?: StyleProp; /** Hint to display at the bottom of the component */ - hintText?: MaybePhraseKey; + hintText?: string; /** Should the error text red dot indicator be shown */ shouldShowRedDotIndicator?: boolean; diff --git a/src/components/MessagesRow.tsx b/src/components/MessagesRow.tsx index 7c764ec94fcd..2aead5da334c 100644 --- a/src/components/MessagesRow.tsx +++ b/src/components/MessagesRow.tsx @@ -4,9 +4,7 @@ import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import type * as Localize from '@libs/Localize'; import CONST from '@src/CONST'; -import type {ReceiptError} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import DotIndicatorMessage from './DotIndicatorMessage'; import Icon from './Icon'; @@ -16,7 +14,7 @@ import Tooltip from './Tooltip'; type MessagesRowProps = { /** The messages to display */ - messages: Record; + messages: Record; /** The type of message, 'error' shows a red dot, 'success' shows a green dot */ type: 'error' | 'success'; diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index b18a98b13304..31fcdaa4843e 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -347,11 +347,11 @@ function MoneyRequestConfirmationList({ useEffect(() => { if (shouldDisplayFieldError && hasSmartScanFailed) { - setFormError('iou.receiptScanningFailed'); + setFormError(translate('iou.receiptScanningFailed')); return; } if (shouldDisplayFieldError && didConfirmSplit) { - setFormError('iou.error.genericSmartscanFailureMessage'); + setFormError(translate('iou.error.genericSmartscanFailureMessage')); return; } // reset the form error whenever the screen gains or loses focus @@ -431,7 +431,7 @@ function MoneyRequestConfirmationList({ const shares: number[] = Object.values(splitSharesMap).map((splitShare) => splitShare?.amount ?? 0); const sumOfShares = shares?.reduce((prev, current): number => prev + current, 0); if (sumOfShares !== iouAmount) { - setFormError('iou.error.invalidSplit'); + setFormError(translate('iou.error.invalidSplit')); return; } @@ -441,7 +441,7 @@ function MoneyRequestConfirmationList({ // A split must have at least two participants with amounts bigger than 0 if (participantsWithAmount.length === 1) { - setFormError('iou.error.invalidSplitParticipants'); + setFormError(translate('iou.error.invalidSplitParticipants')); return; } @@ -698,11 +698,11 @@ function MoneyRequestConfirmationList({ return; } if (!isEditingSplitBill && isMerchantRequired && (isMerchantEmpty || (shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction ?? null)))) { - setFormError('iou.error.invalidMerchant'); + setFormError(translate('iou.error.invalidMerchant')); return; } if (iouCategory.length > CONST.API_TRANSACTION_CATEGORY_MAX_LENGTH) { - setFormError('iou.error.invalidCategoryLength'); + setFormError(translate('iou.error.invalidCategoryLength')); return; } @@ -738,24 +738,7 @@ function MoneyRequestConfirmationList({ onConfirm?.(selectedParticipants); } }, - [ - selectedParticipants, - isMerchantRequired, - isMerchantEmpty, - shouldDisplayFieldError, - transaction, - iouType, - onSendMoney, - iouCurrencyCode, - isDistanceRequest, - iouCategory, - isDistanceRequestWithPendingRoute, - iouAmount, - isEditingSplitBill, - formError, - setFormError, - onConfirm, - ], + [selectedParticipants, isEditingSplitBill, isMerchantRequired, isMerchantEmpty, shouldDisplayFieldError, transaction, iouCategory.length, formError, iouType, setFormError, translate, onSendMoney, iouCurrencyCode, isDistanceRequest, isDistanceRequestWithPendingRoute, iouAmount, onConfirm], ); const footerContent = useMemo(() => { diff --git a/src/components/PDFView/PDFPasswordForm.tsx b/src/components/PDFView/PDFPasswordForm.tsx index e1ef83c9b60d..e571c2830b3c 100644 --- a/src/components/PDFView/PDFPasswordForm.tsx +++ b/src/components/PDFView/PDFPasswordForm.tsx @@ -47,7 +47,7 @@ function PDFPasswordForm({isFocused, isPasswordInvalid = false, shouldShowLoadin const errorText = useMemo(() => { if (isPasswordInvalid) { - return 'attachmentView.passwordIncorrect'; + return translate('attachmentView.passwordIncorrect'); } if (validationErrorText) { return validationErrorText; diff --git a/src/components/Picker/types.ts b/src/components/Picker/types.ts index d935ebe8fdc5..a6a942f41f26 100644 --- a/src/components/Picker/types.ts +++ b/src/components/Picker/types.ts @@ -1,6 +1,5 @@ import type {ChangeEvent, Component, ReactElement} from 'react'; import type {MeasureLayoutOnSuccessCallback, NativeMethods, StyleProp, ViewStyle} from 'react-native'; -import type {MaybePhraseKey} from '@libs/Localize'; type MeasureLayoutOnFailCallback = () => void; @@ -59,7 +58,7 @@ type BasePickerProps = { placeholder?: PickerPlaceholder; /** Error text to display */ - errorText?: MaybePhraseKey; + errorText?: string; /** Customize the BasePicker container */ containerStyles?: StyleProp; diff --git a/src/components/RadioButtonWithLabel.tsx b/src/components/RadioButtonWithLabel.tsx index 9b93d7900772..94265c19645d 100644 --- a/src/components/RadioButtonWithLabel.tsx +++ b/src/components/RadioButtonWithLabel.tsx @@ -3,7 +3,6 @@ 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 FormHelpMessage from './FormHelpMessage'; import * as Pressables from './Pressable'; import RadioButton from './RadioButton'; @@ -29,7 +28,7 @@ type RadioButtonWithLabelProps = { hasError?: boolean; /** Error text to display */ - errorText?: MaybePhraseKey; + errorText?: string; }; const PressableWithFeedback = Pressables.PressableWithFeedback; diff --git a/src/components/RadioButtons.tsx b/src/components/RadioButtons.tsx index 2030ce8f0bfd..07e8fe38f772 100644 --- a/src/components/RadioButtons.tsx +++ b/src/components/RadioButtons.tsx @@ -3,7 +3,6 @@ import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {MaybePhraseKey} from '@libs/Localize'; import FormHelpMessage from './FormHelpMessage'; import RadioButtonWithLabel from './RadioButtonWithLabel'; @@ -24,7 +23,7 @@ type RadioButtonsProps = { onPress: (value: string) => void; /** Potential error text provided by a form InputWrapper */ - errorText?: MaybePhraseKey; + errorText?: string; /** Style for radio button */ radioButtonStyle?: StyleProp; diff --git a/src/components/SingleChoiceQuestion.tsx b/src/components/SingleChoiceQuestion.tsx index 3ff844dd80e9..c2dc72438e43 100644 --- a/src/components/SingleChoiceQuestion.tsx +++ b/src/components/SingleChoiceQuestion.tsx @@ -3,14 +3,13 @@ import React, {forwardRef} from 'react'; // eslint-disable-next-line no-restricted-imports import type {Text as RNText} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {MaybePhraseKey} from '@libs/Localize'; import type {Choice} from './RadioButtons'; import RadioButtons from './RadioButtons'; import Text from './Text'; type SingleChoiceQuestionProps = { prompt: string; - errorText?: MaybePhraseKey; + errorText?: string; possibleAnswers: Choice[]; currentQuestionIndex: number; onInputChange: (value: string) => void; diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index 8cae007679ff..0733cb519f2c 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -6,7 +6,6 @@ import type {View} from 'react-native'; import useGeographicalStateFromRoute from '@hooks/useGeographicalStateFromRoute'; 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 ROUTES from '@src/ROUTES'; @@ -17,7 +16,7 @@ type State = keyof typeof COMMON_CONST.STATES; type StateSelectorProps = { /** Form error text. e.g when no state is selected */ - errorText?: MaybePhraseKey; + errorText?: string; /** Current selected state */ value?: State | ''; diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index e8e2d5ab352d..7a46cca693e3 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -1,6 +1,5 @@ import type {GestureResponderEvent, StyleProp, TextInputProps, TextStyle, ViewStyle} from 'react-native'; import type {AnimatedTextInputRef} from '@components/RNTextInput'; -import type {MaybePhraseKey} from '@libs/Localize'; import type IconAsset from '@src/types/utils/IconAsset'; type CustomBaseTextInputProps = { @@ -20,7 +19,7 @@ type CustomBaseTextInputProps = { placeholder?: string; /** Error text to display */ - errorText?: MaybePhraseKey; + errorText?: string; /** Icon to display in right side of text input */ icon?: IconAsset | null; @@ -68,7 +67,7 @@ type CustomBaseTextInputProps = { hideFocusedState?: boolean; /** Hint text to display below the TextInput */ - hint?: MaybePhraseKey; + hint?: string; /** Prefix character */ prefixCharacter?: string; diff --git a/src/components/TextPicker/types.ts b/src/components/TextPicker/types.ts index 179d16a07262..cbdc1ed21efe 100644 --- a/src/components/TextPicker/types.ts +++ b/src/components/TextPicker/types.ts @@ -1,6 +1,5 @@ import type {MenuItemBaseProps} from '@components/MenuItem'; import type {BaseTextInputProps} from '@components/TextInput/BaseTextInput/types'; -import type {MaybePhraseKey} from '@libs/Localize'; type TextProps = Exclude; @@ -30,7 +29,7 @@ type TextPickerProps = { placeholder?: string; /** Form Error description */ - errorText?: MaybePhraseKey; + errorText?: string; /** Callback to call when the input changes */ onInputChange?: (value: string | undefined) => void; diff --git a/src/components/TimePicker/TimePicker.tsx b/src/components/TimePicker/TimePicker.tsx index aecaf74dc4a3..8905abd370fe 100644 --- a/src/components/TimePicker/TimePicker.tsx +++ b/src/components/TimePicker/TimePicker.tsx @@ -135,15 +135,15 @@ function TimePicker({defaultValue = '', onSubmit, onInputChange = () => {}}: Tim const hour = parseInt(hourStr, 10); if (hour === 0) { setError(true); - setErrorMessage('common.error.invalidTimeRange'); + setErrorMessage(translate('common.error.invalidTimeRange')); return false; } const isValid = DateUtils.isTimeAtLeastOneMinuteInFuture({timeString, dateTimeString: defaultValue}); setError(!isValid); - setErrorMessage('common.error.invalidTimeShouldBeFuture'); + setErrorMessage(translate('common.error.invalidTimeShouldBeFuture')); return isValid; }, - [hours, minutes, amPmValue, defaultValue], + [hours, minutes, amPmValue, defaultValue, translate], ); const resetHours = () => { diff --git a/src/components/ValuePicker/types.ts b/src/components/ValuePicker/types.ts index b9c2c89948d9..b57c9d32061a 100644 --- a/src/components/ValuePicker/types.ts +++ b/src/components/ValuePicker/types.ts @@ -1,5 +1,4 @@ import type {ListItem} from '@components/SelectionList/types'; -import type {MaybePhraseKey} from '@libs/Localize'; type ValuePickerListItem = ListItem & { value?: string; @@ -51,7 +50,7 @@ type ValuePickerProps = { placeholder?: string; /** Form Error description */ - errorText?: MaybePhraseKey; + errorText?: string; /** Callback to call when the input changes */ onInputChange?: (value: string | undefined) => void; diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 8bd37ddd698d..75ae7ae3f043 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -648,7 +648,7 @@ const getDayValidationErrorKey = (inputDate: Date): string => { } if (isAfter(startOfDay(new Date()), startOfDay(inputDate))) { - return 'common.error.invalidDateShouldBeFuture'; + return Localize.translateLocal('common.error.invalidDateShouldBeFuture'); } return ''; }; @@ -662,7 +662,7 @@ const getDayValidationErrorKey = (inputDate: Date): string => { const getTimeValidationErrorKey = (inputTime: Date): string => { const timeNowPlusOneMinute = addMinutes(new Date(), 1); if (isBefore(inputTime, timeNowPlusOneMinute)) { - return 'common.error.invalidTimeShouldBeFuture'; + return Localize.translateLocal('common.error.invalidTimeShouldBeFuture'); } return ''; }; diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 3487f05b9c05..af27dd5801f3 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -1,11 +1,11 @@ import mapValues from 'lodash/mapValues'; import CONST from '@src/CONST'; -import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; +import type {TranslationFlatObject} from '@src/languages/types'; import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; import type Response from '@src/types/onyx/Response'; import type {ReceiptError} from '@src/types/onyx/Transaction'; import DateUtils from './DateUtils'; -import * as Localize from './Localize'; +import type * as Localize from './Localize'; function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatObject { switch (response.jsonCode) { @@ -53,15 +53,15 @@ function getMicroSecondOnyxErrorObject(error: Errors, errorKey?: number): ErrorF } // We can assume that if error is a string, it has already been translated because it is server error -function getErrorMessageWithTranslationData(error: Localize.MaybePhraseKey): Localize.MaybePhraseKey { - return typeof error === 'string' ? [error, {isTranslated: true}] : error; +function getErrorMessageWithTranslationData(error: string): string { + return error; } type OnyxDataWithErrors = { errors?: Errors | null; }; -function getLatestErrorMessage(onyxData: TOnyxData | null): Localize.MaybePhraseKey { +function getLatestErrorMessage(onyxData: TOnyxData | null): string { const errors = onyxData?.errors ?? {}; if (Object.keys(errors).length === 0) { @@ -69,7 +69,7 @@ function getLatestErrorMessage(onyxData: T } const key = Object.keys(errors).sort().reverse()[0]; - return getErrorMessageWithTranslationData(errors[key]); + return getErrorMessageWithTranslationData(errors[key] ?? ''); } function getLatestErrorMessageField(onyxData: TOnyxData): Errors { @@ -148,21 +148,20 @@ function getErrorsWithTranslationData(errors: Localize.MaybePhraseKey | Errors): * @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 | null, message?: TKey | Localize.MaybePhraseKey) { +function addErrorMessage(errors: Errors, inputID?: string | null, message?: string) { if (!message || !inputID) { return; } const errorList = errors; const error = errorList[inputID]; - const translatedMessage = Localize.translateIfPhraseKey(message); if (!error) { - errorList[inputID] = [translatedMessage, {isTranslated: true}]; + errorList[inputID] = [message]; } else if (typeof error === 'string') { - errorList[inputID] = [`${error}\n${translatedMessage}`, {isTranslated: true}]; + errorList[inputID] = [`${error}\n${message}`]; } else if (Array.isArray(error)) { - error[0] = `${error[0]}\n${translatedMessage}`; + error[0] = `${error[0]}\n${message}`; } } diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index 13fe326c2c1c..7fe73526a1a5 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -11,7 +11,7 @@ import type {OnyxFormKey} from '@src/ONYXKEYS'; import type {Report, TaxRates} from '@src/types/onyx'; import * as CardUtils from './CardUtils'; import DateUtils from './DateUtils'; -import type {MaybePhraseKey} from './Localize'; +import * as Localize from './Localize'; import * as LoginUtils from './LoginUtils'; import {parsePhoneNumber} from './PhoneNumber'; import StringUtils from './StringUtils'; @@ -112,7 +112,7 @@ function getFieldRequiredErrors(values: FormOnyxVal return; } - errors[fieldKey] = 'common.error.fieldRequired'; + errors[fieldKey] = Localize.translateLocal('common.error.fieldRequired'); }); return errors; @@ -191,7 +191,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): MaybePhraseKey { +function getAgeRequirementError(date: string, minimumAge: number, maximumAge: number): Localize.MaybePhraseKey { const currentDate = startOfDay(new Date()); const testDate = parse(date, CONST.DATE.FNS_FORMAT_STRING, currentDate); @@ -222,14 +222,14 @@ function getDatePassedError(inputDate: string): string { // If input date is not valid, return an error if (!isValid(parsedDate)) { - return 'common.error.dateInvalid'; + return Localize.translateLocal('common.error.dateInvalid'); } // Clear time for currentDate so comparison is based solely on the date currentDate.setHours(0, 0, 0, 0); if (parsedDate < currentDate) { - return 'common.error.dateInvalid'; + return Localize.translateLocal('common.error.dateInvalid'); } return ''; diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index f8425cd0c40c..fd7fbe4e7d86 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -12,6 +12,7 @@ import INPUT_IDS from '@src/types/form/WorkspaceNewTaxForm'; import type {Policy, TaxRate, TaxRates} from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {OnyxData} from '@src/types/onyx/Request'; +import { translateLocal } from '@libs/Localize'; let allPolicies: OnyxCollection; Onyx.connect({ @@ -39,7 +40,7 @@ const validateTaxName = (policy: Policy, values: FormOnyxValues) => { const errors: FormInputErrors = {}; if (isRequired && value[fieldKey].trim() === '') { - errors[fieldKey] = 'common.error.fieldRequired'; + errors[fieldKey] = translate('common.error.fieldRequired'); } return errors; }, - [fieldKey, isRequired], + [fieldKey, isRequired, translate], ); return ( diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.tsx b/src/pages/EnablePayments/AdditionalDetailsStep.tsx index 4756db4d43ec..cc41738fa581 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.tsx +++ b/src/pages/EnablePayments/AdditionalDetailsStep.tsx @@ -76,32 +76,32 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO if (values.dob) { if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { - errors.dob = 'bankAccount.error.dob'; + errors.dob = translate('bankAccount.error.dob'); } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { - errors.dob = 'bankAccount.error.age'; + errors.dob = translate('bankAccount.error.age'); } } if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { - errors.addressStreet = 'bankAccount.error.addressStreet'; + errors.addressStreet = translate('bankAccount.error.addressStreet'); } if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { - errors.addressZipCode = 'bankAccount.error.zipCode'; + errors.addressZipCode = translate('bankAccount.error.zipCode'); } if (values.phoneNumber && !ValidationUtils.isValidUSPhone(values.phoneNumber, true)) { - errors.phoneNumber = 'bankAccount.error.phoneNumber'; + errors.phoneNumber = translate('bankAccount.error.phoneNumber'); } // walletAdditionalDetails stores errors returned by the server. If the server returns an SSN error // then the user needs to provide the full 9 digit SSN. if (walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN) { if (values.ssn && !ValidationUtils.isValidSSNFullNine(values.ssn)) { - errors.ssn = 'additionalDetailsStep.ssnFull9Error'; + errors.ssn = translate('additionalDetailsStep.ssnFull9Error'); } } else if (values.ssn && !ValidationUtils.isValidSSNLastFour(values.ssn)) { - errors.ssn = 'bankAccount.error.ssnLast4'; + errors.ssn = translate('bankAccount.error.ssnLast4'); } return errors; diff --git a/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx b/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx index fe17ea7e1afb..261613ba9318 100644 --- a/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx +++ b/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx @@ -48,7 +48,7 @@ function TermsStep() { const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); - const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(walletTerms ?? {}) ?? ''; + const errorMessage = error ? translate('common.error.acceptTerms') : ErrorUtils.getLatestErrorMessage(walletTerms ?? {}) ?? ''; const toggleDisclosure = () => { setHasAcceptedDisclosure(!hasAcceptedDisclosure); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx index cfd0f4c5e3f7..6d3a1ebe9d89 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx @@ -19,20 +19,6 @@ import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; const PERSONAL_INFO_DOB_KEY = INPUT_IDS.PERSONAL_INFO_STEP.DOB; const STEP_FIELDS = [PERSONAL_INFO_DOB_KEY]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.dob) { - if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { - errors.dob = 'bankAccount.error.dob'; - } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { - errors.dob = 'bankAccount.error.age'; - } - } - - return errors; -}; - const minDate = subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE); const maxDate = subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); @@ -40,6 +26,20 @@ function DateOfBirthStep({onNext, isEditing}: SubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.dob) { + if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { + errors.dob = translate('bankAccount.error.dob'); + } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { + errors.dob = translate('bankAccount.error.age'); + } + } + + return errors; + }; + const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); const dobDefaultValue = walletAdditionalDetails?.[PERSONAL_INFO_DOB_KEY] ?? walletAdditionalDetails?.[PERSONAL_INFO_DOB_KEY] ?? ''; diff --git a/src/pages/EnablePayments/TermsStep.tsx b/src/pages/EnablePayments/TermsStep.tsx index 916a5200a2e0..b2afb8b49fd9 100644 --- a/src/pages/EnablePayments/TermsStep.tsx +++ b/src/pages/EnablePayments/TermsStep.tsx @@ -58,7 +58,7 @@ function TermsStep(props: TermsStepProps) { const [error, setError] = useState(false); const {translate} = useLocalize(); - const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(props.walletTerms ?? {}) ?? ''; + const errorMessage = error ? translate('common.error.acceptTerms') : ErrorUtils.getLatestErrorMessage(props.walletTerms ?? {}) ?? ''; const toggleDisclosure = () => { setHasAcceptedDisclosure(!hasAcceptedDisclosure); diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx index 31ff883834cc..04281eef0d2e 100644 --- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx +++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx @@ -83,7 +83,7 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, on Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS); }, [selectedPurpose]); - const [errorMessage, setErrorMessage] = useState<'onboarding.purpose.errorSelection' | 'onboarding.purpose.errorContinue' | ''>(''); + const [errorMessage, setErrorMessage] = useState(''); const menuItems: MenuItemProps[] = Object.values(CONST.ONBOARDING_CHOICES).map((choice) => { const translationKey = `onboarding.purpose.${choice}` as const; @@ -110,11 +110,11 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, on const handleOuterClick = useCallback(() => { if (!selectedPurpose) { - setErrorMessage('onboarding.purpose.errorSelection'); + setErrorMessage(translate('onboarding.purpose.errorSelection')); } else { - setErrorMessage('onboarding.purpose.errorContinue'); + setErrorMessage(translate('onboarding.purpose.errorContinue')); } - }, [selectedPurpose]); + }, [selectedPurpose, setErrorMessage, translate]); const onboardingLocalRef = useRef(null); useImperativeHandle(isFocused ? OnboardingRefManager.ref : onboardingLocalRef, () => ({handleOuterClick}), [handleOuterClick]); @@ -147,7 +147,7 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, on buttonText={translate('common.continue')} onSubmit={() => { if (!selectedPurpose) { - setErrorMessage('onboarding.purpose.errorSelection'); + setErrorMessage(translate('onboarding.purpose.errorSelection')); return; } setErrorMessage(''); diff --git a/src/pages/ReimbursementAccount/AddressFormFields.tsx b/src/pages/ReimbursementAccount/AddressFormFields.tsx index 48af00cd4925..2133c4c5bf2e 100644 --- a/src/pages/ReimbursementAccount/AddressFormFields.tsx +++ b/src/pages/ReimbursementAccount/AddressFormFields.tsx @@ -52,7 +52,7 @@ function AddressFormFields({shouldSaveDraft = false, defaultValues, values, erro value={values?.street} defaultValue={defaultValues?.street} onInputChange={onFieldChange} - errorText={errors?.street ? 'bankAccount.error.addressStreet' : ''} + errorText={errors?.street ? translate('bankAccount.error.addressStreet') : ''} renamedInputKeys={inputKeys} maxInputLength={CONST.FORM_CHARACTER_LIMIT} isLimitedToUSA @@ -68,7 +68,7 @@ function AddressFormFields({shouldSaveDraft = false, defaultValues, values, erro value={values?.city} defaultValue={defaultValues?.city} onChangeText={(value) => onFieldChange?.({city: value})} - errorText={errors?.city ? 'bankAccount.error.addressCity' : ''} + errorText={errors?.city ? translate('bankAccount.error.addressCity') : ''} containerStyles={styles.mt6} /> @@ -94,7 +94,7 @@ function AddressFormFields({shouldSaveDraft = false, defaultValues, values, erro value={values?.zipCode} defaultValue={defaultValues?.zipCode} onChangeText={(value) => onFieldChange?.({zipCode: value})} - errorText={errors?.zipCode ? 'bankAccount.error.zipCode' : ''} + errorText={errors?.zipCode ? translate('bankAccount.error.zipCode') : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} hint={['common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}]} containerStyles={styles.mt3} diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx index f563692f0ae9..2173d19887ad 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx @@ -40,9 +40,9 @@ function DateOfBirthUBO({reimbursementAccountDraft, onNext, isEditing, beneficia if (values[dobInputID]) { if (!ValidationUtils.isValidPastDate(values[dobInputID]) || !ValidationUtils.meetsMaximumAgeRequirement(values[dobInputID])) { - errors[dobInputID] = 'bankAccount.error.dob'; + errors[dobInputID] = translate('bankAccount.error.dob'); } else if (!ValidationUtils.meetsMinimumAgeRequirement(values[dobInputID])) { - errors[dobInputID] = 'bankAccount.error.age'; + errors[dobInputID] = translate('bankAccount.error.age'); } } diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx index 6311a63a4059..5e0cd2f700c7 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx @@ -37,16 +37,6 @@ type States = keyof typeof COMMON_CONST.STATES; const BUSINESS_INFO_STEP_KEYS = INPUT_IDS.BUSINESS_INFO_STEP; const BUSINESS_INFO_STEP_INDEXES = CONST.REIMBURSEMENT_ACCOUNT.SUBSTEP_INDEX.BUSINESS_INFO; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, [BUSINESS_INFO_STEP_KEYS.HAS_NO_CONNECTION_TO_CANNABIS]); - - if (!values.hasNoConnectionToCannabis) { - errors.hasNoConnectionToCannabis = 'bankAccount.error.restrictedBusiness'; - } - - return errors; -}; - function ConfirmCompanyLabel() { const {translate} = useLocalize(); @@ -62,6 +52,16 @@ function ConfirmationBusiness({reimbursementAccount, reimbursementAccountDraft, const {translate} = useLocalize(); const styles = useThemeStyles(); + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, [BUSINESS_INFO_STEP_KEYS.HAS_NO_CONNECTION_TO_CANNABIS]); + + if (!values.hasNoConnectionToCannabis) { + errors.hasNoConnectionToCannabis = translate('bankAccount.error.restrictedBusiness'); + } + + return errors; + }; + const values = useMemo(() => getSubstepValues(BUSINESS_INFO_STEP_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); const defaultCheckboxState = reimbursementAccountDraft?.[BUSINESS_INFO_STEP_KEYS.HAS_NO_CONNECTION_TO_CANNABIS] ?? false; diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx index 8fecbe46164f..14a6d03e0670 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx @@ -29,22 +29,22 @@ type IncorporationDateBusinessProps = IncorporationDateBusinessOnyxProps & SubSt const COMPANY_INCORPORATION_DATE_KEY = INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_DATE; const STEP_FIELDS = [COMPANY_INCORPORATION_DATE_KEY]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.incorporationDate && !ValidationUtils.isValidDate(values.incorporationDate)) { - errors.incorporationDate = 'common.error.dateInvalid'; - } else if (values.incorporationDate && !ValidationUtils.isValidPastDate(values.incorporationDate)) { - errors.incorporationDate = 'bankAccount.error.incorporationDateFuture'; - } - - return errors; -}; - function IncorporationDateBusiness({reimbursementAccount, reimbursementAccountDraft, onNext, isEditing}: IncorporationDateBusinessProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.incorporationDate && !ValidationUtils.isValidDate(values.incorporationDate)) { + errors.incorporationDate = translate('common.error.dateInvalid'); + } else if (values.incorporationDate && !ValidationUtils.isValidPastDate(values.incorporationDate)) { + errors.incorporationDate = translate('bankAccount.error.incorporationDateFuture'); + } + + return errors; + }; + const defaultCompanyIncorporationDate = reimbursementAccount?.achData?.incorporationDate ?? reimbursementAccountDraft?.incorporationDate ?? ''; const handleSubmit = useReimbursementAccountStepFormSubmit({ diff --git a/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx b/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx index 03a178f186ee..94d006d72f26 100644 --- a/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx +++ b/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx @@ -30,24 +30,6 @@ const STEP_FIELDS = [ INPUT_IDS.COMPLETE_VERIFICATION.CERTIFY_TRUE_INFORMATION, ]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (!ValidationUtils.isRequiredFulfilled(values.acceptTermsAndConditions)) { - errors.acceptTermsAndConditions = 'common.error.acceptTerms'; - } - - if (!ValidationUtils.isRequiredFulfilled(values.certifyTrueInformation)) { - errors.certifyTrueInformation = 'completeVerificationStep.certifyTrueAndAccurateError'; - } - - if (!ValidationUtils.isRequiredFulfilled(values.isAuthorizedToUseBankAccount)) { - errors.isAuthorizedToUseBankAccount = 'completeVerificationStep.isAuthorizedToUseBankAccountError'; - } - - return errors; -}; - function IsAuthorizedToUseBankAccountLabel() { const {translate} = useLocalize(); return {translate('completeVerificationStep.isAuthorizedToUseBankAccount')}; @@ -76,6 +58,23 @@ function ConfirmAgreements({onNext, reimbursementAccount}: ConfirmAgreementsProp certifyTrueInformation: reimbursementAccount?.achData?.certifyTrueInformation ?? false, acceptTermsAndConditions: reimbursementAccount?.achData?.acceptTermsAndConditions ?? false, }; + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (!ValidationUtils.isRequiredFulfilled(values.acceptTermsAndConditions)) { + errors.acceptTermsAndConditions = translate('common.error.acceptTerms'); + } + + if (!ValidationUtils.isRequiredFulfilled(values.certifyTrueInformation)) { + errors.certifyTrueInformation = translate('completeVerificationStep.certifyTrueAndAccurateError'); + } + + if (!ValidationUtils.isRequiredFulfilled(values.isAuthorizedToUseBankAccount)) { + errors.isAuthorizedToUseBankAccount = translate('completeVerificationStep.isAuthorizedToUseBankAccountError'); + } + + return errors; + }; return ( ): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.dob) { - if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { - errors.dob = 'bankAccount.error.dob'; - } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { - errors.dob = 'bankAccount.error.age'; - } - } - - return errors; -}; - function DateOfBirth({reimbursementAccount, reimbursementAccountDraft, onNext, isEditing}: DateOfBirthProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.dob) { + if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { + errors.dob = translate('bankAccount.error.dob'); + } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { + errors.dob = translate('bankAccount.error.age'); + } + } + + return errors; + }; + const dobDefaultValue = reimbursementAccount?.achData?.[PERSONAL_INFO_DOB_KEY] ?? reimbursementAccountDraft?.[PERSONAL_INFO_DOB_KEY] ?? ''; const minDate = subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE); diff --git a/src/pages/Travel/TravelTerms.tsx b/src/pages/Travel/TravelTerms.tsx index 468ca9b8082a..9f26284848a4 100644 --- a/src/pages/Travel/TravelTerms.tsx +++ b/src/pages/Travel/TravelTerms.tsx @@ -23,7 +23,7 @@ function TravelTerms() { const [hasAcceptedTravelTerms, setHasAcceptedTravelTerms] = useState(false); const [error, setError] = useState(false); - const errorMessage = error ? 'travel.termsAndConditions.error' : ''; + const errorMessage = error ? translate('travel.termsAndConditions.error') : ''; const toggleTravelTerms = () => { setHasAcceptedTravelTerms(!hasAcceptedTravelTerms); diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index 46bd34006550..07037a09df11 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -16,7 +16,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import type {MaybePhraseKey} from '@libs/Localize'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {BaseTextInputRef} from '@src/components/TextInput/BaseTextInput/types'; @@ -98,7 +97,7 @@ function MoneyRequestAmountForm( const textInput = useRef(null); const moneyRequestAmountInput = useRef(null); - const [formError, setFormError] = useState(''); + const [formError, setFormError] = useState(''); const [shouldUpdateSelection, setShouldUpdateSelection] = useState(true); const isFocused = useIsFocused(); @@ -209,12 +208,12 @@ function MoneyRequestAmountForm( // Skip the check for tax amount form as 0 is a valid input const currentAmount = moneyRequestAmountInput.current?.getAmount() ?? ''; if (!currentAmount.length || (!isTaxAmountForm && isAmountInvalid(currentAmount))) { - setFormError('iou.error.invalidAmount'); + setFormError(translate('iou.error.invalidAmount')); return; } if (isTaxAmountInvalid(currentAmount, taxAmount, isTaxAmountForm)) { - setFormError(['iou.error.invalidTaxAmount', {amount: formattedTaxAmount}]); + setFormError([translate('iou.error.invalidTaxAmount'), {amount: formattedTaxAmount}]); return; } @@ -225,7 +224,7 @@ function MoneyRequestAmountForm( onSubmitButtonPress({amount: currentAmount, currency, paymentMethod: iouPaymentType}); }, - [taxAmount, onSubmitButtonPress, currency, formattedTaxAmount, initializeAmount], + [taxAmount, initializeAmount, onSubmitButtonPress, currency, translate, formattedTaxAmount], ); const buttonText: string = useMemo(() => { diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 1d9aec1ea60d..3e8cffd3869a 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -309,7 +309,7 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic )} diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx index 13d03fd557e9..b44f5d9e0610 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx @@ -135,7 +135,7 @@ function CodesStep({account, backTo}: CodesStepProps) { text={translate('common.next')} onPress={() => { if (!account?.codesAreCopied) { - setError('twoFactorAuth.errorStepCodes'); + setError(translate('twoFactorAuth.errorStepCodes')); return; } setStep(CONST.TWO_FACTOR_AUTH_STEPS.VERIFY); diff --git a/src/pages/settings/Wallet/ActivatePhysicalCardPage.tsx b/src/pages/settings/Wallet/ActivatePhysicalCardPage.tsx index 2710cadf94e4..bd6fa17bff34 100644 --- a/src/pages/settings/Wallet/ActivatePhysicalCardPage.tsx +++ b/src/pages/settings/Wallet/ActivatePhysicalCardPage.tsx @@ -105,7 +105,7 @@ function ActivatePhysicalCardPage({ activateCardCodeInputRef.current?.blur(); if (lastFourDigits.replace(CONST.MAGIC_CODE_EMPTY_CHAR, '').length !== LAST_FOUR_DIGITS_LENGTH) { - setFormError('activateCardPage.error.thatDidntMatch'); + setFormError(translate('activateCardPage.error.thatDidntMatch')); return; } if (inactiveCard?.cardID === undefined) { diff --git a/src/pages/settings/Wallet/AddDebitCardPage.tsx b/src/pages/settings/Wallet/AddDebitCardPage.tsx index 0beb3c16018d..b3bc1d839727 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.tsx +++ b/src/pages/settings/Wallet/AddDebitCardPage.tsx @@ -89,31 +89,31 @@ function DebitCardPage({formData}: DebitCardPageProps) { const errors = ValidationUtils.getFieldRequiredErrors(values, REQUIRED_FIELDS); if (values.nameOnCard && !ValidationUtils.isValidLegalName(values.nameOnCard)) { - errors.nameOnCard = 'addDebitCardPage.error.invalidName'; + errors.nameOnCard = translate('addDebitCardPage.error.invalidName'); } if (values.cardNumber && !ValidationUtils.isValidDebitCard(values.cardNumber.replace(/ /g, ''))) { - errors.cardNumber = 'addDebitCardPage.error.debitCardNumber'; + errors.cardNumber = translate('addDebitCardPage.error.debitCardNumber'); } if (values.expirationDate && !ValidationUtils.isValidExpirationDate(values.expirationDate)) { - errors.expirationDate = 'addDebitCardPage.error.expirationDate'; + errors.expirationDate = translate('addDebitCardPage.error.expirationDate'); } if (values.securityCode && !ValidationUtils.isValidSecurityCode(values.securityCode)) { - errors.securityCode = 'addDebitCardPage.error.securityCode'; + errors.securityCode = translate('addDebitCardPage.error.securityCode'); } if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { - errors.addressStreet = 'addDebitCardPage.error.addressStreet'; + errors.addressStreet = translate('addDebitCardPage.error.addressStreet'); } if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { - errors.addressZipCode = 'addDebitCardPage.error.addressZipCode'; + errors.addressZipCode = translate('addDebitCardPage.error.addressZipCode'); } if (!values.acceptTerms) { - errors.acceptTerms = 'common.error.acceptTerms'; + errors.acceptTerms = translate('common.error.acceptTerms'); } return errors; diff --git a/src/pages/settings/Wallet/ReportCardLostPage.tsx b/src/pages/settings/Wallet/ReportCardLostPage.tsx index 11790bd44cb6..813f09af74d5 100644 --- a/src/pages/settings/Wallet/ReportCardLostPage.tsx +++ b/src/pages/settings/Wallet/ReportCardLostPage.tsx @@ -183,7 +183,7 @@ function ReportCardLostPage({ @@ -201,7 +201,7 @@ function ReportCardLostPage({ diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 4286a2603341..9e4b3e487718 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -31,7 +31,6 @@ import * as CloseAccount from '@userActions/CloseAccount'; import * as Session from '@userActions/Session'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {CloseAccountForm} from '@src/types/form'; import type {Account, Credentials} from '@src/types/onyx'; @@ -59,7 +58,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false const {translate} = useLocalize(); const input = useRef(null); const [login, setLogin] = useState(() => Str.removeSMSDomain(credentials?.login ?? '')); - const [formError, setFormError] = useState(); + const [formError, setFormError] = useState(); const prevIsVisible = usePrevious(isVisible); const firstBlurred = useRef(false); const isFocused = useIsFocused(); @@ -73,7 +72,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false (value: string) => { const loginTrim = value.trim(); if (!loginTrim) { - setFormError('common.pleaseEnterEmailOrPhoneNumber'); + setFormError(translate('common.pleaseEnterEmailOrPhoneNumber')); return false; } @@ -82,9 +81,9 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false if (!Str.isValidEmail(loginTrim) && !parsedPhoneNumber.possible) { if (ValidationUtils.isNumericWithSpecialChars(loginTrim)) { - setFormError('common.error.phoneNumber'); + setFormError(translate('common.error.phoneNumber')); } else { - setFormError('loginForm.error.invalidFormatEmailLogin'); + setFormError(translate('loginForm.error.invalidFormatEmailLogin')); } return false; } @@ -92,7 +91,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false setFormError(undefined); return true; }, - [setFormError], + [setFormError, translate], ); /** diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx index 488d3d48eae9..7aaa5a03576b 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -323,7 +323,7 @@ function BaseValidateCodeForm({account, credentials, session, autoComplete, isUs onChangeText={(text) => onTextInput(text, 'twoFactorAuthCode')} onFulfill={validateAndSubmitForm} maxLength={CONST.TFA_CODE_LENGTH} - errorText={formError?.twoFactorAuthCode ?? ''} + errorText={formError?.twoFactorAuthCode ? translate(formError?.twoFactorAuthCode) : ''} hasError={hasError} autoFocus key="twoFactorAuthCode" @@ -356,7 +356,7 @@ function BaseValidateCodeForm({account, credentials, session, autoComplete, isUs value={validateCode} onChangeText={(text) => onTextInput(text, 'validateCode')} onFulfill={validateAndSubmitForm} - errorText={formError?.validateCode ?? ''} + errorText={formError?.validateCode ? translate(formError?.validateCode) : ''} hasError={hasError} autoFocus key="validateCode" diff --git a/src/pages/tasks/NewTaskPage.tsx b/src/pages/tasks/NewTaskPage.tsx index d038e8260418..9fdb0ae67a65 100644 --- a/src/pages/tasks/NewTaskPage.tsx +++ b/src/pages/tasks/NewTaskPage.tsx @@ -99,17 +99,17 @@ function NewTaskPage({task, reports, personalDetails}: NewTaskPageProps) { // the response const onSubmit = () => { if (!task?.title && !task?.shareDestination) { - setErrorMessage('newTaskPage.confirmError'); + setErrorMessage(translate('newTaskPage.confirmError')); return; } if (!task.title) { - setErrorMessage('newTaskPage.pleaseEnterTaskName'); + setErrorMessage(translate('newTaskPage.pleaseEnterTaskName')); return; } if (!task.shareDestination) { - setErrorMessage('newTaskPage.pleaseEnterTaskDestination'); + setErrorMessage(translate('newTaskPage.pleaseEnterTaskDestination')); return; } diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 9142361a531e..f1dbbf1047f4 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -277,7 +277,7 @@ function WorkspaceInvitePage({route, betas, invitedEmailsToAccountIDsDraft, poli isAlertVisible={shouldShowAlertPrompt} buttonText={translate('common.next')} onSubmit={inviteUser} - message={[policy?.alertMessage ?? '', {isTranslated: true}]} + message={policy?.alertMessage ?? ''} containerStyles={[styles.flexReset, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto]} enabledWhenOffline disablePressOnEnter diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index c537ee50d19b..26c87bd0ed4f 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -411,7 +411,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, Policy.dismissAddedWithPrimaryLoginMessages(policyID)} /> diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 382c1dde6d47..cf5d3c1fb3aa 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -169,18 +169,18 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli if (!values.roomName || values.roomName === CONST.POLICY.ROOM_PREFIX) { // We error if the user doesn't enter a room name or left blank - ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.pleaseEnterRoomName'); + ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.pleaseEnterRoomName')); } else if (values.roomName !== CONST.POLICY.ROOM_PREFIX && !ValidationUtils.isValidRoomName(values.roomName)) { // We error if the room name has invalid characters - ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomNameInvalidError'); + ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomNameInvalidError')); } else if (ValidationUtils.isReservedRoomName(values.roomName)) { // Certain names are reserved for default rooms and should not be used for policy rooms. - ErrorUtils.addErrorMessage(errors, 'roomName', ['newRoomPage.roomNameReservedError', {reservedName: values.roomName}]); + ErrorUtils.addErrorMessage(errors, 'roomName', [translate('newRoomPage.roomNameReservedError'), {reservedName: values.roomName}]); } else if (ValidationUtils.isExistingRoomName(values.roomName, reports, values.policyID ?? '')) { // Certain names are reserved for default rooms and should not be used for policy rooms. - ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomAlreadyExistsError'); + ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomAlreadyExistsError')); } else if (values.roomName.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'roomName', ['common.error.characterLimitExceedCounter', {length: values.roomName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'roomName', [translate('common.error.characterLimitExceedCounter'), {length: values.roomName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); } const descriptionLength = ReportUtils.getCommentLength(values.reportDescription); diff --git a/src/stories/Form.stories.tsx b/src/stories/Form.stories.tsx index f4e89f6766f0..ab29612b0556 100644 --- a/src/stories/Form.stories.tsx +++ b/src/stories/Form.stories.tsx @@ -12,7 +12,6 @@ import Picker from '@components/Picker'; import StateSelector from '@components/StateSelector'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import type {MaybePhraseKey} from '@libs/Localize'; import NetworkConnection from '@libs/NetworkConnection'; import * as ValidationUtils from '@libs/ValidationUtils'; import * as FormActions from '@userActions/FormActions'; @@ -58,7 +57,7 @@ function Template(props: FormProviderProps) { FormActions.setDraftValues(props.formID, props.draftValues); if (props.formState?.error) { - FormActions.setErrors(props.formID, {error: props.formState.error as MaybePhraseKey}); + FormActions.setErrors(props.formID, {error: props.formState.error as string}); } else { FormActions.clearErrors(props.formID); } @@ -172,7 +171,7 @@ function WithNativeEventHandler(props: FormProviderProps) { FormActions.setDraftValues(props.formID, props.draftValues); if (props.formState?.error) { - FormActions.setErrors(props.formID, {error: props.formState.error as MaybePhraseKey}); + FormActions.setErrors(props.formID, {error: props.formState.error as string}); } else { FormActions.clearErrors(props.formID); } diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index 8b96a89a2a1b..37a06f20ca63 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -1,7 +1,7 @@ import type {ValueOf} from 'type-fest'; -import type {MaybePhraseKey} from '@libs/Localize'; import type {AvatarSource} from '@libs/UserUtils'; import type CONST from '@src/CONST'; +import type { MaybePhraseKey } from '@libs/Localize'; type PendingAction = ValueOf | null; @@ -19,7 +19,7 @@ type OnyxValueWithOfflineFeedback = keyof TO type ErrorFields = Record; -type Errors = Record; +type Errors = Record; type AvatarType = typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; From 7010adfa191473171f573d8cdb742e20a62107fb Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 13:02:54 +0700 Subject: [PATCH 005/127] Fix translate error for DotIndicatorMessage --- src/components/AddressForm.tsx | 73 ++++++++++--------- src/components/DotIndicatorMessage.tsx | 11 ++- src/components/MessagesRow.tsx | 3 +- .../MoneyRequestConfirmationList.tsx | 20 ++++- src/components/OfflineWithFeedback.tsx | 10 +-- src/components/PDFView/PDFPasswordForm.tsx | 2 +- src/libs/ErrorUtils.ts | 24 +++--- src/libs/actions/IOU.ts | 4 +- src/libs/actions/TaxRate.ts | 2 +- .../PersonalInfo/substeps/DateOfBirthStep.tsx | 4 +- .../substeps/ConfirmationBusiness.tsx | 2 +- .../substeps/IncorporationDateBusiness.tsx | 4 +- .../substeps/ConfirmAgreements.tsx | 8 +- .../PersonalInfo/substeps/DateOfBirth.tsx | 4 +- .../MoneyRequestParticipantsSelector.tsx | 2 +- .../request/step/IOURequestStepDistance.tsx | 2 +- .../Contacts/ContactMethodDetailsPage.tsx | 2 +- .../Wallet/ActivatePhysicalCardPage.tsx | 2 +- .../settings/Wallet/ReportCardLostPage.tsx | 4 +- src/pages/signin/LoginForm/BaseLoginForm.tsx | 2 +- src/pages/workspace/WorkspaceNewRoomPage.tsx | 15 ++-- src/types/onyx/OnyxCommon.ts | 1 - 22 files changed, 107 insertions(+), 94 deletions(-) diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index bd3ad7ea0888..dc5c47ad182c 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -88,50 +88,53 @@ function AddressForm({ * @returns - An object containing the errors for each inputID */ - const validator = useCallback((values: FormOnyxValues): 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'); - } - - // 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): 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] = translate('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', '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 = ['privatePersonalDetails.error.incorrectZipFormat', countryZipFormat]; + } else { + errors.zipPostCode = 'common.error.fieldRequired'; + } } + } else if (!CONST.GENERIC_ZIP_CODE_REGEX.test(values?.zipPostCode?.trim()?.toUpperCase() ?? '')) { + errors.zipPostCode = 'privatePersonalDetails.error.incorrectZipFormat'; } - } else if (!CONST.GENERIC_ZIP_CODE_REGEX.test(values?.zipPostCode?.trim()?.toUpperCase() ?? '')) { - errors.zipPostCode = 'privatePersonalDetails.error.incorrectZipFormat'; - } - return errors; - }, [translate]); + return errors; + }, + [translate], + ); return ( ; + messages: Record; /** The type of message, 'error' shows a red dot, 'success' shows a green dot */ type: 'error' | 'success'; @@ -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 = Object.keys(messages) + const sortedMessages: Array = 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 = [...new Set(sortedMessages)].map((message) => (isReceiptError(message) ? message : Localize.translateIfPhraseKey(message))); + const uniqueMessages: Array = [...new Set(sortedMessages)].map((message) => message); const isErrorMessage = type === 'error'; diff --git a/src/components/MessagesRow.tsx b/src/components/MessagesRow.tsx index 2aead5da334c..6a5be5db07bc 100644 --- a/src/components/MessagesRow.tsx +++ b/src/components/MessagesRow.tsx @@ -5,6 +5,7 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; +import type {ReceiptError} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import DotIndicatorMessage from './DotIndicatorMessage'; import Icon from './Icon'; @@ -14,7 +15,7 @@ import Tooltip from './Tooltip'; type MessagesRowProps = { /** The messages to display */ - messages: Record; + messages: Record; /** The type of message, 'error' shows a red dot, 'success' shows a green dot */ type: 'error' | 'success'; diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 31fcdaa4843e..c745ca34251d 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -738,7 +738,25 @@ function MoneyRequestConfirmationList({ onConfirm?.(selectedParticipants); } }, - [selectedParticipants, isEditingSplitBill, isMerchantRequired, isMerchantEmpty, shouldDisplayFieldError, transaction, iouCategory.length, formError, iouType, setFormError, translate, onSendMoney, iouCurrencyCode, isDistanceRequest, isDistanceRequestWithPendingRoute, iouAmount, onConfirm], + [ + selectedParticipants, + isEditingSplitBill, + isMerchantRequired, + isMerchantEmpty, + shouldDisplayFieldError, + transaction, + iouCategory.length, + formError, + iouType, + setFormError, + translate, + onSendMoney, + iouCurrencyCode, + isDistanceRequest, + isDistanceRequestWithPendingRoute, + iouAmount, + onConfirm, + ], ); const footerContent = useMemo(() => { diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 1ff3ee2ed737..70354c4a4676 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -5,8 +5,6 @@ import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import type {MaybePhraseKey} from '@libs/Localize'; import mapChildrenFlat from '@libs/mapChildrenFlat'; import shouldRenderOffscreen from '@libs/shouldRenderOffscreen'; import type {AllStyles} from '@styles/utils/types'; @@ -63,10 +61,6 @@ type OfflineWithFeedbackProps = ChildrenProps & { type StrikethroughProps = Partial & {style: Array}; -function isMaybePhraseKeyType(message: unknown): message is MaybePhraseKey { - return typeof message === 'string' || Array.isArray(message); -} - function OfflineWithFeedback({ pendingAction, canDismissError = true, @@ -90,8 +84,8 @@ function OfflineWithFeedback({ // Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages. const errorEntries = Object.entries(errors ?? {}); - const filteredErrorEntries = errorEntries.filter((errorEntry): errorEntry is [string, MaybePhraseKey | ReceiptError] => errorEntry[1] !== null); - const errorMessages = mapValues(Object.fromEntries(filteredErrorEntries), (error) => (isMaybePhraseKeyType(error) ? ErrorUtils.getErrorMessageWithTranslationData(error) : error)); + const filteredErrorEntries = errorEntries.filter((errorEntry): errorEntry is [string, string | ReceiptError] => errorEntry[1] !== null); + const errorMessages = mapValues(Object.fromEntries(filteredErrorEntries), (error) => error); const hasErrorMessages = !isEmptyObject(errorMessages); const isOfflinePendingAction = !!isOffline && !!pendingAction; diff --git a/src/components/PDFView/PDFPasswordForm.tsx b/src/components/PDFView/PDFPasswordForm.tsx index e571c2830b3c..e212d79b27d6 100644 --- a/src/components/PDFView/PDFPasswordForm.tsx +++ b/src/components/PDFView/PDFPasswordForm.tsx @@ -53,7 +53,7 @@ function PDFPasswordForm({isFocused, isPasswordInvalid = false, shouldShowLoadin return validationErrorText; } return ''; - }, [isPasswordInvalid, validationErrorText]); + }, [isPasswordInvalid, validationErrorText, translate]); useEffect(() => { if (!isFocused) { diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index af27dd5801f3..2019d5d2daf5 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -1,11 +1,11 @@ import mapValues from 'lodash/mapValues'; import CONST from '@src/CONST'; -import type {TranslationFlatObject} from '@src/languages/types'; +import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; import type Response from '@src/types/onyx/Response'; import type {ReceiptError} from '@src/types/onyx/Transaction'; import DateUtils from './DateUtils'; -import type * as Localize from './Localize'; +import * as Localize from './Localize'; function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatObject { switch (response.jsonCode) { @@ -40,8 +40,8 @@ function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatO * Method used to get an error object with microsecond as the key. * @param error - error key or message to be saved */ -function getMicroSecondOnyxError(error: string, isTranslated = false, errorKey?: number): Errors { - return {[errorKey ?? DateUtils.getMicroseconds()]: error && [error, {isTranslated}]}; +function getMicroSecondOnyxError(error: TranslationPaths, _isTranslated = false, errorKey?: number): Errors { + return {[errorKey ?? DateUtils.getMicroseconds()]: Localize.translateLocal(error)}; } /** @@ -53,8 +53,8 @@ function getMicroSecondOnyxErrorObject(error: Errors, errorKey?: number): ErrorF } // We can assume that if error is a string, it has already been translated because it is server error -function getErrorMessageWithTranslationData(error: string): string { - return error; +function getErrorMessageWithTranslationData(error: string | null): string { + return error ?? ''; } type OnyxDataWithErrors = { @@ -130,12 +130,12 @@ function getLatestErrorFieldForAnyField; Onyx.connect({ diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx index 6d3a1ebe9d89..7c4f5f43f2a1 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx @@ -28,7 +28,7 @@ function DateOfBirthStep({onNext, isEditing}: SubStepProps) { const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - + if (values.dob) { if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { errors.dob = translate('bankAccount.error.dob'); @@ -36,7 +36,7 @@ function DateOfBirthStep({onNext, isEditing}: SubStepProps) { errors.dob = translate('bankAccount.error.age'); } } - + return errors; }; diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx index 5e0cd2f700c7..fa660c98d8bc 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx @@ -54,7 +54,7 @@ function ConfirmationBusiness({reimbursementAccount, reimbursementAccountDraft, const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, [BUSINESS_INFO_STEP_KEYS.HAS_NO_CONNECTION_TO_CANNABIS]); - + if (!values.hasNoConnectionToCannabis) { errors.hasNoConnectionToCannabis = translate('bankAccount.error.restrictedBusiness'); } diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx index 14a6d03e0670..58a1feb710c2 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx @@ -35,13 +35,13 @@ function IncorporationDateBusiness({reimbursementAccount, reimbursementAccountDr const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - + if (values.incorporationDate && !ValidationUtils.isValidDate(values.incorporationDate)) { errors.incorporationDate = translate('common.error.dateInvalid'); } else if (values.incorporationDate && !ValidationUtils.isValidPastDate(values.incorporationDate)) { errors.incorporationDate = translate('bankAccount.error.incorporationDateFuture'); } - + return errors; }; diff --git a/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx b/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx index 94d006d72f26..1e2f004b9c8d 100644 --- a/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx +++ b/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx @@ -60,19 +60,19 @@ function ConfirmAgreements({onNext, reimbursementAccount}: ConfirmAgreementsProp }; const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - + if (!ValidationUtils.isRequiredFulfilled(values.acceptTermsAndConditions)) { errors.acceptTermsAndConditions = translate('common.error.acceptTerms'); } - + if (!ValidationUtils.isRequiredFulfilled(values.certifyTrueInformation)) { errors.certifyTrueInformation = translate('completeVerificationStep.certifyTrueAndAccurateError'); } - + if (!ValidationUtils.isRequiredFulfilled(values.isAuthorizedToUseBankAccount)) { errors.isAuthorizedToUseBankAccount = translate('completeVerificationStep.isAuthorizedToUseBankAccountError'); } - + return errors; }; diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx index 3c3ed7cee480..c47139af8a90 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx @@ -38,7 +38,7 @@ function DateOfBirth({reimbursementAccount, reimbursementAccountDraft, onNext, i const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - + if (values.dob) { if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { errors.dob = translate('bankAccount.error.dob'); @@ -46,7 +46,7 @@ function DateOfBirth({reimbursementAccount, reimbursementAccountDraft, onNext, i errors.dob = translate('bankAccount.error.age'); } } - + return errors; }; diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 3e8cffd3869a..d3afa0ad3a36 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -309,7 +309,7 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic )} diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index b486510ead0a..e2f665c52cae 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -326,7 +326,7 @@ function IOURequestStepDistance({ return {duplicateWaypointsError: translate('iou.error.duplicateWaypointsErrorMessage')} as Errors; } if (atLeastTwoDifferentWaypointsError) { - return {atLeastTwoDifferentWaypointsError: 'iou.error.atLeastTwoDifferentWaypoints'} as Errors; + return {atLeastTwoDifferentWaypointsError: translate('iou.error.atLeastTwoDifferentWaypoints')} as Errors; } return {}; }; diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 8aaeb7151563..0e392bb8b3c0 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -231,7 +231,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { type="success" style={[themeStyles.mb3]} // eslint-disable-next-line @typescript-eslint/naming-convention - messages={{0: ['contacts.enterMagicCode', {contactMethod: formattedContactMethod}]}} + messages={{0: translate('contacts.enterMagicCode', {contactMethod: formattedContactMethod})}} /> ; diff --git a/src/pages/settings/Wallet/ReportCardLostPage.tsx b/src/pages/settings/Wallet/ReportCardLostPage.tsx index 813f09af74d5..4a84f0294d78 100644 --- a/src/pages/settings/Wallet/ReportCardLostPage.tsx +++ b/src/pages/settings/Wallet/ReportCardLostPage.tsx @@ -183,7 +183,7 @@ function ReportCardLostPage({ @@ -201,7 +201,7 @@ function ReportCardLostPage({ diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 9e4b3e487718..097bb843402a 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -265,7 +265,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false style={[styles.mv2]} type="success" // eslint-disable-next-line @typescript-eslint/naming-convention,@typescript-eslint/prefer-nullish-coalescing - messages={{0: closeAccount?.success ? [closeAccount.success, {isTranslated: true}] : account?.message || ''}} + messages={{0: closeAccount?.success ? closeAccount.success : account?.message || ''}} /> )} { diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index cf5d3c1fb3aa..436edd0f1c89 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -175,20 +175,21 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomNameInvalidError')); } else if (ValidationUtils.isReservedRoomName(values.roomName)) { // Certain names are reserved for default rooms and should not be used for policy rooms. - ErrorUtils.addErrorMessage(errors, 'roomName', [translate('newRoomPage.roomNameReservedError'), {reservedName: values.roomName}]); + ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomNameReservedError', {reservedName: values.roomName})); } else if (ValidationUtils.isExistingRoomName(values.roomName, reports, values.policyID ?? '')) { // Certain names are reserved for default rooms and should not be used for policy rooms. ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomAlreadyExistsError')); } else if (values.roomName.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'roomName', [translate('common.error.characterLimitExceedCounter'), {length: values.roomName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'roomName', translate('common.error.characterLimitExceedCounter', {length: values.roomName.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } const descriptionLength = ReportUtils.getCommentLength(values.reportDescription); if (descriptionLength > CONST.REPORT_DESCRIPTION.MAX_LENGTH) { - ErrorUtils.addErrorMessage(errors, 'reportDescription', [ - 'common.error.characterLimitExceedCounter', - {length: descriptionLength, limit: CONST.REPORT_DESCRIPTION.MAX_LENGTH}, - ]); + ErrorUtils.addErrorMessage( + errors, + 'reportDescription', + translate('common.error.characterLimitExceedCounter', {length: descriptionLength, limit: CONST.REPORT_DESCRIPTION.MAX_LENGTH}), + ); } if (!values.policyID) { @@ -197,7 +198,7 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli return errors; }, - [reports], + [reports, translate], ); const writeCapabilityOptions = useMemo( diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index 37a06f20ca63..98c8066da8f4 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -1,7 +1,6 @@ import type {ValueOf} from 'type-fest'; import type {AvatarSource} from '@libs/UserUtils'; import type CONST from '@src/CONST'; -import type { MaybePhraseKey } from '@libs/Localize'; type PendingAction = ValueOf | null; From 7d39bf56cf5db269ffaabc35961892475cde38be Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 13:37:29 +0700 Subject: [PATCH 006/127] fix test --- tests/actions/IOUTest.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 940d533b9d2b..fcfe94ce2952 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -10,6 +10,7 @@ import * as Report from '@src/libs/actions/Report'; import * as ReportActions from '@src/libs/actions/ReportActions'; import * as User from '@src/libs/actions/User'; import DateUtils from '@src/libs/DateUtils'; +import * as Localize from '@src/libs/Localize'; import Navigation from '@src/libs/Navigation/Navigation'; import * as NumberUtils from '@src/libs/NumberUtils'; import * as PersonalDetailsUtils from '@src/libs/PersonalDetailsUtils'; @@ -769,7 +770,7 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); expect(transaction?.pendingAction).toBeFalsy(); expect(transaction?.errors).toBeTruthy(); - expect(Object.values(transaction?.errors ?? {})[0]).toEqual(expect.arrayContaining(['iou.error.genericCreateFailureMessage', {isTranslated: false}])); + expect(Object.values(transaction?.errors ?? {})[0]).toEqual(Localize.translateLocal('iou.error.genericCreateFailureMessage')); resolve(); }, }); From 6177f8b0f07aefbf4b3f350df62503aea0985646 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 13:45:04 +0700 Subject: [PATCH 007/127] fix error utils test --- src/libs/ErrorUtils.ts | 2 +- tests/unit/ErrorUtilsTest.ts | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 2019d5d2daf5..2621aa4ebd72 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -148,7 +148,7 @@ function getErrorsWithTranslationData(errors: Errors): Errors { * @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 | null, message?: string) { +function addErrorMessage(errors: Errors, inputID?: string | null, message?: string | null) { if (!message || !inputID) { return; } diff --git a/tests/unit/ErrorUtilsTest.ts b/tests/unit/ErrorUtilsTest.ts index 9168c1ca12a5..6f615ab20436 100644 --- a/tests/unit/ErrorUtilsTest.ts +++ b/tests/unit/ErrorUtilsTest.ts @@ -6,21 +6,21 @@ describe('ErrorUtils', () => { const errors: Errors = {}; ErrorUtils.addErrorMessage(errors, 'username', 'Username cannot be empty'); - expect(errors).toEqual({username: ['Username cannot be empty', {isTranslated: true}]}); + expect(errors).toEqual({username: 'Username cannot be empty'}); }); test('should append an error message to an existing error message for a given inputID', () => { const errors: Errors = {username: 'Username cannot be empty'}; ErrorUtils.addErrorMessage(errors, 'username', 'Username must be at least 6 characters long'); - expect(errors).toEqual({username: ['Username cannot be empty\nUsername must be at least 6 characters long', {isTranslated: true}]}); + expect(errors).toEqual({username: 'Username cannot be empty\nUsername must be at least 6 characters long'}); }); test('should add an error to input which does not contain any errors yet', () => { const errors: Errors = {username: 'Username cannot be empty'}; ErrorUtils.addErrorMessage(errors, 'password', 'Password cannot be empty'); - expect(errors).toEqual({username: 'Username cannot be empty', password: ['Password cannot be empty', {isTranslated: true}]}); + expect(errors).toEqual({username: 'Username cannot be empty', password: 'Password cannot be empty'}); }); test('should not mutate the errors object when message is empty', () => { @@ -59,10 +59,7 @@ describe('ErrorUtils', () => { ErrorUtils.addErrorMessage(errors, 'username', 'Username must not contain special characters'); expect(errors).toEqual({ - username: [ - 'Username cannot be empty\nUsername must be at least 6 characters long\nUsername must contain at least one letter\nUsername must not contain special characters', - {isTranslated: true}, - ], + username: 'Username cannot be empty\nUsername must be at least 6 characters long\nUsername must contain at least one letter\nUsername must not contain special characters', }); }); }); From 04512137e4405c537ff75664aa6950b4fc43c929 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 13:51:18 +0700 Subject: [PATCH 008/127] complete fix tes --- tests/unit/ErrorUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/ErrorUtilsTest.ts b/tests/unit/ErrorUtilsTest.ts index 6f615ab20436..f5517c56156a 100644 --- a/tests/unit/ErrorUtilsTest.ts +++ b/tests/unit/ErrorUtilsTest.ts @@ -50,7 +50,7 @@ describe('ErrorUtils', () => { ErrorUtils.addErrorMessage(errors, 'username', 'Username must be at least 6 characters long'); ErrorUtils.addErrorMessage(errors, 'username', 'Username must contain at least one letter'); - expect(errors).toEqual({username: ['Username cannot be empty\nUsername must be at least 6 characters long\nUsername must contain at least one letter', {isTranslated: true}]}); + expect(errors).toEqual({username: 'Username cannot be empty\nUsername must be at least 6 characters long\nUsername must contain at least one letter'}); }); test('should append multiple error messages to an existing error message for the same inputID', () => { From 7b70d73d6614270502ce87f49a10eb2474f44826 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 14:54:12 +0700 Subject: [PATCH 009/127] fix translation error --- src/libs/ErrorUtils.ts | 4 ++-- src/pages/ChatFinderPage/index.tsx | 2 +- src/pages/iou/request/MoneyRequestParticipantsSelector.tsx | 2 +- tests/actions/IOUTest.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 2621aa4ebd72..cef907a91cfc 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -126,9 +126,9 @@ function getLatestErrorFieldForAnyField option === action); diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index fcfe94ce2952..63417b7ddd3b 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -1862,7 +1862,7 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); const updatedAction = Object.values(allActions ?? {}).find((reportAction) => !isEmptyObject(reportAction)); expect(updatedAction?.actionName).toEqual('MODIFIEDEXPENSE'); - expect(Object.values(updatedAction?.errors ?? {})).toEqual(expect.arrayContaining([['iou.error.genericEditFailureMessage', {isTranslated: false}]])); + expect(Object.values(updatedAction?.errors ?? {})).toEqual(Localize.translateLocal('iou.error.genericEditFailureMessage')); resolve(); }, }); @@ -2084,7 +2084,7 @@ describe('actions/IOU', () => { callback: (allActions) => { Onyx.disconnect(connectionID); const erroredAction = Object.values(allActions ?? {}).find((action) => !isEmptyObject(action?.errors)); - expect(Object.values(erroredAction?.errors ?? {})).toEqual(expect.arrayContaining([['iou.error.other', {isTranslated: false}]])); + expect(Object.values(erroredAction?.errors ?? {})).toEqual(Localize.translateLocal('iou.error.other')); resolve(); }, }); From 55a90773cb37ac21fd93a8b43a434e3a7c7b132a Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 15:39:24 +0700 Subject: [PATCH 010/127] remove the use of MaybePhraseKey type --- src/components/AddressForm.tsx | 3 +- src/components/AddressSearch/types.ts | 3 +- src/components/Form/types.ts | 3 +- src/components/RoomNameInput/types.ts | 3 +- src/components/SelectionList/types.ts | 3 +- src/libs/ErrorUtils.ts | 9 +- src/libs/OptionsListUtils.ts | 6 +- src/libs/ValidationUtils.ts | 8 +- src/libs/actions/BankAccounts.ts | 4 +- src/libs/actions/IOU.ts | 82 +++++++++---------- src/libs/actions/Policy/Category.ts | 12 +-- src/libs/actions/Policy/Policy.ts | 50 +++++------ src/libs/actions/Policy/Tag.ts | 12 +-- src/libs/actions/Report.ts | 22 ++--- src/libs/actions/Session/index.ts | 4 +- src/libs/actions/SignInRedirect.ts | 2 +- src/libs/actions/Task.ts | 6 +- src/libs/actions/TaxRate.ts | 10 +-- src/libs/actions/User.ts | 10 +-- src/libs/actions/connections/index.ts | 6 +- src/pages/ChatFinderPage/index.tsx | 3 +- .../BaseOnboardingPersonalDetails.tsx | 4 +- .../OnboardingWork/BaseOnboardingWork.tsx | 2 +- .../AddressFormFields.tsx | 2 +- src/pages/iou/MoneyRequestAmountForm.tsx | 2 +- .../MoneyRequestParticipantsSelector.tsx | 3 +- .../step/IOURequestStepDescription.tsx | 26 +++--- .../settings/Profile/DisplayNamePage.tsx | 4 +- .../Profile/PersonalDetails/LegalNamePage.tsx | 17 ++-- src/pages/settings/Report/RoomNamePage.tsx | 6 +- .../settings/Wallet/AddDebitCardPage.tsx | 2 +- src/pages/tasks/NewTaskDescriptionPage.tsx | 6 +- src/pages/tasks/NewTaskDetailsPage.tsx | 8 +- src/pages/tasks/NewTaskTitlePage.tsx | 2 +- src/pages/tasks/TaskDescriptionPage.tsx | 4 +- src/pages/tasks/TaskTitlePage.tsx | 4 +- src/pages/workspace/WorkspaceNamePage.tsx | 4 +- .../WorkspaceProfileDescriptionPage.tsx | 4 +- .../workspace/categories/CategoryForm.tsx | 8 +- .../workspace/tags/WorkspaceCreateTagPage.tsx | 4 +- tests/actions/IOUTest.ts | 2 +- 41 files changed, 198 insertions(+), 177 deletions(-) diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index dc5c47ad182c..8601c3fa1e9f 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -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 Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; @@ -77,7 +76,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; diff --git a/src/components/AddressSearch/types.ts b/src/components/AddressSearch/types.ts index bc7acf3f7e40..82e4c3c3fc37 100644 --- a/src/components/AddressSearch/types.ts +++ b/src/components/AddressSearch/types.ts @@ -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'; @@ -35,7 +34,7 @@ type AddressSearchProps = { onBlur?: () => void; /** Error text to display */ - errorText?: MaybePhraseKey; + errorText?: string; /** Hint text to display */ hint?: string; diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 331f1c943b30..9aa8bc921164 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -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'; @@ -139,7 +138,7 @@ type FormRef = { type InputRefs = Record>; -type FormInputErrors = Partial, MaybePhraseKey>>; +type FormInputErrors = Partial, string | undefined>>; export type { FormProps, diff --git a/src/components/RoomNameInput/types.ts b/src/components/RoomNameInput/types.ts index 80f08a01e472..763d0f3f2668 100644 --- a/src/components/RoomNameInput/types.ts +++ b/src/components/RoomNameInput/types.ts @@ -1,10 +1,9 @@ import type {NativeSyntheticEvent, ReturnKeyTypeOptions, TextInputFocusEventData, TextInputSubmitEditingEventData} from 'react-native'; -import type {MaybePhraseKey} from '@libs/Localize'; type RoomNameInputProps = { value?: string; disabled?: boolean; - errorText?: MaybePhraseKey; + errorText?: string; onChangeText?: (value: string) => void; onSubmitEditing?: (event: NativeSyntheticEvent) => void; onInputChange?: (value: string) => void; diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index b3bee8fb15c5..f0d2c479c9c5 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -1,6 +1,5 @@ import type {MutableRefObject, ReactElement, ReactNode} from 'react'; import type {GestureResponderEvent, InputModeOptions, LayoutChangeEvent, SectionListData, StyleProp, TextInput, TextStyle, ViewStyle} from 'react-native'; -import type {MaybePhraseKey} from '@libs/Localize'; import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; import type CONST from '@src/CONST'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; @@ -288,7 +287,7 @@ type BaseSelectionListProps = Partial & { textInputPlaceholder?: string; /** Hint for the text input */ - textInputHint?: MaybePhraseKey; + textInputHint?: string; /** Value for the text input */ textInputValue?: string; diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index cef907a91cfc..44630efe9f45 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -40,10 +40,14 @@ function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatO * Method used to get an error object with microsecond as the key. * @param error - error key or message to be saved */ -function getMicroSecondOnyxError(error: TranslationPaths, _isTranslated = false, errorKey?: number): Errors { +function getMicroSecondOnyxErrorWithTranslationKey(error: TranslationPaths, errorKey?: number): Errors { return {[errorKey ?? DateUtils.getMicroseconds()]: Localize.translateLocal(error)}; } +function getMicroSecondOnyxErrorWithMessage(error: string, errorKey?: number): Errors { + return {[errorKey ?? DateUtils.getMicroseconds()]: error}; +} + /** * Method used to get an error object with microsecond as the key and an object as the value. * @param error - error key or message to be saved @@ -189,7 +193,8 @@ export { getLatestErrorMessage, getLatestErrorMessageField, getLatestErrorFieldForAnyField, - getMicroSecondOnyxError, + getMicroSecondOnyxErrorWithTranslationKey, + getMicroSecondOnyxErrorWithMessage, getMicroSecondOnyxErrorObject, isReceiptError, }; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index f321c10c686e..b23ce6f23847 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -532,14 +532,14 @@ function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry< const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage?.IOUTransactionID : null; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; if (TransactionUtils.hasMissingSmartscanFields(transaction ?? null) && !ReportUtils.isSettled(transaction?.reportID)) { - reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); + reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); } } else if ((ReportUtils.isIOUReport(report) || ReportUtils.isExpenseReport(report)) && report?.ownerAccountID === currentUserAccountID) { if (ReportUtils.hasMissingSmartscanFields(report?.reportID ?? '') && !ReportUtils.isSettled(report?.reportID)) { - reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); + reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); } } else if (ReportUtils.hasSmartscanError(Object.values(reportActions ?? {}))) { - reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); + reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); } // All error objects related to the report. Each object in the sources contains error messages keyed by microtime const errorSources = { diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index 7fe73526a1a5..c9144547833a 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -191,12 +191,12 @@ 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): Localize.MaybePhraseKey { +function getAgeRequirementError(date: string, minimumAge: number, maximumAge: number): string { const currentDate = startOfDay(new Date()); const testDate = parse(date, CONST.DATE.FNS_FORMAT_STRING, currentDate); if (!isValid(testDate)) { - return 'common.error.dateInvalid'; + return Localize.translateLocal('common.error.dateInvalid'); } const maximalDate = subYears(currentDate, minimumAge); @@ -207,10 +207,10 @@ function getAgeRequirementError(date: string, minimumAge: number, maximumAge: nu } if (isSameDay(testDate, maximalDate) || isAfter(testDate, maximalDate)) { - return ['privatePersonalDetails.error.dateShouldBeBefore', {dateString: format(maximalDate, CONST.DATE.FNS_FORMAT_STRING)}]; + return Localize.translateLocal('privatePersonalDetails.error.dateShouldBeBefore', {dateString: format(maximalDate, CONST.DATE.FNS_FORMAT_STRING)}); } - return ['privatePersonalDetails.error.dateShouldBeAfter', {dateString: format(minimalDate, CONST.DATE.FNS_FORMAT_STRING)}]; + return Localize.translateLocal('privatePersonalDetails.error.dateShouldBeAfter', {dateString: format(minimalDate, CONST.DATE.FNS_FORMAT_STRING)}); } /** diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index cddb2c371a60..89d0296994e3 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -163,7 +163,7 @@ function getVBBADataForOnyx(currentStep?: BankAccountStep): OnyxData { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, value: { isLoading: false, - errors: ErrorUtils.getMicroSecondOnyxError('walletPage.addBankAccountFailure'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('walletPage.addBankAccountFailure'), }, }, ], @@ -239,7 +239,7 @@ function addPersonalBankAccount(account: PlaidBankAccount) { key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, value: { isLoading: false, - errors: ErrorUtils.getMicroSecondOnyxError('walletPage.addBankAccountFailure'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('walletPage.addBankAccountFailure'), }, }, ], diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index dfaea862b888..0b4991ec2138 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -444,7 +444,7 @@ function updateDistanceRequestRate(transactionID: string, rateID: string, policy /** Helper function to get the receipt error for expenses, or the generic error if there's no receipt */ function getReceiptError(receipt?: Receipt, filename?: string, isScanRequest = true, errorKey?: number): Errors | ErrorFields { return isEmptyObject(receipt) || !isScanRequest - ? ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage', false, errorKey) + ? ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage', errorKey) : ErrorUtils.getMicroSecondOnyxErrorObject({error: CONST.IOU.RECEIPT_ERROR, source: receipt.source?.toString() ?? '', filename: filename ?? ''}, errorKey); } @@ -740,7 +740,7 @@ function buildOnyxDataForMoneyRequest( ...(isNewChatReport ? { errorFields: { - createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, } : {}), @@ -752,7 +752,7 @@ function buildOnyxDataForMoneyRequest( value: { pendingFields: null, errorFields: { - ...(shouldCreateNewMoneyRequestReport ? {createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage')} : {}), + ...(shouldCreateNewMoneyRequestReport ? {createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage')} : {}), }, }, }, @@ -764,7 +764,7 @@ function buildOnyxDataForMoneyRequest( errorFields: existingTransactionThreadReport ? null : { - createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, }, }, @@ -791,7 +791,7 @@ function buildOnyxDataForMoneyRequest( errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt?.filename, isScanRequest, errorKey), }, [iouAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, } : { @@ -811,7 +811,7 @@ function buildOnyxDataForMoneyRequest( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport.reportID}`, value: { [transactionThreadCreatedReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, }, }); @@ -1072,7 +1072,7 @@ function buildOnyxDataForInvoice( ...(isNewChatReport ? { errorFields: { - createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, } : {}), @@ -1084,7 +1084,7 @@ function buildOnyxDataForInvoice( value: { pendingFields: null, errorFields: { - createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, }, }, @@ -1093,7 +1093,7 @@ function buildOnyxDataForInvoice( key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport.reportID}`, value: { errorFields: { - createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, }, }, @@ -1101,7 +1101,7 @@ function buildOnyxDataForInvoice( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, value: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateInvoiceFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateInvoiceFailureMessage'), pendingAction: null, pendingFields: clearedPendingFields, }, @@ -1116,7 +1116,7 @@ function buildOnyxDataForInvoice( errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt?.filename, false, errorKey), }, [iouAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateInvoiceFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateInvoiceFailureMessage'), }, }, }, @@ -1125,7 +1125,7 @@ function buildOnyxDataForInvoice( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport.reportID}`, value: { [transactionThreadCreatedReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateInvoiceFailureMessage', false, errorKey), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateInvoiceFailureMessage', errorKey), }, }, }, @@ -1413,7 +1413,7 @@ function buildOnyxDataForTrackExpense( value: { pendingFields: null, errorFields: { - ...(shouldCreateNewMoneyRequestReport ? {createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage')} : {}), + ...(shouldCreateNewMoneyRequestReport ? {createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage')} : {}), }, }, }, @@ -1429,7 +1429,7 @@ function buildOnyxDataForTrackExpense( errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt?.filename, isScanRequest), }, [iouAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, } : { @@ -1474,7 +1474,7 @@ function buildOnyxDataForTrackExpense( errorFields: existingTransactionThreadReport ? null : { - createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, }, }, @@ -1494,7 +1494,7 @@ function buildOnyxDataForTrackExpense( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport.reportID}`, value: { [transactionThreadCreatedReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, }, }, @@ -1680,7 +1680,7 @@ function getDeleteTrackExpenseInformation( [reportAction.reportActionID]: { ...reportAction, pendingAction: null, - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericDeleteFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericDeleteFailureMessage'), }, }, }, @@ -2518,7 +2518,7 @@ function getUpdateMoneyRequestParams( value: { [updatedReportAction.reportActionID]: { ...(updatedReportAction as OnyxTypes.ReportAction), - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericEditFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage'), }, }, }); @@ -2802,7 +2802,7 @@ function getUpdateTrackExpenseParams( value: { [updatedReportAction.reportActionID]: { ...(updatedReportAction as OnyxTypes.ReportAction), - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericEditFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage'), }, }, }); @@ -3130,7 +3130,7 @@ const getConvertTrackedExpenseInformation = ( value: { [modifiedExpenseReportAction.reportActionID]: { ...(modifiedExpenseReportAction as OnyxTypes.ReportAction), - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericEditFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage'), }, }, }); @@ -3934,7 +3934,7 @@ function createSplitsAndOnyxData( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransaction.transactionID}`, value: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, }, ]; @@ -3945,7 +3945,7 @@ function createSplitsAndOnyxData( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`, value: { [splitIOUReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, }, }); @@ -3956,7 +3956,7 @@ function createSplitsAndOnyxData( key: `${ONYXKEYS.COLLECTION.REPORT}${splitChatReport.reportID}`, value: { errorFields: { - createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, }, }, @@ -3965,7 +3965,7 @@ function createSplitsAndOnyxData( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`, value: { [splitIOUReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, }, }, @@ -4480,7 +4480,7 @@ function startSplitBill({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransaction.transactionID}`, value: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, }, ]; @@ -4502,7 +4502,7 @@ function startSplitBill({ key: `${ONYXKEYS.COLLECTION.REPORT}${splitChatReport.reportID}`, value: { errorFields: { - createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, }, }, @@ -4511,7 +4511,7 @@ function startSplitBill({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`, value: { [splitChatCreatedReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, [splitIOUReportAction.reportActionID]: { errors: getReceiptError(receipt, filename), @@ -4686,7 +4686,7 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, value: { ...unmodifiedTransaction, - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, }, { @@ -4695,7 +4695,7 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA value: { [reportAction.reportActionID]: { ...reportAction, - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, }, }, @@ -5060,7 +5060,7 @@ function editRegularMoneyRequest( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`, value: { [updatedReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericEditFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage'), }, }, }, @@ -5741,7 +5741,7 @@ function getSendMoneyParams( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, value: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), }, }, { @@ -5749,7 +5749,7 @@ function getSendMoneyParams( key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTransactionThread.reportID}`, value: { errorFields: { - createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, }, }, @@ -5758,7 +5758,7 @@ function getSendMoneyParams( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThread.reportID}`, value: { [optimisticCreatedActionForTransactionThread.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, }, }, @@ -5777,7 +5777,7 @@ function getSendMoneyParams( key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, value: { errorFields: { - createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + createChat: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, }, }, @@ -5786,7 +5786,7 @@ function getSendMoneyParams( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, value: { [optimisticIOUReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), }, }, }, @@ -5802,7 +5802,7 @@ function getSendMoneyParams( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, value: { [optimisticIOUReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), }, }, }); @@ -5950,7 +5950,7 @@ function getPayMoneyRequestParams( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { [optimisticIOUReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), }, }, }, @@ -6190,7 +6190,7 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, value: { [optimisticApprovedReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), }, }, }, @@ -6344,7 +6344,7 @@ function submitReport(expenseReport: OnyxTypes.Report) { key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, value: { [optimisticSubmittedReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), }, }, }); @@ -6432,7 +6432,7 @@ function cancelPayment(expenseReport: OnyxTypes.Report, chatReport: OnyxTypes.Re key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, value: { [optimisticReportAction.reportActionID ?? '']: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), }, }, }, @@ -6505,7 +6505,7 @@ function detachReceipt(transactionID: string) { key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, value: { ...transaction, - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.receiptDeleteFailureError'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.receiptDeleteFailureError'), }, }, ]; diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 82bca6dc7c22..ec540e624ffb 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -92,7 +92,7 @@ function buildOptimisticPolicyCategories(policyID: string, categories: readonly (acc, category) => ({ ...acc, [category]: { - errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.createFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.categories.createFailureMessage'), pendingAction: null, }, }), @@ -211,7 +211,7 @@ function setWorkspaceCategoryEnabled(policyID: string, categoriesToUpdate: Recor acc[key] = { ...policyCategories[key], ...categoriesToUpdate[key], - errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.updateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.categories.updateFailureMessage'), pendingFields: { enabled: null, }, @@ -327,7 +327,7 @@ function renamePolicyCategory(policyID: string, policyCategory: {oldName: string ...policyCategoryToUpdate, name: policyCategory.oldName, unencodedName: decodeURIComponent(policyCategory.oldName), - errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.updateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.categories.updateFailureMessage'), pendingAction: null, }, }, @@ -380,7 +380,7 @@ function setWorkspaceRequiresCategory(policyID: string, requiresCategory: boolea key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { requiresCategory: !requiresCategory, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.updateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.categories.updateFailureMessage'), pendingFields: { requiresCategory: null, }, @@ -446,7 +446,7 @@ function deleteWorkspaceCategories(policyID: string, categoryNamesToDelete: stri value: categoryNamesToDelete.reduce>>((acc, categoryName) => { acc[categoryName] = { pendingAction: null, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.deleteFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.categories.deleteFailureMessage'), }; return acc; }, {}), @@ -579,7 +579,7 @@ function setPolicyDistanceRatesDefaultCategory(policyID: string, currentCustomUn customUnits: { [currentCustomUnit.customUnitID]: { ...currentCustomUnit, - errorFields: {defaultCategory: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + errorFields: {defaultCategory: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, pendingFields: {defaultCategory: null}, }, }, diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index fc2a8ad7970e..9bdc433b7e71 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -467,7 +467,7 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean, frequency }, autoReportingFrequency: policy.autoReportingFrequency ?? null, pendingFields: {autoReporting: null}, - errorFields: {autoReporting: ErrorUtils.getMicroSecondOnyxError('workflowsDelayedSubmissionPage.autoReportingErrorMessage')}, + errorFields: {autoReporting: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workflowsDelayedSubmissionPage.autoReportingErrorMessage')}, }, }, ]; @@ -508,7 +508,7 @@ function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf value: { autoReportingFrequency: policy.autoReportingFrequency ?? null, pendingFields: {autoReportingFrequency: null}, - errorFields: {autoReportingFrequency: ErrorUtils.getMicroSecondOnyxError('workflowsDelayedSubmissionPage.autoReportingFrequencyErrorMessage')}, + errorFields: {autoReportingFrequency: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workflowsDelayedSubmissionPage.autoReportingFrequencyErrorMessage')}, }, }, ]; @@ -549,7 +549,7 @@ function setWorkspaceAutoReportingMonthlyOffset(policyID: string, autoReportingO value: { autoReportingOffset: policy.autoReportingOffset ?? null, pendingFields: {autoReportingOffset: null}, - errorFields: {autoReportingOffset: ErrorUtils.getMicroSecondOnyxError('workflowsDelayedSubmissionPage.monthlyOffsetErrorMessage')}, + errorFields: {autoReportingOffset: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workflowsDelayedSubmissionPage.monthlyOffsetErrorMessage')}, }, }, ]; @@ -595,7 +595,7 @@ function setWorkspaceApprovalMode(policyID: string, approver: string, approvalMo approver: policy.approver ?? null, approvalMode: policy.approvalMode ?? null, pendingFields: {approvalMode: null}, - errorFields: {approvalMode: ErrorUtils.getMicroSecondOnyxError('workflowsApprovalPage.genericErrorMessage')}, + errorFields: {approvalMode: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workflowsApprovalPage.genericErrorMessage')}, }, }, ]; @@ -653,7 +653,7 @@ function setWorkspacePayer(policyID: string, reimburserEmail: string) { key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { achAccount: {reimburser: policy.achAccount?.reimburser ?? null}, - errorFields: {reimburser: ErrorUtils.getMicroSecondOnyxError('workflowsPayerPage.genericErrorMessage')}, + errorFields: {reimburser: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workflowsPayerPage.genericErrorMessage')}, pendingFields: {reimburser: null}, }, }, @@ -713,7 +713,7 @@ function setWorkspaceReimbursement(policyID: string, reimbursementChoice: ValueO isLoadingWorkspaceReimbursement: false, reimbursementChoice: policy.reimbursementChoice ?? null, achAccount: {reimburser: policy.achAccount?.reimburser ?? null}, - errorFields: {reimbursementChoice: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + errorFields: {reimbursementChoice: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, pendingFields: {reimbursementChoice: null}, }, }, @@ -810,7 +810,7 @@ function removeMembers(accountIDs: number[], policyID: string) { emailList.forEach((email) => { optimisticMembersState[email] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}; successMembersState[email] = null; - failureMembersState[email] = {errors: ErrorUtils.getMicroSecondOnyxError('workspace.people.error.genericRemove')}; + failureMembersState[email] = {errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.people.error.genericRemove')}; }); const optimisticData: OnyxUpdate[] = [ @@ -969,7 +969,7 @@ function leaveWorkspace(policyID: string) { pendingAction: policy?.pendingAction, employeeList: { [sessionEmail]: { - errors: ErrorUtils.getMicroSecondOnyxError('workspace.people.error.genericRemove'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.people.error.genericRemove'), }, }, }, @@ -1075,7 +1075,7 @@ function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newR key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { employeeList: previousEmployeeList, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.editor.genericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.editor.genericFailureMessage'), }, }, ]; @@ -1363,7 +1363,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount optimisticMembersState[email] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, role: CONST.POLICY.ROLE.USER}; successMembersState[email] = {pendingAction: null}; failureMembersState[email] = { - errors: ErrorUtils.getMicroSecondOnyxError('workspace.people.error.genericAdd'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.people.error.genericAdd'), }; }); @@ -1441,7 +1441,7 @@ function inviteMemberToWorkspace(policyID: string, inviterEmail: string) { { onyxMethod: Onyx.METHOD.MERGE, key: memberJoinKey, - value: {...failureMembersState, errors: ErrorUtils.getMicroSecondOnyxError('common.genericEditFailureMessage')}, + value: {...failureMembersState, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, }, ]; @@ -1535,7 +1535,7 @@ function deleteWorkspaceAvatar(policyID: string) { key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { errorFields: { - avatarURL: ErrorUtils.getMicroSecondOnyxError('avatarWithImagePicker.deleteWorkspaceError'), + avatarURL: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('avatarWithImagePicker.deleteWorkspaceError'), }, }, }, @@ -1594,7 +1594,7 @@ function updateGeneralSettings(policyID: string, name: string, currencyValue?: s failureRates[rateID] = { ...currentRates[rateID], pendingFields: {currency: null}, - errorFields: {currency: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + errorFields: {currency: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, }; } } @@ -1655,7 +1655,7 @@ function updateGeneralSettings(policyID: string, name: string, currencyValue?: s key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { errorFields: { - generalSettings: ErrorUtils.getMicroSecondOnyxError('workspace.editor.genericFailureMessage'), + generalSettings: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.editor.genericFailureMessage'), }, ...(customUnitID && { customUnits: { @@ -1720,7 +1720,7 @@ function updateWorkspaceDescription(policyID: string, description: string, curre key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { errorFields: { - description: ErrorUtils.getMicroSecondOnyxError('workspace.editor.genericFailureMessage'), + description: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.editor.genericFailureMessage'), }, }, }, @@ -1869,7 +1869,7 @@ function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: C rates: { [newCustomUnit.rates.customUnitRateID]: { ...currentCustomUnit.rates, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.reimburse.updateCustomUnitError'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.reimburse.updateCustomUnitError'), }, }, }, @@ -2096,7 +2096,7 @@ function buildOptimisticPolicyCategories(policyID: string, categories: readonly (acc, category) => ({ ...acc, [category]: { - errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.createFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.categories.createFailureMessage'), pendingAction: null, }, }), @@ -3635,7 +3635,7 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom [customUnitID]: { rates: { [customUnitRate.customUnitRateID ?? '']: { - errors: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), }, }, }, @@ -3753,7 +3753,7 @@ function setPolicyDistanceRatesUnit(policyID: string, currentCustomUnit: CustomU customUnits: { [currentCustomUnit.customUnitID]: { ...currentCustomUnit, - errorFields: {attributes: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + errorFields: {attributes: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, pendingFields: {attributes: null}, }, }, @@ -3799,7 +3799,7 @@ function updatePolicyDistanceRateValue(policyID: string, customUnit: CustomUnit, failureRates[rateID] = { ...currentRates[rateID], pendingFields: {rate: null}, - errorFields: {rate: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + errorFields: {rate: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, }; } } @@ -3870,7 +3870,7 @@ function setPolicyDistanceRatesEnabled(policyID: string, customUnit: CustomUnit, failureRates[rateID] = { ...currentRates[rateID], pendingFields: {enabled: null}, - errorFields: {enabled: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + errorFields: {enabled: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, }; } } @@ -3941,7 +3941,7 @@ function deletePolicyDistanceRates(policyID: string, customUnit: CustomUnit, rat failureRates[rateID] = { ...currentRates[rateID], pendingAction: null, - errors: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), }; } else { optimisticRates[rateID] = currentRates[rateID]; @@ -4037,7 +4037,7 @@ function setPolicyCustomTaxName(policyID: string, customTaxName: string) { taxRates: { name: originalCustomTaxName, pendingFields: {name: null}, - errorFields: {name: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + errorFields: {name: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, }, }, }, @@ -4089,7 +4089,7 @@ function setWorkspaceCurrencyDefault(policyID: string, taxCode: string) { taxRates: { defaultExternalID: originalDefaultExternalID, pendingFields: {defaultExternalID: null}, - errorFields: {defaultExternalID: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + errorFields: {defaultExternalID: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, }, }, }, @@ -4141,7 +4141,7 @@ function setForeignCurrencyDefault(policyID: string, taxCode: string) { taxRates: { foreignTaxDefault: originalDefaultForeignCurrencyID, pendingFields: {foreignTaxDefault: null}, - errorFields: {foreignTaxDefault: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + errorFields: {foreignTaxDefault: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, }, }, }, diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index 7760f29d6703..512300591fd1 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -160,7 +160,7 @@ function createPolicyTag(policyID: string, tagName: string) { [policyTag.name]: { tags: { [newTagName]: { - errors: ErrorUtils.getMicroSecondOnyxError('workspace.tags.genericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.tags.genericFailureMessage'), }, }, }, @@ -242,7 +242,7 @@ function setWorkspaceTagEnabled(policyID: string, tagsToUpdate: Record>>>((acc, tagName) => { - acc[tagName] = {pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxError('workspace.tags.deleteFailureMessage')}; + acc[tagName] = {pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.tags.deleteFailureMessage')}; return acc; }, {}), }, @@ -416,7 +416,7 @@ function renamePolicyTag(policyID: string, policyTag: {oldName: string; newName: [newTagName]: null, [oldTagName]: { ...oldTag, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.tags.genericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.tags.genericFailureMessage'), }, }, }, @@ -514,7 +514,7 @@ function renamePolicyTaglist(policyID: string, policyTagListName: {oldName: stri value: { errors: { [oldName]: oldName, - [newName]: ErrorUtils.getMicroSecondOnyxError('workspace.tags.genericFailureMessage'), + [newName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.tags.genericFailureMessage'), }, [newName]: null, [oldName]: oldPolicyTags, @@ -566,7 +566,7 @@ function setPolicyRequiresTag(policyID: string, requiresTag: boolean) { key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { requiresTag: !requiresTag, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.tags.genericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.tags.genericFailureMessage'), pendingFields: { requiresTag: null, }, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 33da5f6336a0..0af60ee812d6 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -569,7 +569,7 @@ function addActions(reportID: string, text = '', file?: FileObject) { failureReportActions[actionKey] = { // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style ...(action as OptimisticAddCommentReportAction), - errors: ErrorUtils.getMicroSecondOnyxError('report.genericAddCommentFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericAddCommentFailureMessage'), }; }); @@ -1746,7 +1746,7 @@ function updateReportName(reportID: string, value: string, previousValue: string reportName: null, }, errorFields: { - reportName: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReporNameEditFailureMessage'), + reportName: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericUpdateReporNameEditFailureMessage'), }, }, }, @@ -1828,7 +1828,7 @@ function updateReportField(reportID: string, reportField: PolicyReportField, pre [fieldKey]: null, }, errorFields: { - [fieldKey]: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReportFieldFailureMessage'), + [fieldKey]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericUpdateReportFieldFailureMessage'), }, }, }, @@ -1897,7 +1897,7 @@ function deleteReportField(reportID: string, reportField: PolicyReportField) { [fieldKey]: null, }, errorFields: { - [fieldKey]: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReportFieldFailureMessage'), + [fieldKey]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericUpdateReportFieldFailureMessage'), }, }, }, @@ -2074,7 +2074,7 @@ function addPolicyReport(policyReport: ReportUtils.OptimisticChatReport) { key: `${ONYXKEYS.COLLECTION.REPORT}${policyReport.reportID}`, value: { errorFields: { - addWorkspaceRoom: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'), + addWorkspaceRoom: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), }, }, }, @@ -2755,7 +2755,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails } return { ...pendingChatMember, - errors: ErrorUtils.getMicroSecondOnyxError('roomMembersPage.error.genericAdd'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('roomMembersPage.error.genericAdd'), }; }) ?? null, }, @@ -3047,7 +3047,7 @@ const updatePrivateNotes = (reportID: string, accountID: number, note: string) = value: { privateNotes: { [accountID]: { - errors: ErrorUtils.getMicroSecondOnyxError('privateNotes.error.genericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('privateNotes.error.genericFailureMessage'), }, }, }, @@ -3271,7 +3271,7 @@ function completeOnboarding( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, value: { [taskReportAction.reportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('report.genericAddCommentFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericAddCommentFailureMessage'), } as ReportAction, }, }, @@ -3353,10 +3353,10 @@ function completeOnboarding( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, value: { [introductionCommentAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('report.genericAddCommentFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericAddCommentFailureMessage'), } as ReportAction, [textCommentAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('report.genericAddCommentFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericAddCommentFailureMessage'), } as ReportAction, }, }, @@ -3394,7 +3394,7 @@ function completeOnboarding( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, value: { [videoCommentAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('report.genericAddCommentFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericAddCommentFailureMessage'), } as ReportAction, }, }); diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 303517558206..b2a433864723 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -392,7 +392,7 @@ function signInAttemptState(): OnyxData { value: { isLoading: false, loadingForm: null, - errors: ErrorUtils.getMicroSecondOnyxError('loginForm.cannotGetAccountDetails'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('loginForm.cannotGetAccountDetails'), }, }, ], @@ -673,7 +673,7 @@ function clearAccountMessages() { } function setAccountError(error: string) { - Onyx.merge(ONYXKEYS.ACCOUNT, {errors: ErrorUtils.getMicroSecondOnyxError(error, true)}); + Onyx.merge(ONYXKEYS.ACCOUNT, {errors: ErrorUtils.getMicroSecondOnyxErrorWithMessage(error)}); } // It's necessary to throttle requests to reauthenticate since calling this multiple times will cause Pusher to diff --git a/src/libs/actions/SignInRedirect.ts b/src/libs/actions/SignInRedirect.ts index c6d4ffcaa30f..e72a4b011074 100644 --- a/src/libs/actions/SignInRedirect.ts +++ b/src/libs/actions/SignInRedirect.ts @@ -34,7 +34,7 @@ function clearStorageAndRedirect(errorMessage?: string): Promise { } // `Onyx.clear` reinitializes the Onyx instance with initial values so use `Onyx.merge` instead of `Onyx.set` - Onyx.merge(ONYXKEYS.SESSION, {errors: ErrorUtils.getMicroSecondOnyxError(errorMessage)}); + Onyx.merge(ONYXKEYS.SESSION, {errors: ErrorUtils.getMicroSecondOnyxErrorWithMessage(errorMessage)}); }); } diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 55d898d3d4f3..143b30ac6406 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -269,7 +269,7 @@ function createTaskAndNavigate( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, value: { [optimisticAddCommentReport.reportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('task.genericCreateTaskFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('task.genericCreateTaskFailureMessage'), }, }, }); @@ -347,7 +347,7 @@ function completeTask(taskReport: OnyxEntry) { key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`, value: { [completedTaskReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('task.messages.error'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('task.messages.error'), }, }, }, @@ -416,7 +416,7 @@ function reopenTask(taskReport: OnyxEntry) { key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`, value: { [reopenedTaskReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('task.messages.error'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('task.messages.error'), }, }, }, diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index cdf5bd490c29..b4624a48fc7b 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -124,7 +124,7 @@ function createPolicyTax(policyID: string, taxRate: TaxRate) { taxRates: { taxes: { [taxRate.code]: { - errors: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.error.createFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.taxes.error.createFailureMessage'), }, }, }, @@ -232,7 +232,7 @@ function setPolicyTaxesEnabled(policyID: string, taxesIDsToUpdate: string[], isE acc[taxID] = { isDisabled: !!originalTaxes[taxID].isDisabled, pendingFields: {isDisabled: null}, - errorFields: {isDisabled: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.error.updateFailureMessage')}, + errorFields: {isDisabled: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.taxes.error.updateFailureMessage')}, }; return acc; }, {}), @@ -313,7 +313,7 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { taxes: taxesToDelete.reduce((acc, taxID) => { acc[taxID] = { pendingAction: null, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.error.deleteFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.taxes.error.deleteFailureMessage'), }; return acc; }, {}), @@ -377,7 +377,7 @@ function updatePolicyTaxValue(policyID: string, taxID: string, taxValue: number) [taxID]: { value: originalTaxRate.value, pendingFields: {value: null}, - errorFields: {value: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.error.updateFailureMessage')}, + errorFields: {value: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.taxes.error.updateFailureMessage')}, }, }, }, @@ -439,7 +439,7 @@ function renamePolicyTax(policyID: string, taxID: string, newName: string) { [taxID]: { name: originalTaxRate.name, pendingFields: {name: null}, - errorFields: {name: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.error.updateFailureMessage')}, + errorFields: {name: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.taxes.error.updateFailureMessage')}, }, }, }, diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index f7e90f775b65..67c9a38afb35 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -156,7 +156,7 @@ function requestContactMethodValidateCode(contactMethod: string) { [contactMethod]: { validateCodeSent: false, errorFields: { - validateCodeSent: ErrorUtils.getMicroSecondOnyxError('contacts.genericFailureMessages.requestContactMethodValidateCode'), + validateCodeSent: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('contacts.genericFailureMessages.requestContactMethodValidateCode'), }, pendingFields: { validateCodeSent: null, @@ -239,7 +239,7 @@ function deleteContactMethod(contactMethod: string, loginList: Record [settingName, null])), - errorFields: Object.fromEntries(Object.keys(configUpdate).map((settingName) => [settingName, ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')])), + errorFields: Object.fromEntries( + Object.keys(configUpdate).map((settingName) => [settingName, ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')]), + ), }, }, }, diff --git a/src/pages/ChatFinderPage/index.tsx b/src/pages/ChatFinderPage/index.tsx index 35417cf4ecd4..9ec8a24d23c3 100644 --- a/src/pages/ChatFinderPage/index.tsx +++ b/src/pages/ChatFinderPage/index.tsx @@ -13,7 +13,6 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import type {MaybePhraseKey} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import type {RootStackParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; @@ -59,7 +58,7 @@ function ChatFinderPage({betas, isSearchingForReports, navigation}: ChatFinderPa shouldInitialize: isScreenTransitionEnd, }); - const offlineMessage: MaybePhraseKey = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; + const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); const [, debouncedSearchValueInServer, setSearchValueInServer] = useDebouncedState('', 500); diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 2260fc872270..a7a78aa9048c 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -96,7 +96,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat if (!ValidationUtils.isValidDisplayName(values.firstName)) { ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.hasInvalidCharacter'); } else if (values.firstName.length > CONST.DISPLAY_NAME.MAX_LENGTH) { - ErrorUtils.addErrorMessage(errors, 'firstName', ['common.error.characterLimitExceedCounter', {length: values.firstName.length, limit: CONST.DISPLAY_NAME.MAX_LENGTH}]); + ErrorUtils.addErrorMessage(errors, 'firstName', translate('common.error.characterLimitExceedCounter', {length: values.firstName.length, limit: CONST.DISPLAY_NAME.MAX_LENGTH})); } if (ValidationUtils.doesContainReservedWord(values.firstName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.containsReservedWord'); @@ -106,7 +106,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat if (!ValidationUtils.isValidDisplayName(values.lastName)) { ErrorUtils.addErrorMessage(errors, 'lastName', 'personalDetails.error.hasInvalidCharacter'); } else if (values.lastName.length > CONST.DISPLAY_NAME.MAX_LENGTH) { - ErrorUtils.addErrorMessage(errors, 'lastName', ['common.error.characterLimitExceedCounter', {length: values.lastName.length, limit: CONST.DISPLAY_NAME.MAX_LENGTH}]); + ErrorUtils.addErrorMessage(errors, 'lastName', translate('common.error.characterLimitExceedCounter', {length: values.lastName.length, limit: CONST.DISPLAY_NAME.MAX_LENGTH})); } if (ValidationUtils.doesContainReservedWord(values.lastName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { ErrorUtils.addErrorMessage(errors, 'lastName', 'personalDetails.error.containsReservedWord'); diff --git a/src/pages/OnboardingWork/BaseOnboardingWork.tsx b/src/pages/OnboardingWork/BaseOnboardingWork.tsx index b0e01d0c8caa..cdaa264682d1 100644 --- a/src/pages/OnboardingWork/BaseOnboardingWork.tsx +++ b/src/pages/OnboardingWork/BaseOnboardingWork.tsx @@ -63,7 +63,7 @@ function BaseOnboardingWork({shouldUseNativeStyles, onboardingPurposeSelected, o } else if ([...work].length > CONST.TITLE_CHARACTER_LIMIT) { // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 // code units. - ErrorUtils.addErrorMessage(errors, 'work', ['common.error.characterLimitExceedCounter', {length: [...work].length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'work', translate('common.error.characterLimitExceedCounter', {length: [...work].length, limit: CONST.TITLE_CHARACTER_LIMIT})); } return errors; diff --git a/src/pages/ReimbursementAccount/AddressFormFields.tsx b/src/pages/ReimbursementAccount/AddressFormFields.tsx index 2133c4c5bf2e..c586c71f3d47 100644 --- a/src/pages/ReimbursementAccount/AddressFormFields.tsx +++ b/src/pages/ReimbursementAccount/AddressFormFields.tsx @@ -96,7 +96,7 @@ function AddressFormFields({shouldSaveDraft = false, defaultValues, values, erro onChangeText={(value) => onFieldChange?.({zipCode: value})} errorText={errors?.zipCode ? translate('bankAccount.error.zipCode') : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} - hint={['common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}]} + hint={translate('common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples})} containerStyles={styles.mt3} /> diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index 07037a09df11..01a0565c4f96 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -213,7 +213,7 @@ function MoneyRequestAmountForm( } if (isTaxAmountInvalid(currentAmount, taxAmount, isTaxAmountForm)) { - setFormError([translate('iou.error.invalidTaxAmount'), {amount: formattedTaxAmount}]); + setFormError(translate('iou.error.invalidTaxAmount', {amount: formattedTaxAmount})); return; } diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 2000652d9511..113ece3eca35 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -19,7 +19,6 @@ import usePermissions from '@hooks/usePermissions'; import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import type {MaybePhraseKey} from '@libs/Localize'; import type {Options} from '@libs/OptionsListUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as Policy from '@userActions/Policy/Policy'; @@ -65,7 +64,7 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic shouldInitialize: didScreenTransitionEnd, }); - const offlineMessage: MaybePhraseKey = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; + const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const isIOUSplit = iouType === CONST.IOU.TYPE.SPLIT; const isCategorizeOrShareAction = [CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].some((option) => option === action); diff --git a/src/pages/iou/request/step/IOURequestStepDescription.tsx b/src/pages/iou/request/step/IOURequestStepDescription.tsx index e8388a92e602..cbee039e2b28 100644 --- a/src/pages/iou/request/step/IOURequestStepDescription.tsx +++ b/src/pages/iou/request/step/IOURequestStepDescription.tsx @@ -94,18 +94,22 @@ function IOURequestStepDescription({ /** * @returns - An object containing the errors for each inputID */ - const validate = useCallback((values: FormOnyxValues): FormInputErrors => { - const errors = {}; - - if (values.moneyRequestComment.length > CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'moneyRequestComment', [ - 'common.error.characterLimitExceedCounter', - {length: values.moneyRequestComment.length, limit: CONST.DESCRIPTION_LIMIT}, - ]); - } + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = {}; + + if (values.moneyRequestComment.length > CONST.DESCRIPTION_LIMIT) { + ErrorUtils.addErrorMessage( + errors, + 'moneyRequestComment', + translate('common.error.characterLimitExceedCounter', {length: values.moneyRequestComment.length, limit: CONST.DESCRIPTION_LIMIT}), + ); + } - return errors; - }, []); + return errors; + }, + [translate], + ); const navigateBack = () => { Navigation.goBack(backTo); diff --git a/src/pages/settings/Profile/DisplayNamePage.tsx b/src/pages/settings/Profile/DisplayNamePage.tsx index 3ab443b23561..830c06c9a254 100644 --- a/src/pages/settings/Profile/DisplayNamePage.tsx +++ b/src/pages/settings/Profile/DisplayNamePage.tsx @@ -49,7 +49,7 @@ function DisplayNamePage({isLoadingApp = true, currentUserPersonalDetails}: Disp if (!ValidationUtils.isValidDisplayName(values.firstName)) { ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.hasInvalidCharacter'); } else if (values.firstName.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'firstName', ['common.error.characterLimitExceedCounter', {length: values.firstName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'firstName', translate('common.error.characterLimitExceedCounter', {length: values.firstName.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } if (ValidationUtils.doesContainReservedWord(values.firstName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.containsReservedWord'); @@ -59,7 +59,7 @@ function DisplayNamePage({isLoadingApp = true, currentUserPersonalDetails}: Disp if (!ValidationUtils.isValidDisplayName(values.lastName)) { ErrorUtils.addErrorMessage(errors, 'lastName', 'personalDetails.error.hasInvalidCharacter'); } else if (values.lastName.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'lastName', ['common.error.characterLimitExceedCounter', {length: values.lastName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'lastName', translate('common.error.characterLimitExceedCounter', {length: values.lastName.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } if (ValidationUtils.doesContainReservedWord(values.lastName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { ErrorUtils.addErrorMessage(errors, 'lastName', 'personalDetails.error.containsReservedWord'); diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx index 085c70b47937..1242618d1407 100644 --- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx @@ -49,10 +49,11 @@ function LegalNamePage({privatePersonalDetails, isLoadingApp = true}: LegalNameP } else if (!values.legalFirstName) { errors.legalFirstName = 'common.error.fieldRequired'; } else 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}, - ]); + ErrorUtils.addErrorMessage( + errors, + 'legalFirstName', + translate('common.error.characterLimitExceedCounter', {length: values.legalFirstName.length, limit: CONST.LEGAL_NAME.MAX_LENGTH}), + ); } if (ValidationUtils.doesContainReservedWord(values.legalFirstName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'personalDetails.error.containsReservedWord'); @@ -65,7 +66,11 @@ function LegalNamePage({privatePersonalDetails, isLoadingApp = true}: LegalNameP } else if (!values.legalLastName) { errors.legalLastName = 'common.error.fieldRequired'; } else 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}]); + ErrorUtils.addErrorMessage( + errors, + 'legalLastName', + translate('common.error.characterLimitExceedCounter', {length: values.legalLastName.length, limit: CONST.LEGAL_NAME.MAX_LENGTH}), + ); } if (ValidationUtils.doesContainReservedWord(values.legalLastName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { ErrorUtils.addErrorMessage(errors, 'legalLastName', 'personalDetails.error.containsReservedWord'); @@ -73,7 +78,7 @@ function LegalNamePage({privatePersonalDetails, isLoadingApp = true}: LegalNameP } return errors; - }, []); + }, [translate]); return ( CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'roomName', ['common.error.characterLimitExceedCounter', {length: values.roomName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'roomName', translate('common.error.characterLimitExceedCounter', {length: values.roomName.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } return errors; }, - [report, reports], + [report, reports, translate], ); return ( diff --git a/src/pages/settings/Wallet/AddDebitCardPage.tsx b/src/pages/settings/Wallet/AddDebitCardPage.tsx index b3bc1d839727..a5085b3d2315 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.tsx +++ b/src/pages/settings/Wallet/AddDebitCardPage.tsx @@ -199,7 +199,7 @@ function DebitCardPage({formData}: DebitCardPageProps) { role={CONST.ROLE.PRESENTATION} inputMode={CONST.INPUT_MODE.NUMERIC} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} - hint={['common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}]} + hint={translate('common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples})} containerStyles={[styles.mt4]} /> diff --git a/src/pages/tasks/NewTaskDescriptionPage.tsx b/src/pages/tasks/NewTaskDescriptionPage.tsx index 93ad5e63f3d7..2f186149f9ae 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.tsx +++ b/src/pages/tasks/NewTaskDescriptionPage.tsx @@ -49,7 +49,11 @@ function NewTaskDescriptionPage({task}: NewTaskDescriptionPageProps) { const errors = {}; if (values.taskDescription.length > CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'taskDescription', ['common.error.characterLimitExceedCounter', {length: values.taskDescription.length, limit: CONST.DESCRIPTION_LIMIT}]); + ErrorUtils.addErrorMessage( + errors, + 'taskDescription', + translate('common.error.characterLimitExceedCounter', {length: values.taskDescription.length, limit: CONST.DESCRIPTION_LIMIT}), + ); } return errors; diff --git a/src/pages/tasks/NewTaskDetailsPage.tsx b/src/pages/tasks/NewTaskDetailsPage.tsx index 52fdbf5523a2..ae269b1dbc81 100644 --- a/src/pages/tasks/NewTaskDetailsPage.tsx +++ b/src/pages/tasks/NewTaskDetailsPage.tsx @@ -58,10 +58,14 @@ function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) { // We error if the user doesn't enter a task name ErrorUtils.addErrorMessage(errors, 'taskTitle', 'newTaskPage.pleaseEnterTaskName'); } else if (values.taskTitle.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'taskTitle', ['common.error.characterLimitExceedCounter', {length: values.taskTitle.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'taskTitle', translate('common.error.characterLimitExceedCounter', {length: values.taskTitle.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } if (values.taskDescription.length > CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'taskDescription', ['common.error.characterLimitExceedCounter', {length: values.taskDescription.length, limit: CONST.DESCRIPTION_LIMIT}]); + ErrorUtils.addErrorMessage( + errors, + 'taskDescription', + translate('common.error.characterLimitExceedCounter', {length: values.taskDescription.length, limit: CONST.DESCRIPTION_LIMIT}), + ); } return errors; diff --git a/src/pages/tasks/NewTaskTitlePage.tsx b/src/pages/tasks/NewTaskTitlePage.tsx index 582d2a5c6500..0ff6c178d24a 100644 --- a/src/pages/tasks/NewTaskTitlePage.tsx +++ b/src/pages/tasks/NewTaskTitlePage.tsx @@ -42,7 +42,7 @@ function NewTaskTitlePage({task}: NewTaskTitlePageProps) { // We error if the user doesn't enter a task name ErrorUtils.addErrorMessage(errors, 'taskTitle', 'newTaskPage.pleaseEnterTaskName'); } else if (values.taskTitle.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'taskTitle', ['common.error.characterLimitExceedCounter', {length: values.taskTitle.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'taskTitle', translate('common.error.characterLimitExceedCounter', {length: values.taskTitle.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } return errors; diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index c48746c81239..dbceacd1e97a 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -39,11 +39,11 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti const errors = {}; if (values?.description && values.description?.length > CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'description', ['common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT})); } return errors; - }, []); + }, [translate]); const submit = useCallback( (values: FormOnyxValues) => { diff --git a/src/pages/tasks/TaskTitlePage.tsx b/src/pages/tasks/TaskTitlePage.tsx index 99558efaeb03..918d9d624f5b 100644 --- a/src/pages/tasks/TaskTitlePage.tsx +++ b/src/pages/tasks/TaskTitlePage.tsx @@ -35,11 +35,11 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) if (!title) { ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, 'newTaskPage.pleaseEnterTaskName'); } else if (title.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, ['common.error.characterLimitExceedCounter', {length: title.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, translate('common.error.characterLimitExceedCounter', {length: title.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } return errors; - }, []); + }, [translate]); const submit = useCallback( (values: FormOnyxValues) => { diff --git a/src/pages/workspace/WorkspaceNamePage.tsx b/src/pages/workspace/WorkspaceNamePage.tsx index 6eea77962334..173543043a6b 100644 --- a/src/pages/workspace/WorkspaceNamePage.tsx +++ b/src/pages/workspace/WorkspaceNamePage.tsx @@ -47,11 +47,11 @@ function WorkspaceNamePage({policy}: Props) { } else if ([...name].length > CONST.TITLE_CHARACTER_LIMIT) { // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 // code units. - ErrorUtils.addErrorMessage(errors, 'name', ['common.error.characterLimitExceedCounter', {length: [...name].length, limit: CONST.TITLE_CHARACTER_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'name', translate('common.error.characterLimitExceedCounter', {length: [...name].length, limit: CONST.TITLE_CHARACTER_LIMIT})); } return errors; - }, []); + }, [translate]); return ( CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'description', ['common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT})); } return errors; - }, []); + }, [translate]); const submit = useCallback( (values: FormOnyxValues) => { diff --git a/src/pages/workspace/categories/CategoryForm.tsx b/src/pages/workspace/categories/CategoryForm.tsx index 397677ab749a..e045889ff6b6 100644 --- a/src/pages/workspace/categories/CategoryForm.tsx +++ b/src/pages/workspace/categories/CategoryForm.tsx @@ -48,12 +48,16 @@ function CategoryForm({onSubmit, policyCategories, categoryName, validateEdit}: errors.categoryName = 'workspace.categories.invalidCategoryName'; } else if ([...newCategoryName].length > CONST.CATEGORY_NAME_LIMIT) { // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 code units. - ErrorUtils.addErrorMessage(errors, 'categoryName', ['common.error.characterLimitExceedCounter', {length: [...newCategoryName].length, limit: CONST.CATEGORY_NAME_LIMIT}]); + ErrorUtils.addErrorMessage( + errors, + 'categoryName', + translate('common.error.characterLimitExceedCounter', {length: [...newCategoryName].length, limit: CONST.CATEGORY_NAME_LIMIT}), + ); } return errors; }, - [policyCategories], + [policyCategories, translate], ); const submit = useCallback( diff --git a/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx b/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx index 0ed373f135e4..408bf4eb2366 100644 --- a/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx +++ b/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx @@ -49,12 +49,12 @@ function CreateTagPage({route, policyTags}: CreateTagPageProps) { errors.tagName = 'workspace.tags.existingTagError'; } else if ([...tagName].length > CONST.TAG_NAME_LIMIT) { // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 code units. - ErrorUtils.addErrorMessage(errors, 'tagName', ['common.error.characterLimitExceedCounter', {length: [...tagName].length, limit: CONST.TAG_NAME_LIMIT}]); + ErrorUtils.addErrorMessage(errors, 'tagName', translate('common.error.characterLimitExceedCounter', {length: [...tagName].length, limit: CONST.TAG_NAME_LIMIT})); } return errors; }, - [policyTags], + [policyTags, translate], ); const createTag = useCallback( diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 63417b7ddd3b..164e51393e21 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -1862,7 +1862,7 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); const updatedAction = Object.values(allActions ?? {}).find((reportAction) => !isEmptyObject(reportAction)); expect(updatedAction?.actionName).toEqual('MODIFIEDEXPENSE'); - expect(Object.values(updatedAction?.errors ?? {})).toEqual(Localize.translateLocal('iou.error.genericEditFailureMessage')); + expect(Object.values(updatedAction?.errors ?? {})[0]).toEqual(Localize.translateLocal('iou.error.genericEditFailureMessage')); resolve(); }, }); From 20e829fe0f9d92937b11fb9517613605d6c9be5e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 15:48:25 +0700 Subject: [PATCH 011/127] fix test --- .../Profile/PersonalDetails/LegalNamePage.tsx | 71 ++++++++++--------- src/pages/tasks/TaskDescriptionPage.tsx | 17 +++-- src/pages/tasks/TaskTitlePage.tsx | 21 +++--- src/pages/workspace/WorkspaceNamePage.tsx | 27 +++---- .../WorkspaceProfileDescriptionPage.tsx | 17 +++-- tests/actions/IOUTest.ts | 2 +- tests/unit/ValidationUtilsTest.ts | 11 ++- 7 files changed, 93 insertions(+), 73 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx index 1242618d1407..83f8456f5fd3 100644 --- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx @@ -40,45 +40,48 @@ function LegalNamePage({privatePersonalDetails, isLoadingApp = true}: LegalNameP const legalFirstName = privatePersonalDetails?.legalFirstName ?? ''; const legalLastName = privatePersonalDetails?.legalLastName ?? ''; - const validate = useCallback((values: FormOnyxValues) => { - const errors: Errors = {}; + const validate = useCallback( + (values: FormOnyxValues) => { + const errors: Errors = {}; - 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.LEGAL_NAME.MAX_LENGTH) { - ErrorUtils.addErrorMessage( - errors, - 'legalFirstName', - translate('common.error.characterLimitExceedCounter', {length: values.legalFirstName.length, limit: CONST.LEGAL_NAME.MAX_LENGTH}), - ); + 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.LEGAL_NAME.MAX_LENGTH) { + ErrorUtils.addErrorMessage( + errors, + 'legalFirstName', + translate('common.error.characterLimitExceedCounter', {length: values.legalFirstName.length, limit: CONST.LEGAL_NAME.MAX_LENGTH}), + ); + } + if (ValidationUtils.doesContainReservedWord(values.legalFirstName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { + ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'personalDetails.error.containsReservedWord'); + } } - if (ValidationUtils.doesContainReservedWord(values.legalFirstName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, 'legalFirstName', '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.LEGAL_NAME.MAX_LENGTH) { - ErrorUtils.addErrorMessage( - errors, - 'legalLastName', - translate('common.error.characterLimitExceedCounter', {length: values.legalLastName.length, limit: CONST.LEGAL_NAME.MAX_LENGTH}), - ); - } - 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.LEGAL_NAME.MAX_LENGTH) { + ErrorUtils.addErrorMessage( + errors, + 'legalLastName', + translate('common.error.characterLimitExceedCounter', {length: values.legalLastName.length, limit: CONST.LEGAL_NAME.MAX_LENGTH}), + ); + } + if (ValidationUtils.doesContainReservedWord(values.legalLastName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { + ErrorUtils.addErrorMessage(errors, 'legalLastName', 'personalDetails.error.containsReservedWord'); + } } - } - return errors; - }, [translate]); + return errors; + }, + [translate], + ); return ( ): FormInputErrors => { - const errors = {}; + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = {}; - if (values?.description && values.description?.length > CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT})); - } + if (values?.description && values.description?.length > CONST.DESCRIPTION_LIMIT) { + ErrorUtils.addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT})); + } - return errors; - }, [translate]); + return errors; + }, + [translate], + ); const submit = useCallback( (values: FormOnyxValues) => { diff --git a/src/pages/tasks/TaskTitlePage.tsx b/src/pages/tasks/TaskTitlePage.tsx index 918d9d624f5b..70ced7a81072 100644 --- a/src/pages/tasks/TaskTitlePage.tsx +++ b/src/pages/tasks/TaskTitlePage.tsx @@ -29,17 +29,20 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) const styles = useThemeStyles(); const {translate} = useLocalize(); - const validate = useCallback(({title}: FormOnyxValues): FormInputErrors => { - const errors: FormInputErrors = {}; + const validate = useCallback( + ({title}: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; - if (!title) { - ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, 'newTaskPage.pleaseEnterTaskName'); - } else if (title.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, translate('common.error.characterLimitExceedCounter', {length: title.length, limit: CONST.TITLE_CHARACTER_LIMIT})); - } + if (!title) { + ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, 'newTaskPage.pleaseEnterTaskName'); + } else if (title.length > CONST.TITLE_CHARACTER_LIMIT) { + ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, translate('common.error.characterLimitExceedCounter', {length: title.length, limit: CONST.TITLE_CHARACTER_LIMIT})); + } - return errors; - }, [translate]); + return errors; + }, + [translate], + ); const submit = useCallback( (values: FormOnyxValues) => { diff --git a/src/pages/workspace/WorkspaceNamePage.tsx b/src/pages/workspace/WorkspaceNamePage.tsx index 173543043a6b..03d4e7107c1c 100644 --- a/src/pages/workspace/WorkspaceNamePage.tsx +++ b/src/pages/workspace/WorkspaceNamePage.tsx @@ -38,20 +38,23 @@ function WorkspaceNamePage({policy}: Props) { [policy], ); - const validate = useCallback((values: FormOnyxValues) => { - const errors: FormInputErrors = {}; - const name = values.name.trim(); + const validate = useCallback( + (values: FormOnyxValues) => { + const errors: FormInputErrors = {}; + const name = values.name.trim(); - if (!ValidationUtils.isRequiredFulfilled(name)) { - errors.name = 'workspace.editor.nameIsRequiredError'; - } else if ([...name].length > CONST.TITLE_CHARACTER_LIMIT) { - // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 - // code units. - ErrorUtils.addErrorMessage(errors, 'name', translate('common.error.characterLimitExceedCounter', {length: [...name].length, limit: CONST.TITLE_CHARACTER_LIMIT})); - } + if (!ValidationUtils.isRequiredFulfilled(name)) { + errors.name = 'workspace.editor.nameIsRequiredError'; + } else if ([...name].length > CONST.TITLE_CHARACTER_LIMIT) { + // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 + // code units. + ErrorUtils.addErrorMessage(errors, 'name', translate('common.error.characterLimitExceedCounter', {length: [...name].length, limit: CONST.TITLE_CHARACTER_LIMIT})); + } - return errors; - }, [translate]); + return errors; + }, + [translate], + ); return ( ) => { - const errors = {}; + const validate = useCallback( + (values: FormOnyxValues) => { + const errors = {}; - if (values.description.length > CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT})); - } + if (values.description.length > CONST.DESCRIPTION_LIMIT) { + ErrorUtils.addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT})); + } - return errors; - }, [translate]); + return errors; + }, + [translate], + ); const submit = useCallback( (values: FormOnyxValues) => { diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 164e51393e21..de0e4556ba5e 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -2084,7 +2084,7 @@ describe('actions/IOU', () => { callback: (allActions) => { Onyx.disconnect(connectionID); const erroredAction = Object.values(allActions ?? {}).find((action) => !isEmptyObject(action?.errors)); - expect(Object.values(erroredAction?.errors ?? {})).toEqual(Localize.translateLocal('iou.error.other')); + expect(Object.values(erroredAction?.errors ?? {})[0]).toEqual(Localize.translateLocal('iou.error.other')); resolve(); }, }); diff --git a/tests/unit/ValidationUtilsTest.ts b/tests/unit/ValidationUtilsTest.ts index fc5dacee2cf2..3bd083bc07e4 100644 --- a/tests/unit/ValidationUtilsTest.ts +++ b/tests/unit/ValidationUtilsTest.ts @@ -1,4 +1,5 @@ import {addDays, format, startOfDay, subYears} from 'date-fns'; +import * as Localize from '@libs/Localize'; import CONST from '@src/CONST'; import * as ValidationUtils from '@src/libs/ValidationUtils'; @@ -186,19 +187,23 @@ describe('ValidationUtils', () => { test('Should return an error message for a date before the minimum age requirement', () => { const invalidDate: string = format(subYears(new Date(), 17), CONST.DATE.FNS_FORMAT_STRING); // Date of birth 17 years ago const error = ValidationUtils.getAgeRequirementError(invalidDate, 18, 150); - expect(error).toEqual(['privatePersonalDetails.error.dateShouldBeBefore', {dateString: format(startOfDay(subYears(new Date(), 18)), CONST.DATE.FNS_FORMAT_STRING)}]); + expect(error).toEqual( + Localize.translateLocal('privatePersonalDetails.error.dateShouldBeBefore', {dateString: format(startOfDay(subYears(new Date(), 18)), CONST.DATE.FNS_FORMAT_STRING)}), + ); }); test('Should return an error message for a date after the maximum age requirement', () => { const invalidDate: string = format(subYears(new Date(), 160), CONST.DATE.FNS_FORMAT_STRING); // Date of birth 160 years ago const error = ValidationUtils.getAgeRequirementError(invalidDate, 18, 150); - expect(error).toEqual(['privatePersonalDetails.error.dateShouldBeAfter', {dateString: format(startOfDay(subYears(new Date(), 150)), CONST.DATE.FNS_FORMAT_STRING)}]); + expect(error).toEqual( + Localize.translateLocal('privatePersonalDetails.error.dateShouldBeAfter', {dateString: format(startOfDay(subYears(new Date(), 150)), CONST.DATE.FNS_FORMAT_STRING)}), + ); }); test('Should return an error message for an invalid date', () => { const invalidDate = '2023-07-32'; // Invalid date const error = ValidationUtils.getAgeRequirementError(invalidDate, 18, 150); - expect(error).toBe('common.error.dateInvalid'); + expect(error).toBe(Localize.translateLocal('common.error.dateInvalid')); }); }); From a7fb5482db646a8e41a4be8254d851172343b183 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 16:04:33 +0700 Subject: [PATCH 012/127] fix lint --- src/components/AddPlaidBankAccount.tsx | 1 + src/components/Form/FormProvider.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/AddPlaidBankAccount.tsx b/src/components/AddPlaidBankAccount.tsx index 366f14ec9780..26444b1a48c4 100644 --- a/src/components/AddPlaidBankAccount.tsx +++ b/src/components/AddPlaidBankAccount.tsx @@ -177,6 +177,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; diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 3d20f910dca0..bd65d3046e18 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -256,6 +256,7 @@ function FormProvider( (Object.keys(errorFields) .sort() .map((key) => errorFields[key]) + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style .at(-1) as string) ?? ''; const inputRef = inputProps.ref; From 6d3a233ef472f09fc30f01ce115d07a5f9309b28 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 16:22:02 +0700 Subject: [PATCH 013/127] fix test --- src/components/Form/FormProvider.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index bd65d3046e18..536af18d85b3 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -256,8 +256,7 @@ function FormProvider( (Object.keys(errorFields) .sort() .map((key) => errorFields[key]) - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style - .at(-1) as string) ?? ''; + .at(-1)) ?? ''; const inputRef = inputProps.ref; From 0c4cabc755963e8ccae139198d6f7e9e830d6248 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 16:31:03 +0700 Subject: [PATCH 014/127] fix prettier --- src/components/Form/FormProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 536af18d85b3..16ecc3805d2d 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -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)) ?? ''; + .at(-1) ?? ''; const inputRef = inputProps.ref; From f29281c39395a8378d87b52a3d040ee9b68b14cd Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 00:05:08 +0700 Subject: [PATCH 015/127] add missing form helper error translation --- src/libs/PolicyDistanceRatesUtils.ts | 5 ++- src/libs/actions/BankAccounts.ts | 3 +- src/pages/EditReportFieldText.tsx | 4 +- .../PersonalInfo/substeps/AddressStep.tsx | 28 +++++++------- .../PersonalInfo/substeps/FullNameStep.tsx | 24 ++++++------ .../PersonalInfo/substeps/PhoneNumberStep.tsx | 17 +++++---- .../substeps/SocialSecurityNumberStep.tsx | 19 +++++----- .../BaseOnboardingPersonalDetails.tsx | 10 ++--- .../OnboardingWork/BaseOnboardingWork.tsx | 2 +- .../BankInfo/substeps/Manual.tsx | 37 ++++++++++--------- .../AddressUBO.tsx | 4 +- .../SocialSecurityNumberUBO.tsx | 2 +- .../BusinessInfo/substeps/AddressBusiness.tsx | 32 ++++++++-------- .../BusinessInfo/substeps/NameBusiness.tsx | 20 +++++----- .../substeps/PhoneNumberBusiness.tsx | 20 +++++----- .../BusinessInfo/substeps/TaxIdBusiness.tsx | 21 +++++------ .../BusinessInfo/substeps/WebsiteBusiness.tsx | 20 +++++----- .../components/BankAccountValidationForm.tsx | 2 +- .../PersonalInfo/substeps/Address.tsx | 28 +++++++------- .../PersonalInfo/substeps/FullName.tsx | 25 ++++++------- .../substeps/SocialSecurityNumber.tsx | 19 +++++----- .../IntroSchoolPrincipalPage.tsx | 14 +++---- src/pages/TeachersUnite/KnowATeacherPage.tsx | 12 +++--- src/pages/iou/HoldReasonPage.tsx | 6 +-- .../request/step/IOURequestStepMerchant.tsx | 4 +- .../request/step/IOURequestStepWaypoint.tsx | 4 +- .../ExitSurvey/ExitSurveyReasonPage.tsx | 2 +- .../ExitSurvey/ExitSurveyResponsePage.tsx | 2 +- .../Profile/Contacts/NewContactMethodPage.tsx | 6 +-- .../settings/Profile/DisplayNamePage.tsx | 8 ++-- .../Profile/PersonalDetails/LegalNamePage.tsx | 10 ++--- src/pages/settings/Report/RoomNamePage.tsx | 6 +-- .../settings/Security/CloseAccountPage.tsx | 2 +- .../Wallet/Card/GetPhysicalCardName.tsx | 8 ++-- .../Wallet/Card/GetPhysicalCardPhone.tsx | 4 +- src/pages/tasks/NewTaskDetailsPage.tsx | 2 +- src/pages/tasks/NewTaskTitlePage.tsx | 2 +- src/pages/tasks/TaskTitlePage.tsx | 2 +- .../workspace/WorkspaceInviteMessagePage.tsx | 2 +- src/pages/workspace/WorkspaceNamePage.tsx | 2 +- .../workspace/categories/CategoryForm.tsx | 6 +-- .../workspace/categories/EditCategoryPage.tsx | 6 +-- .../members/WorkspaceOwnerPaymentCardForm.tsx | 12 +++--- src/pages/workspace/tags/EditTagPage.tsx | 6 +-- .../workspace/tags/WorkspaceCreateTagPage.tsx | 4 +- .../workspace/tags/WorkspaceEditTagsPage.tsx | 16 ++++---- .../WorkspaceTaxesSettingsCustomTaxName.tsx | 19 ++++++---- 47 files changed, 259 insertions(+), 250 deletions(-) diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts index db739cc3b8c7..11ee74bab232 100644 --- a/src/libs/PolicyDistanceRatesUtils.ts +++ b/src/libs/PolicyDistanceRatesUtils.ts @@ -4,6 +4,7 @@ import type ONYXKEYS from '@src/ONYXKEYS'; import type {Rate} from '@src/types/onyx/Policy'; import * as CurrencyUtils from './CurrencyUtils'; import getPermittedDecimalSeparator from './getPermittedDecimalSeparator'; +import * as Localize from './Localize'; import * as MoneyRequestUtils from './MoneyRequestUtils'; import * as NumberUtils from './NumberUtils'; @@ -17,9 +18,9 @@ function validateRateValue(values: FormOnyxValues, currency: stri // Allow one more decimal place for accuracy const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{0,${CurrencyUtils.getCurrencyDecimals(currency) + 1}})?$`, 'i'); if (!rateValueRegex.test(parsedRate) || parsedRate === '') { - errors.rate = 'workspace.reimburse.invalidRateError'; + errors.rate = Localize.translateLocal('workspace.reimburse.invalidRateError'); } else if (NumberUtils.parseFloatAnyLocale(parsedRate) <= 0) { - errors.rate = 'workspace.reimburse.lowRateError'; + errors.rate = Localize.translateLocal('workspace.reimburse.lowRateError'); } return errors; } diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 89d0296994e3..3912572418c2 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -14,6 +14,7 @@ import type { } from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; +import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -532,7 +533,7 @@ function validatePlaidSelection(values: FormOnyxValues): Form const errorFields: FormInputErrors = {}; if (!values.selectedPlaidAccountID) { - errorFields.selectedPlaidAccountID = 'bankAccount.error.youNeedToSelectAnOption'; + errorFields.selectedPlaidAccountID = Localize.translateLocal('bankAccount.error.youNeedToSelectAnOption'); } return errorFields; diff --git a/src/pages/EditReportFieldText.tsx b/src/pages/EditReportFieldText.tsx index 0649d59cd2ee..d619eb52b695 100644 --- a/src/pages/EditReportFieldText.tsx +++ b/src/pages/EditReportFieldText.tsx @@ -36,11 +36,11 @@ function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, isRequired, f (values: FormOnyxValues) => { const errors: FormInputErrors = {}; if (isRequired && values[fieldKey].trim() === '') { - errors[fieldKey] = 'common.error.fieldRequired'; + errors[fieldKey] = translate('common.error.fieldRequired'); } return errors; }, - [fieldKey, isRequired], + [fieldKey, isRequired, translate], ); return ( diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx index ad490fa2d7c9..22347f1b3bff 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx @@ -25,20 +25,6 @@ const INPUT_KEYS = { const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.STREET, PERSONAL_INFO_STEP_KEY.CITY, PERSONAL_INFO_STEP_KEY.STATE, PERSONAL_INFO_STEP_KEY.ZIP_CODE]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { - errors.addressStreet = 'bankAccount.error.addressStreet'; - } - - if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { - errors.addressZipCode = 'bankAccount.error.zipCode'; - } - - return errors; -}; - function AddressStep({onNext, isEditing}: SubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -52,6 +38,20 @@ function AddressStep({onNext, isEditing}: SubStepProps) { zipCode: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.ZIP_CODE] ?? '', }; + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { + errors.addressStreet = translate('bankAccount.error.addressStreet'); + } + + if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { + errors.addressZipCode = translate('bankAccount.error.zipCode'); + } + + return errors; + }; + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx index f63d5ef84879..89240720f533 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx @@ -19,18 +19,6 @@ import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.LAST_NAME]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.legalFirstName && !ValidationUtils.isValidLegalName(values.legalFirstName)) { - errors.legalFirstName = 'bankAccount.error.firstName'; - } - - if (values.legalLastName && !ValidationUtils.isValidLegalName(values.legalLastName)) { - errors.legalLastName = 'bankAccount.error.lastName'; - } - return errors; -}; - function FullNameStep({onNext, isEditing}: SubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -42,6 +30,18 @@ function FullNameStep({onNext, isEditing}: SubStepProps) { lastName: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.LAST_NAME] ?? '', }; + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + if (values.legalFirstName && !ValidationUtils.isValidLegalName(values.legalFirstName)) { + errors.legalFirstName = translate('bankAccount.error.firstName'); + } + + if (values.legalLastName && !ValidationUtils.isValidLegalName(values.legalLastName)) { + errors.legalLastName = translate('bankAccount.error.lastName'); + } + return errors; + }; + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx index 86b6a40948fc..a5326504a1c6 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx @@ -19,14 +19,6 @@ import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.PHONE_NUMBER]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.phoneNumber && !ValidationUtils.isValidUSPhone(values.phoneNumber, true)) { - errors.phoneNumber = 'bankAccount.error.phoneNumber'; - } - return errors; -}; function PhoneNumberStep({onNext, isEditing}: SubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -35,6 +27,15 @@ function PhoneNumberStep({onNext, isEditing}: SubStepProps) { const defaultPhoneNumber = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.PHONE_NUMBER] ?? ''; + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.phoneNumber && !ValidationUtils.isValidUSPhone(values.phoneNumber, true)) { + errors.phoneNumber = translate('bankAccount.error.phoneNumber'); + } + return errors; + }; + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx index 4c366c5a1873..cb3f2fa263f7 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx @@ -19,19 +19,20 @@ import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.SSN_LAST_4]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.ssn && !ValidationUtils.isValidSSNLastFour(values.ssn)) { - errors.ssn = 'bankAccount.error.ssnLast4'; - } - - return errors; -}; function SocialSecurityNumberStep({onNext, isEditing}: SubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.ssn && !ValidationUtils.isValidSSNLastFour(values.ssn)) { + errors.ssn = translate('bankAccount.error.ssnLast4'); + } + + return errors; + }; + const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); const defaultSsnLast4 = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.SSN_LAST_4] ?? ''; diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index a7a78aa9048c..7a5fa8fe013a 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -91,25 +91,25 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat // First we validate the first name field if (values.firstName.replace(CONST.REGEX.ANY_SPACE, '').length === 0) { - ErrorUtils.addErrorMessage(errors, 'firstName', 'onboarding.error.requiredFirstName'); + ErrorUtils.addErrorMessage(errors, 'firstName', translate('onboarding.error.requiredFirstName')); } if (!ValidationUtils.isValidDisplayName(values.firstName)) { - ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.hasInvalidCharacter'); + ErrorUtils.addErrorMessage(errors, 'firstName', translate('personalDetails.error.hasInvalidCharacter')); } else if (values.firstName.length > CONST.DISPLAY_NAME.MAX_LENGTH) { ErrorUtils.addErrorMessage(errors, 'firstName', translate('common.error.characterLimitExceedCounter', {length: values.firstName.length, limit: CONST.DISPLAY_NAME.MAX_LENGTH})); } if (ValidationUtils.doesContainReservedWord(values.firstName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.containsReservedWord'); + ErrorUtils.addErrorMessage(errors, 'firstName', translate('personalDetails.error.containsReservedWord')); } // Then we validate the last name field if (!ValidationUtils.isValidDisplayName(values.lastName)) { - ErrorUtils.addErrorMessage(errors, 'lastName', 'personalDetails.error.hasInvalidCharacter'); + ErrorUtils.addErrorMessage(errors, 'lastName', translate('personalDetails.error.hasInvalidCharacter')); } else if (values.lastName.length > CONST.DISPLAY_NAME.MAX_LENGTH) { ErrorUtils.addErrorMessage(errors, 'lastName', translate('common.error.characterLimitExceedCounter', {length: values.lastName.length, limit: CONST.DISPLAY_NAME.MAX_LENGTH})); } if (ValidationUtils.doesContainReservedWord(values.lastName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, 'lastName', 'personalDetails.error.containsReservedWord'); + ErrorUtils.addErrorMessage(errors, 'lastName', translate('personalDetails.error.containsReservedWord')); } return errors; diff --git a/src/pages/OnboardingWork/BaseOnboardingWork.tsx b/src/pages/OnboardingWork/BaseOnboardingWork.tsx index cdaa264682d1..9b8824300d30 100644 --- a/src/pages/OnboardingWork/BaseOnboardingWork.tsx +++ b/src/pages/OnboardingWork/BaseOnboardingWork.tsx @@ -59,7 +59,7 @@ function BaseOnboardingWork({shouldUseNativeStyles, onboardingPurposeSelected, o const work = values.work.trim(); if (!ValidationUtils.isRequiredFulfilled(work)) { - errors.work = 'workspace.editor.nameIsRequiredError'; + errors.work = translate('workspace.editor.nameIsRequiredError'); } else if ([...work].length > CONST.TITLE_CHARACTER_LIMIT) { // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 // code units. diff --git a/src/pages/ReimbursementAccount/BankInfo/substeps/Manual.tsx b/src/pages/ReimbursementAccount/BankInfo/substeps/Manual.tsx index 3f58d7a2073e..b85ca99b2935 100644 --- a/src/pages/ReimbursementAccount/BankInfo/substeps/Manual.tsx +++ b/src/pages/ReimbursementAccount/BankInfo/substeps/Manual.tsx @@ -37,25 +37,28 @@ function Manual({reimbursementAccount, onNext}: ManualProps) { [BANK_INFO_STEP_KEYS.ACCOUNT_NUMBER]: reimbursementAccount?.achData?.[BANK_INFO_STEP_KEYS.ACCOUNT_NUMBER] ?? '', }; - const validate = useCallback((values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - const routingNumber = values.routingNumber?.trim(); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const routingNumber = values.routingNumber?.trim(); - if ( - values.accountNumber && - !CONST.BANK_ACCOUNT.REGEX.US_ACCOUNT_NUMBER.test(values.accountNumber.trim()) && - !CONST.BANK_ACCOUNT.REGEX.MASKED_US_ACCOUNT_NUMBER.test(values.accountNumber.trim()) - ) { - errors.accountNumber = 'bankAccount.error.accountNumber'; - } else if (values.accountNumber && values.accountNumber === routingNumber) { - errors.accountNumber = 'bankAccount.error.routingAndAccountNumberCannotBeSame'; - } - if (routingNumber && (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(routingNumber) || !ValidationUtils.isValidRoutingNumber(routingNumber))) { - errors.routingNumber = 'bankAccount.error.routingNumber'; - } + if ( + values.accountNumber && + !CONST.BANK_ACCOUNT.REGEX.US_ACCOUNT_NUMBER.test(values.accountNumber.trim()) && + !CONST.BANK_ACCOUNT.REGEX.MASKED_US_ACCOUNT_NUMBER.test(values.accountNumber.trim()) + ) { + errors.accountNumber = translate('bankAccount.error.accountNumber'); + } else if (values.accountNumber && values.accountNumber === routingNumber) { + errors.accountNumber = translate('bankAccount.error.routingAndAccountNumberCannotBeSame'); + } + if (routingNumber && (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(routingNumber) || !ValidationUtils.isValidRoutingNumber(routingNumber))) { + errors.routingNumber = translate('bankAccount.error.routingNumber'); + } - return errors; - }, []); + return errors; + }, + [translate], + ); const shouldDisableInputs = !!(reimbursementAccount?.achData?.bankAccountID ?? ''); diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx index c49f26abf80c..284a27108a48 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx @@ -47,11 +47,11 @@ function AddressUBO({reimbursementAccountDraft, onNext, isEditing, beneficialOwn const errors = ValidationUtils.getFieldRequiredErrors(values, stepFields); if (values[inputKeys.street] && !ValidationUtils.isValidAddress(values[inputKeys.street])) { - errors[inputKeys.street] = 'bankAccount.error.addressStreet'; + errors[inputKeys.street] = translate('bankAccount.error.addressStreet'); } if (values[inputKeys.zipCode] && !ValidationUtils.isValidZipCode(values[inputKeys.zipCode])) { - errors[inputKeys.zipCode] = 'bankAccount.error.zipCode'; + errors[inputKeys.zipCode] = translate('bankAccount.error.zipCode'); } return errors; diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx index 7c0c5ac0c057..d7814816cdb9 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx @@ -36,7 +36,7 @@ function SocialSecurityNumberUBO({reimbursementAccountDraft, onNext, isEditing, const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, stepFields); if (values[ssnLast4InputID] && !ValidationUtils.isValidSSNLastFour(values[ssnLast4InputID])) { - errors[ssnLast4InputID] = 'bankAccount.error.ssnLast4'; + errors[ssnLast4InputID] = translate('bankAccount.error.ssnLast4'); } return errors; }; diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx index cca9e5619d59..8454a82969e7 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx @@ -32,27 +32,27 @@ const INPUT_KEYS = { const STEP_FIELDS = [COMPANY_BUSINESS_INFO_KEY.STREET, COMPANY_BUSINESS_INFO_KEY.CITY, COMPANY_BUSINESS_INFO_KEY.STATE, COMPANY_BUSINESS_INFO_KEY.ZIP_CODE]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); +function AddressBusiness({reimbursementAccount, onNext, isEditing}: AddressBusinessProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); - if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { - errors.addressStreet = 'bankAccount.error.addressStreet'; - } + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.addressCity && !ValidationUtils.isValidAddress(values.addressCity)) { - errors.addressCity = 'bankAccount.error.addressCity'; - } + if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { + errors.addressStreet = translate('bankAccount.error.addressStreet'); + } - if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { - errors.addressZipCode = 'bankAccount.error.zipCode'; - } + if (values.addressCity && !ValidationUtils.isValidAddress(values.addressCity)) { + errors.addressCity = translate('bankAccount.error.addressCity'); + } - return errors; -}; + if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { + errors.addressZipCode = translate('bankAccount.error.zipCode'); + } -function AddressBusiness({reimbursementAccount, onNext, isEditing}: AddressBusinessProps) { - const {translate} = useLocalize(); - const styles = useThemeStyles(); + return errors; + }; const defaultValues = { street: reimbursementAccount?.achData?.addressStreet ?? '', diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/NameBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/NameBusiness.tsx index 377fbf9782ae..712f42578f36 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/NameBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/NameBusiness.tsx @@ -26,16 +26,6 @@ type NameBusinessProps = NameBusinessOnyxProps & SubStepProps; const COMPANY_NAME_KEY = INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_NAME; const STEP_FIELDS = [COMPANY_NAME_KEY]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.companyName && !ValidationUtils.isValidCompanyName(values.companyName)) { - errors.companyName = 'bankAccount.error.companyName'; - } - - return errors; -}; - function NameBusiness({reimbursementAccount, onNext, isEditing}: NameBusinessProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -45,6 +35,16 @@ function NameBusiness({reimbursementAccount, onNext, isEditing}: NameBusinessPro const shouldDisableCompanyName = !!(bankAccountID && defaultCompanyName && reimbursementAccount?.achData?.state !== 'SETUP'); + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.companyName && !ValidationUtils.isValidCompanyName(values.companyName)) { + errors.companyName = translate('bankAccount.error.companyName'); + } + + return errors; + }; + const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx index 018922610080..b22424d194f9 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx @@ -26,21 +26,21 @@ type PhoneNumberBusinessProps = PhoneNumberBusinessOnyxProps & SubStepProps; const COMPANY_PHONE_NUMBER_KEY = INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_PHONE; const STEP_FIELDS = [COMPANY_PHONE_NUMBER_KEY]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true)) { - errors.companyPhone = 'bankAccount.error.phoneNumber'; - } - - return errors; -}; - function PhoneNumberBusiness({reimbursementAccount, onNext, isEditing}: PhoneNumberBusinessProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const defaultCompanyPhoneNumber = reimbursementAccount?.achData?.companyPhone ?? ''; + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true)) { + errors.companyPhone = translate('bankAccount.error.phoneNumber'); + } + + return errors; + }; + const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/TaxIdBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/TaxIdBusiness.tsx index cabf0fc01576..6fba24b901e3 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/TaxIdBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/TaxIdBusiness.tsx @@ -25,17 +25,6 @@ type TaxIdBusinessProps = TaxIdBusinessOnyxProps & SubStepProps; const COMPANY_TAX_ID_KEY = INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_TAX_ID; const STEP_FIELDS = [COMPANY_TAX_ID_KEY]; - -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.companyTaxID && !ValidationUtils.isValidTaxID(values.companyTaxID)) { - errors.companyTaxID = 'bankAccount.error.taxID'; - } - - return errors; -}; - function TaxIdBusiness({reimbursementAccount, onNext, isEditing}: TaxIdBusinessProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -43,6 +32,16 @@ function TaxIdBusiness({reimbursementAccount, onNext, isEditing}: TaxIdBusinessP const bankAccountID = reimbursementAccount?.achData?.bankAccountID ?? 0; const shouldDisableCompanyTaxID = !!(bankAccountID && defaultCompanyTaxId && reimbursementAccount?.achData?.state !== 'SETUP'); + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.companyTaxID && !ValidationUtils.isValidTaxID(values.companyTaxID)) { + errors.companyTaxID = translate('bankAccount.error.taxID'); + } + + return errors; + }; + const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/WebsiteBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/WebsiteBusiness.tsx index e06c4d9d575e..7115e1a82158 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/WebsiteBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/WebsiteBusiness.tsx @@ -34,16 +34,6 @@ type WebsiteBusinessProps = WebsiteBusinessOnyxProps & SubStepProps; const COMPANY_WEBSITE_KEY = INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_WEBSITE; const STEP_FIELDS = [COMPANY_WEBSITE_KEY]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.website && !ValidationUtils.isValidWebsite(values.website)) { - errors.website = 'bankAccount.error.website'; - } - - return errors; -}; - function WebsiteBusiness({reimbursementAccount, user, session, onNext, isEditing}: WebsiteBusinessProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -51,6 +41,16 @@ function WebsiteBusiness({reimbursementAccount, user, session, onNext, isEditing const defaultWebsiteExample = useMemo(() => getDefaultCompanyWebsite(session, user), [session, user]); const defaultCompanyWebsite = reimbursementAccount?.achData?.website ?? defaultWebsiteExample; + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.website && !ValidationUtils.isValidWebsite(values.website)) { + errors.website = translate('bankAccount.error.website'); + } + + return errors; + }; + const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx b/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx index a83e18d119c9..9bb4b4a56bfe 100644 --- a/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx +++ b/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx @@ -73,7 +73,7 @@ function BankAccountValidationForm({requiresTwoFactorAuth, reimbursementAccount, if (ValidationUtils.isRequiredFulfilled(filteredValue.toString())) { return; } - errors[key as keyof AmountValues] = 'common.error.invalidAmount'; + errors[key as keyof AmountValues] = translate('common.error.invalidAmount'); }); return errors; diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx index a0ec8220ce3e..e75978b77298 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx @@ -34,20 +34,6 @@ const INPUT_KEYS = { const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.STREET, PERSONAL_INFO_STEP_KEY.CITY, PERSONAL_INFO_STEP_KEY.STATE, PERSONAL_INFO_STEP_KEY.ZIP_CODE]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.requestorAddressStreet && !ValidationUtils.isValidAddress(values.requestorAddressStreet)) { - errors.requestorAddressStreet = 'bankAccount.error.addressStreet'; - } - - if (values.requestorAddressZipCode && !ValidationUtils.isValidZipCode(values.requestorAddressZipCode)) { - errors.requestorAddressZipCode = 'bankAccount.error.zipCode'; - } - - return errors; -}; - function Address({reimbursementAccount, onNext, isEditing}: AddressProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -59,6 +45,20 @@ function Address({reimbursementAccount, onNext, isEditing}: AddressProps) { zipCode: reimbursementAccount?.achData?.[PERSONAL_INFO_STEP_KEY.ZIP_CODE] ?? '', }; + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.requestorAddressStreet && !ValidationUtils.isValidAddress(values.requestorAddressStreet)) { + errors.requestorAddressStreet = translate('bankAccount.error.addressStreet'); + } + + if (values.requestorAddressZipCode && !ValidationUtils.isValidZipCode(values.requestorAddressZipCode)) { + errors.requestorAddressZipCode = translate('bankAccount.error.zipCode'); + } + + return errors; + }; + const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx index 1d225c1c32f2..79da5ea4c707 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx @@ -27,19 +27,6 @@ type FullNameProps = FullNameOnyxProps & SubStepProps; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.LAST_NAME]; - -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.firstName && !ValidationUtils.isValidLegalName(values.firstName)) { - errors.firstName = 'bankAccount.error.firstName'; - } - - if (values.lastName && !ValidationUtils.isValidLegalName(values.lastName)) { - errors.lastName = 'bankAccount.error.lastName'; - } - return errors; -}; - function FullName({reimbursementAccount, onNext, isEditing}: FullNameProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -49,6 +36,18 @@ function FullName({reimbursementAccount, onNext, isEditing}: FullNameProps) { lastName: reimbursementAccount?.achData?.[PERSONAL_INFO_STEP_KEY.LAST_NAME] ?? '', }; + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + if (values.firstName && !ValidationUtils.isValidLegalName(values.firstName)) { + errors.firstName = translate('bankAccount.error.firstName'); + } + + if (values.lastName && !ValidationUtils.isValidLegalName(values.lastName)) { + errors.lastName = translate('bankAccount.error.lastName'); + } + return errors; + }; + const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx index e647fd768fb1..d33a0ac6243a 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx @@ -28,21 +28,22 @@ type SocialSecurityNumberProps = SocialSecurityNumberOnyxProps & SubStepProps; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.SSN_LAST_4]; -const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.ssnLast4 && !ValidationUtils.isValidSSNLastFour(values.ssnLast4)) { - errors.ssnLast4 = 'bankAccount.error.ssnLast4'; - } - - return errors; -}; function SocialSecurityNumber({reimbursementAccount, onNext, isEditing}: SocialSecurityNumberProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const defaultSsnLast4 = reimbursementAccount?.achData?.[PERSONAL_INFO_STEP_KEY.SSN_LAST_4] ?? ''; + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + + if (values.ssnLast4 && !ValidationUtils.isValidSSNLastFour(values.ssnLast4)) { + errors.ssnLast4 = translate('bankAccount.error.ssnLast4'); + } + + return errors; + }; + const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx index 6e8c3b340f4e..00bc9a1e73c9 100644 --- a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx +++ b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx @@ -50,27 +50,27 @@ function IntroSchoolPrincipalPage(props: IntroSchoolPrincipalPageProps) { const errors: FormInputErrors = {}; if (!values.firstName || !ValidationUtils.isValidPersonName(values.firstName)) { - ErrorUtils.addErrorMessage(errors, 'firstName', 'bankAccount.error.firstName'); + ErrorUtils.addErrorMessage(errors, 'firstName', translate('bankAccount.error.firstName')); } if (!values.lastName || !ValidationUtils.isValidPersonName(values.lastName)) { - ErrorUtils.addErrorMessage(errors, 'lastName', 'bankAccount.error.lastName'); + ErrorUtils.addErrorMessage(errors, 'lastName', translate('bankAccount.error.lastName')); } if (!values.partnerUserID) { - ErrorUtils.addErrorMessage(errors, 'partnerUserID', 'teachersUnitePage.error.enterEmail'); + ErrorUtils.addErrorMessage(errors, 'partnerUserID', translate('teachersUnitePage.error.enterEmail')); } if (values.partnerUserID && props.loginList?.[values.partnerUserID.toLowerCase()]) { - ErrorUtils.addErrorMessage(errors, 'partnerUserID', 'teachersUnitePage.error.tryDifferentEmail'); + ErrorUtils.addErrorMessage(errors, 'partnerUserID', translate('teachersUnitePage.error.tryDifferentEmail')); } if (values.partnerUserID && !Str.isValidEmail(values.partnerUserID)) { - ErrorUtils.addErrorMessage(errors, 'partnerUserID', 'teachersUnitePage.error.enterValidEmail'); + ErrorUtils.addErrorMessage(errors, 'partnerUserID', translate('teachersUnitePage.error.enterValidEmail')); } if (values.partnerUserID && LoginUtils.isEmailPublicDomain(values.partnerUserID)) { - ErrorUtils.addErrorMessage(errors, 'partnerUserID', 'teachersUnitePage.error.tryDifferentEmail'); + ErrorUtils.addErrorMessage(errors, 'partnerUserID', translate('teachersUnitePage.error.tryDifferentEmail')); } return errors; }, - [props.loginList], + [props.loginList, translate], ); return ( diff --git a/src/pages/TeachersUnite/KnowATeacherPage.tsx b/src/pages/TeachersUnite/KnowATeacherPage.tsx index cb58009c40f0..54de95f81828 100644 --- a/src/pages/TeachersUnite/KnowATeacherPage.tsx +++ b/src/pages/TeachersUnite/KnowATeacherPage.tsx @@ -59,24 +59,24 @@ function KnowATeacherPage(props: KnowATeacherPageProps) { const validateIfNumber = LoginUtils.validateNumber(phoneLogin); if (!values.firstName || !ValidationUtils.isValidPersonName(values.firstName)) { - ErrorUtils.addErrorMessage(errors, 'firstName', 'bankAccount.error.firstName'); + ErrorUtils.addErrorMessage(errors, 'firstName', translate('bankAccount.error.firstName')); } if (!values.lastName || !ValidationUtils.isValidPersonName(values.lastName)) { - ErrorUtils.addErrorMessage(errors, 'lastName', 'bankAccount.error.lastName'); + ErrorUtils.addErrorMessage(errors, 'lastName', translate('bankAccount.error.lastName')); } if (!values.partnerUserID) { - ErrorUtils.addErrorMessage(errors, 'partnerUserID', 'teachersUnitePage.error.enterPhoneEmail'); + ErrorUtils.addErrorMessage(errors, 'partnerUserID', translate('teachersUnitePage.error.enterPhoneEmail')); } if (values.partnerUserID && props.loginList?.[validateIfNumber || values.partnerUserID.toLowerCase()]) { - ErrorUtils.addErrorMessage(errors, 'partnerUserID', 'teachersUnitePage.error.tryDifferentEmail'); + ErrorUtils.addErrorMessage(errors, 'partnerUserID', translate('teachersUnitePage.error.tryDifferentEmail')); } if (values.partnerUserID && !(validateIfNumber || Str.isValidEmail(values.partnerUserID))) { - ErrorUtils.addErrorMessage(errors, 'partnerUserID', 'contacts.genericFailureMessages.invalidContactMethod'); + ErrorUtils.addErrorMessage(errors, 'partnerUserID', translate('contacts.genericFailureMessages.invalidContactMethod')); } return errors; }, - [props.loginList], + [props.loginList, translate], ); return ( diff --git a/src/pages/iou/HoldReasonPage.tsx b/src/pages/iou/HoldReasonPage.tsx index 18b290a81ea4..a17ed6df11d3 100644 --- a/src/pages/iou/HoldReasonPage.tsx +++ b/src/pages/iou/HoldReasonPage.tsx @@ -73,20 +73,20 @@ function HoldReasonPage({route}: HoldReasonPageProps) { const errors: FormInputErrors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.COMMENT]); if (!values.comment) { - errors.comment = 'common.error.fieldRequired'; + errors.comment = translate('common.error.fieldRequired'); } // We have extra isWorkspaceRequest condition since, for 1:1 requests, canEditMoneyRequest will rightly return false // as we do not allow requestee to edit fields like description and amount. // But, we still want the requestee to be able to put the request on hold if (!ReportUtils.canEditMoneyRequest(parentReportAction) && isWorkspaceRequest) { const formErrors = {}; - ErrorUtils.addErrorMessage(formErrors, 'reportModified', 'common.error.requestModified'); + ErrorUtils.addErrorMessage(formErrors, 'reportModified', translate('common.error.requestModified')); FormActions.setErrors(ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM, formErrors); } return errors; }, - [parentReportAction, isWorkspaceRequest], + [parentReportAction, isWorkspaceRequest, translate], ); useEffect(() => { diff --git a/src/pages/iou/request/step/IOURequestStepMerchant.tsx b/src/pages/iou/request/step/IOURequestStepMerchant.tsx index fc7d39b49089..bd5ec0eed4c3 100644 --- a/src/pages/iou/request/step/IOURequestStepMerchant.tsx +++ b/src/pages/iou/request/step/IOURequestStepMerchant.tsx @@ -73,12 +73,12 @@ function IOURequestStepMerchant({ const errors: FormInputErrors = {}; if (isMerchantRequired && !value.moneyRequestMerchant) { - errors.moneyRequestMerchant = 'common.error.fieldRequired'; + errors.moneyRequestMerchant = translate('common.error.fieldRequired'); } return errors; }, - [isMerchantRequired], + [isMerchantRequired, translate], ); const updateMerchant = (value: FormOnyxValues) => { diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx index 6c0eae98fb85..158cc8ceca85 100644 --- a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx +++ b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx @@ -90,13 +90,13 @@ function IOURequestStepWaypoint({ const errors = {}; const waypointValue = values[`waypoint${pageIndex}`] ?? ''; if (isOffline && waypointValue !== '' && !ValidationUtils.isValidAddress(waypointValue)) { - ErrorUtils.addErrorMessage(errors, `waypoint${pageIndex}`, 'bankAccount.error.address'); + ErrorUtils.addErrorMessage(errors, `waypoint${pageIndex}`, translate('bankAccount.error.address')); } // If the user is online, and they are trying to save a value without using the autocomplete, show an error message instructing them to use a selected address instead. // That enables us to save the address with coordinates when it is selected if (!isOffline && waypointValue !== '' && waypointAddress !== waypointValue) { - ErrorUtils.addErrorMessage(errors, `waypoint${pageIndex}`, 'distance.error.selectSuggestedAddress'); + ErrorUtils.addErrorMessage(errors, `waypoint${pageIndex}`, translate('distance.error.selectSuggestedAddress')); } return errors; diff --git a/src/pages/settings/ExitSurvey/ExitSurveyReasonPage.tsx b/src/pages/settings/ExitSurvey/ExitSurveyReasonPage.tsx index dbaf330803c1..d9f53faa62d0 100644 --- a/src/pages/settings/ExitSurvey/ExitSurveyReasonPage.tsx +++ b/src/pages/settings/ExitSurvey/ExitSurveyReasonPage.tsx @@ -60,7 +60,7 @@ function ExitSurveyReasonPage({draftReason}: ExitSurveyReasonPageOnyxProps) { validate={() => { const errors: Errors = {}; if (!reason) { - errors[INPUT_IDS.REASON] = 'common.error.fieldRequired'; + errors[INPUT_IDS.REASON] = translate('common.error.fieldRequired'); } return errors; }} diff --git a/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx b/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx index 62183b0ebc26..64e8159334d9 100644 --- a/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx +++ b/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx @@ -110,7 +110,7 @@ function ExitSurveyResponsePage({draftResponse, route, navigation}: ExitSurveyRe validate={() => { const errors: Errors = {}; if (!draftResponse?.trim()) { - errors[INPUT_IDS.RESPONSE] = 'common.error.fieldRequired'; + errors[INPUT_IDS.RESPONSE] = translate('common.error.fieldRequired'); } return errors; }} diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx index 20e12f71664e..4366f8ff284d 100644 --- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx +++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx @@ -57,15 +57,15 @@ function NewContactMethodPage({loginList, route}: NewContactMethodPageProps) { const errors = {}; if (!values.phoneOrEmail) { - ErrorUtils.addErrorMessage(errors, 'phoneOrEmail', 'contacts.genericFailureMessages.contactMethodRequired'); + ErrorUtils.addErrorMessage(errors, 'phoneOrEmail', translate('contacts.genericFailureMessages.contactMethodRequired')); } if (!!values.phoneOrEmail && !(validateIfnumber || Str.isValidEmail(values.phoneOrEmail))) { - ErrorUtils.addErrorMessage(errors, 'phoneOrEmail', 'contacts.genericFailureMessages.invalidContactMethod'); + ErrorUtils.addErrorMessage(errors, 'phoneOrEmail', translate('contacts.genericFailureMessages.invalidContactMethod')); } if (!!values.phoneOrEmail && loginList?.[validateIfnumber || values.phoneOrEmail.toLowerCase()]) { - ErrorUtils.addErrorMessage(errors, 'phoneOrEmail', 'contacts.genericFailureMessages.enteredMethodIsAlreadySubmited'); + ErrorUtils.addErrorMessage(errors, 'phoneOrEmail', translate('contacts.genericFailureMessages.enteredMethodIsAlreadySubmited')); } return errors; diff --git a/src/pages/settings/Profile/DisplayNamePage.tsx b/src/pages/settings/Profile/DisplayNamePage.tsx index 830c06c9a254..e338fc16b0ee 100644 --- a/src/pages/settings/Profile/DisplayNamePage.tsx +++ b/src/pages/settings/Profile/DisplayNamePage.tsx @@ -47,22 +47,22 @@ function DisplayNamePage({isLoadingApp = true, currentUserPersonalDetails}: Disp // First we validate the first name field if (!ValidationUtils.isValidDisplayName(values.firstName)) { - ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.hasInvalidCharacter'); + ErrorUtils.addErrorMessage(errors, 'firstName', translate('personalDetails.error.hasInvalidCharacter')); } else if (values.firstName.length > CONST.TITLE_CHARACTER_LIMIT) { ErrorUtils.addErrorMessage(errors, 'firstName', translate('common.error.characterLimitExceedCounter', {length: values.firstName.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } if (ValidationUtils.doesContainReservedWord(values.firstName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.containsReservedWord'); + ErrorUtils.addErrorMessage(errors, 'firstName', translate('personalDetails.error.containsReservedWord')); } // Then we validate the last name field if (!ValidationUtils.isValidDisplayName(values.lastName)) { - ErrorUtils.addErrorMessage(errors, 'lastName', 'personalDetails.error.hasInvalidCharacter'); + ErrorUtils.addErrorMessage(errors, 'lastName', translate('personalDetails.error.hasInvalidCharacter')); } else if (values.lastName.length > CONST.TITLE_CHARACTER_LIMIT) { ErrorUtils.addErrorMessage(errors, 'lastName', translate('common.error.characterLimitExceedCounter', {length: values.lastName.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } if (ValidationUtils.doesContainReservedWord(values.lastName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, 'lastName', 'personalDetails.error.containsReservedWord'); + ErrorUtils.addErrorMessage(errors, 'lastName', translate('personalDetails.error.containsReservedWord')); } return errors; }; diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx index 83f8456f5fd3..c5e725450264 100644 --- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx @@ -46,9 +46,9 @@ function LegalNamePage({privatePersonalDetails, isLoadingApp = true}: LegalNameP if (typeof values.legalFirstName === 'string') { if (!ValidationUtils.isValidLegalName(values.legalFirstName)) { - ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'privatePersonalDetails.error.hasInvalidCharacter'); + ErrorUtils.addErrorMessage(errors, 'legalFirstName', translate('privatePersonalDetails.error.hasInvalidCharacter')); } else if (!values.legalFirstName) { - errors.legalFirstName = 'common.error.fieldRequired'; + errors.legalFirstName = translate('common.error.fieldRequired'); } else if (values.legalFirstName.length > CONST.LEGAL_NAME.MAX_LENGTH) { ErrorUtils.addErrorMessage( errors, @@ -57,13 +57,13 @@ function LegalNamePage({privatePersonalDetails, isLoadingApp = true}: LegalNameP ); } if (ValidationUtils.doesContainReservedWord(values.legalFirstName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, 'legalFirstName', 'personalDetails.error.containsReservedWord'); + ErrorUtils.addErrorMessage(errors, 'legalFirstName', translate('personalDetails.error.containsReservedWord')); } } if (typeof values.legalLastName === 'string') { if (!ValidationUtils.isValidLegalName(values.legalLastName)) { - ErrorUtils.addErrorMessage(errors, 'legalLastName', 'privatePersonalDetails.error.hasInvalidCharacter'); + ErrorUtils.addErrorMessage(errors, 'legalLastName', translate('privatePersonalDetails.error.hasInvalidCharacter')); } else if (!values.legalLastName) { errors.legalLastName = 'common.error.fieldRequired'; } else if (values.legalLastName.length > CONST.LEGAL_NAME.MAX_LENGTH) { @@ -74,7 +74,7 @@ function LegalNamePage({privatePersonalDetails, isLoadingApp = true}: LegalNameP ); } if (ValidationUtils.doesContainReservedWord(values.legalLastName, CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, 'legalLastName', 'personalDetails.error.containsReservedWord'); + ErrorUtils.addErrorMessage(errors, 'legalLastName', translate('personalDetails.error.containsReservedWord')); } } diff --git a/src/pages/settings/Report/RoomNamePage.tsx b/src/pages/settings/Report/RoomNamePage.tsx index 91451307aed7..67068182c8a5 100644 --- a/src/pages/settings/Report/RoomNamePage.tsx +++ b/src/pages/settings/Report/RoomNamePage.tsx @@ -53,16 +53,16 @@ function RoomNamePage({report, policy, reports}: RoomNamePageProps) { if (!values.roomName || values.roomName === CONST.POLICY.ROOM_PREFIX) { // We error if the user doesn't enter a room name or left blank - ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.pleaseEnterRoomName'); + ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.pleaseEnterRoomName')); } else if (!ValidationUtils.isValidRoomName(values.roomName)) { // We error if the room name has invalid characters - ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomNameInvalidError'); + ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomNameInvalidError')); } else if (ValidationUtils.isReservedRoomName(values.roomName)) { // Certain names are reserved for default rooms and should not be used for policy rooms. ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomNameReservedError', {reservedName: values.roomName})); } else if (ValidationUtils.isExistingRoomName(values.roomName, reports, report?.policyID ?? '')) { // The room name can't be set to one that already exists on the policy - ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomAlreadyExistsError'); + ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomAlreadyExistsError')); } else if (values.roomName.length > CONST.TITLE_CHARACTER_LIMIT) { ErrorUtils.addErrorMessage(errors, 'roomName', translate('common.error.characterLimitExceedCounter', {length: values.roomName.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } diff --git a/src/pages/settings/Security/CloseAccountPage.tsx b/src/pages/settings/Security/CloseAccountPage.tsx index 0fc0f5b7790a..d7a8087edb57 100644 --- a/src/pages/settings/Security/CloseAccountPage.tsx +++ b/src/pages/settings/Security/CloseAccountPage.tsx @@ -72,7 +72,7 @@ function CloseAccountPage({session}: CloseAccountPageProps) { const errors = ValidationUtils.getFieldRequiredErrors(values, ['phoneOrEmail']); if (values.phoneOrEmail && userEmailOrPhone && sanitizePhoneOrEmail(userEmailOrPhone) !== sanitizePhoneOrEmail(values.phoneOrEmail)) { - errors.phoneOrEmail = 'closeAccountPage.enterYourDefaultContactMethod'; + errors.phoneOrEmail = translate('closeAccountPage.enterYourDefaultContactMethod'); } return errors; }; diff --git a/src/pages/settings/Wallet/Card/GetPhysicalCardName.tsx b/src/pages/settings/Wallet/Card/GetPhysicalCardName.tsx index 1667aa8d36d3..deaa05350c7f 100644 --- a/src/pages/settings/Wallet/Card/GetPhysicalCardName.tsx +++ b/src/pages/settings/Wallet/Card/GetPhysicalCardName.tsx @@ -43,15 +43,15 @@ function GetPhysicalCardName({ const errors: OnValidateResult = {}; if (values?.legalFirstName && !ValidationUtils.isValidLegalName(values.legalFirstName)) { - errors.legalFirstName = 'privatePersonalDetails.error.hasInvalidCharacter'; + errors.legalFirstName = translate('privatePersonalDetails.error.hasInvalidCharacter'); } else if (!values?.legalFirstName) { - errors.legalFirstName = 'common.error.fieldRequired'; + errors.legalFirstName = translate('common.error.fieldRequired'); } if (values?.legalLastName && !ValidationUtils.isValidLegalName(values.legalLastName)) { - errors.legalLastName = 'privatePersonalDetails.error.hasInvalidCharacter'; + errors.legalLastName = translate('privatePersonalDetails.error.hasInvalidCharacter'); } else if (!values?.legalLastName) { - errors.legalLastName = 'common.error.fieldRequired'; + errors.legalLastName = translate('common.error.fieldRequired'); } return errors; diff --git a/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx b/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx index f709de4b51c3..56d5a29a3203 100644 --- a/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx +++ b/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx @@ -42,9 +42,9 @@ function GetPhysicalCardPhone({ const errors: OnValidateResult = {}; if (!LoginUtils.validateNumber(phoneNumberToValidate)) { - errors.phoneNumber = 'common.error.phoneNumber'; + errors.phoneNumber = translate('common.error.phoneNumber'); } else if (!phoneNumberToValidate) { - errors.phoneNumber = 'common.error.fieldRequired'; + errors.phoneNumber = translate('common.error.fieldRequired'); } return errors; diff --git a/src/pages/tasks/NewTaskDetailsPage.tsx b/src/pages/tasks/NewTaskDetailsPage.tsx index ae269b1dbc81..3824bebde1e9 100644 --- a/src/pages/tasks/NewTaskDetailsPage.tsx +++ b/src/pages/tasks/NewTaskDetailsPage.tsx @@ -56,7 +56,7 @@ function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) { if (!values.taskTitle) { // We error if the user doesn't enter a task name - ErrorUtils.addErrorMessage(errors, 'taskTitle', 'newTaskPage.pleaseEnterTaskName'); + ErrorUtils.addErrorMessage(errors, 'taskTitle', translate('newTaskPage.pleaseEnterTaskName')); } else if (values.taskTitle.length > CONST.TITLE_CHARACTER_LIMIT) { ErrorUtils.addErrorMessage(errors, 'taskTitle', translate('common.error.characterLimitExceedCounter', {length: values.taskTitle.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } diff --git a/src/pages/tasks/NewTaskTitlePage.tsx b/src/pages/tasks/NewTaskTitlePage.tsx index 0ff6c178d24a..cdc9fc27596a 100644 --- a/src/pages/tasks/NewTaskTitlePage.tsx +++ b/src/pages/tasks/NewTaskTitlePage.tsx @@ -40,7 +40,7 @@ function NewTaskTitlePage({task}: NewTaskTitlePageProps) { if (!values.taskTitle) { // We error if the user doesn't enter a task name - ErrorUtils.addErrorMessage(errors, 'taskTitle', 'newTaskPage.pleaseEnterTaskName'); + ErrorUtils.addErrorMessage(errors, 'taskTitle', translate('newTaskPage.pleaseEnterTaskName')); } else if (values.taskTitle.length > CONST.TITLE_CHARACTER_LIMIT) { ErrorUtils.addErrorMessage(errors, 'taskTitle', translate('common.error.characterLimitExceedCounter', {length: values.taskTitle.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } diff --git a/src/pages/tasks/TaskTitlePage.tsx b/src/pages/tasks/TaskTitlePage.tsx index 70ced7a81072..e99b0b3da67a 100644 --- a/src/pages/tasks/TaskTitlePage.tsx +++ b/src/pages/tasks/TaskTitlePage.tsx @@ -34,7 +34,7 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) const errors: FormInputErrors = {}; if (!title) { - ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, 'newTaskPage.pleaseEnterTaskName'); + ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, translate('newTaskPage.pleaseEnterTaskName')); } else if (title.length > CONST.TITLE_CHARACTER_LIMIT) { ErrorUtils.addErrorMessage(errors, INPUT_IDS.TITLE, translate('common.error.characterLimitExceedCounter', {length: title.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index ffc7299eb1f0..add00da9643a 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -125,7 +125,7 @@ function WorkspaceInviteMessagePage({ const validate = (): FormInputErrors => { const errorFields: FormInputErrors = {}; if (isEmptyObject(invitedEmailsToAccountIDsDraft)) { - errorFields.welcomeMessage = 'workspace.inviteMessage.inviteNoMembersError'; + errorFields.welcomeMessage = translate('workspace.inviteMessage.inviteNoMembersError'); } return errorFields; }; diff --git a/src/pages/workspace/WorkspaceNamePage.tsx b/src/pages/workspace/WorkspaceNamePage.tsx index 03d4e7107c1c..eee99a912d82 100644 --- a/src/pages/workspace/WorkspaceNamePage.tsx +++ b/src/pages/workspace/WorkspaceNamePage.tsx @@ -44,7 +44,7 @@ function WorkspaceNamePage({policy}: Props) { const name = values.name.trim(); if (!ValidationUtils.isRequiredFulfilled(name)) { - errors.name = 'workspace.editor.nameIsRequiredError'; + errors.name = translate('workspace.editor.nameIsRequiredError'); } else if ([...name].length > CONST.TITLE_CHARACTER_LIMIT) { // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 // code units. diff --git a/src/pages/workspace/categories/CategoryForm.tsx b/src/pages/workspace/categories/CategoryForm.tsx index ae1822515fa5..efe457729e36 100644 --- a/src/pages/workspace/categories/CategoryForm.tsx +++ b/src/pages/workspace/categories/CategoryForm.tsx @@ -40,11 +40,11 @@ function CategoryForm({onSubmit, policyCategories, categoryName, validateEdit}: const newCategoryName = values.categoryName.trim(); if (!ValidationUtils.isRequiredFulfilled(newCategoryName)) { - errors.categoryName = 'workspace.categories.categoryRequiredError'; + errors.categoryName = translate('workspace.categories.categoryRequiredError'); } else if (policyCategories?.[newCategoryName]) { - errors.categoryName = 'workspace.categories.existingCategoryError'; + errors.categoryName = translate('workspace.categories.existingCategoryError'); } else if (newCategoryName === CONST.INVALID_CATEGORY_NAME) { - errors.categoryName = 'workspace.categories.invalidCategoryName'; + errors.categoryName = translate('workspace.categories.invalidCategoryName'); } else if ([...newCategoryName].length > CONST.CATEGORY_NAME_LIMIT) { // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 code units. ErrorUtils.addErrorMessage( diff --git a/src/pages/workspace/categories/EditCategoryPage.tsx b/src/pages/workspace/categories/EditCategoryPage.tsx index 7b37375272d2..224233fff318 100644 --- a/src/pages/workspace/categories/EditCategoryPage.tsx +++ b/src/pages/workspace/categories/EditCategoryPage.tsx @@ -37,14 +37,14 @@ function EditCategoryPage({route, policyCategories}: EditCategoryPageProps) { const newCategoryName = values.categoryName.trim(); if (!newCategoryName) { - errors.categoryName = 'workspace.categories.categoryRequiredError'; + errors.categoryName = translate('workspace.categories.categoryRequiredError'); } else if (policyCategories?.[newCategoryName] && currentCategoryName !== newCategoryName) { - errors.categoryName = 'workspace.categories.existingCategoryError'; + errors.categoryName = translate('workspace.categories.existingCategoryError'); } return errors; }, - [policyCategories, currentCategoryName], + [policyCategories, currentCategoryName, translate], ); const editCategory = useCallback( diff --git a/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx b/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx index 1a2f32449c41..8684792be806 100644 --- a/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx +++ b/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx @@ -76,27 +76,27 @@ function WorkspaceOwnerPaymentCardForm({policy}: WorkspaceOwnerPaymentCardFormPr const errors = ValidationUtils.getFieldRequiredErrors(formValues, REQUIRED_FIELDS); if (formValues.nameOnCard && !ValidationUtils.isValidLegalName(formValues.nameOnCard)) { - errors.nameOnCard = 'addDebitCardPage.error.invalidName'; + errors.nameOnCard = translate('addDebitCardPage.error.invalidName'); } if (formValues.cardNumber && !ValidationUtils.isValidDebitCard(formValues.cardNumber.replace(/ /g, ''))) { - errors.cardNumber = 'addDebitCardPage.error.debitCardNumber'; + errors.cardNumber = translate('addDebitCardPage.error.debitCardNumber'); } if (formValues.expirationDate && !ValidationUtils.isValidExpirationDate(formValues.expirationDate)) { - errors.expirationDate = 'addDebitCardPage.error.expirationDate'; + errors.expirationDate = translate('addDebitCardPage.error.expirationDate'); } if (formValues.securityCode && !ValidationUtils.isValidSecurityCode(formValues.securityCode)) { - errors.securityCode = 'addDebitCardPage.error.securityCode'; + errors.securityCode = translate('addDebitCardPage.error.securityCode'); } if (formValues.addressStreet && !ValidationUtils.isValidAddress(formValues.addressStreet)) { - errors.addressStreet = 'addDebitCardPage.error.addressStreet'; + errors.addressStreet = translate('addDebitCardPage.error.addressStreet'); } if (formValues.addressZipCode && !ValidationUtils.isValidZipCode(formValues.addressZipCode)) { - errors.addressZipCode = 'addDebitCardPage.error.addressZipCode'; + errors.addressZipCode = translate('addDebitCardPage.error.addressZipCode'); } return errors; diff --git a/src/pages/workspace/tags/EditTagPage.tsx b/src/pages/workspace/tags/EditTagPage.tsx index a6b3b5dd6657..f2269bbb8c53 100644 --- a/src/pages/workspace/tags/EditTagPage.tsx +++ b/src/pages/workspace/tags/EditTagPage.tsx @@ -43,14 +43,14 @@ function EditTagPage({route, policyTags}: EditTagPageProps) { const tagName = values.tagName.trim(); const {tags} = PolicyUtils.getTagList(policyTags, route.params.orderWeight); if (!ValidationUtils.isRequiredFulfilled(tagName)) { - errors.tagName = 'workspace.tags.tagRequiredError'; + errors.tagName = translate('workspace.tags.tagRequiredError'); } else if (tags?.[tagName] && currentTagName !== tagName) { - errors.tagName = 'workspace.tags.existingTagError'; + errors.tagName = translate('workspace.tags.existingTagError'); } return errors; }, - [route.params.orderWeight, currentTagName, policyTags], + [policyTags, route.params.orderWeight, currentTagName, translate], ); const editTag = useCallback( diff --git a/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx b/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx index 408bf4eb2366..1214507480ae 100644 --- a/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx +++ b/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx @@ -44,9 +44,9 @@ function CreateTagPage({route, policyTags}: CreateTagPageProps) { const {tags} = PolicyUtils.getTagList(policyTags, 0); if (!ValidationUtils.isRequiredFulfilled(tagName)) { - errors.tagName = 'workspace.tags.tagRequiredError'; + errors.tagName = translate('workspace.tags.tagRequiredError'); } else if (tags?.[tagName]) { - errors.tagName = 'workspace.tags.existingTagError'; + errors.tagName = translate('workspace.tags.existingTagError'); } else if ([...tagName].length > CONST.TAG_NAME_LIMIT) { // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 code units. ErrorUtils.addErrorMessage(errors, 'tagName', translate('common.error.characterLimitExceedCounter', {length: [...tagName].length, limit: CONST.TAG_NAME_LIMIT})); diff --git a/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx b/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx index f0b3ada7f2e5..91883cc989b0 100644 --- a/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx @@ -30,20 +30,20 @@ type WorkspaceEditTagsPageOnyxProps = { type WorkspaceEditTagsPageProps = WorkspaceEditTagsPageOnyxProps & StackScreenProps; -const validateTagName = (values: FormOnyxValues) => { - const errors: FormInputErrors = {}; - if (!values[INPUT_IDS.POLICY_TAGS_NAME] && values[INPUT_IDS.POLICY_TAGS_NAME].trim() === '') { - errors[INPUT_IDS.POLICY_TAGS_NAME] = 'common.error.fieldRequired'; - } - return errors; -}; - function WorkspaceEditTagsPage({route, policyTags}: WorkspaceEditTagsPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const taglistName = useMemo(() => PolicyUtils.getTagListName(policyTags, route.params.orderWeight), [policyTags, route.params.orderWeight]); const {inputCallbackRef} = useAutoFocusInput(); + const validateTagName = (values: FormOnyxValues) => { + const errors: FormInputErrors = {}; + if (!values[INPUT_IDS.POLICY_TAGS_NAME] && values[INPUT_IDS.POLICY_TAGS_NAME].trim() === '') { + errors[INPUT_IDS.POLICY_TAGS_NAME] = translate('common.error.fieldRequired'); + } + return errors; + }; + const updateTaglistName = useCallback( (values: FormOnyxValues) => { if (values[INPUT_IDS.POLICY_TAGS_NAME] !== taglistName) { diff --git a/src/pages/workspace/taxes/WorkspaceTaxesSettingsCustomTaxName.tsx b/src/pages/workspace/taxes/WorkspaceTaxesSettingsCustomTaxName.tsx index dbade9a82aa2..f24a388011ab 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesSettingsCustomTaxName.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesSettingsCustomTaxName.tsx @@ -36,16 +36,19 @@ function WorkspaceTaxesSettingsCustomTaxName({ const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); - const validate = useCallback((values: FormOnyxValues) => { - const errors: FormInputErrors = {}; - const customTaxName = values[INPUT_IDS.NAME]; + const validate = useCallback( + (values: FormOnyxValues) => { + const errors: FormInputErrors = {}; + const customTaxName = values[INPUT_IDS.NAME]; - if (!ValidationUtils.isRequiredFulfilled(customTaxName)) { - errors.name = 'workspace.taxes.error.customNameRequired'; - } + if (!ValidationUtils.isRequiredFulfilled(customTaxName)) { + errors.name = translate('workspace.taxes.error.customNameRequired'); + } - return errors; - }, []); + return errors; + }, + [translate], + ); const submit = ({name}: WorkspaceTaxCustomName) => { setPolicyCustomTaxName(policyID, name); From 267cf232b2f9dc94f5555568e7d15ce97c5e04e5 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 15:31:01 +0700 Subject: [PATCH 016/127] fix: Workspace - Wrong members number is displayed --- src/libs/actions/Policy/Policy.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 4d918352ba91..7ffbe8ccb355 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1321,6 +1321,9 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I }, isOptimisticReport: false, pendingChatMembers: null, + participants: { + [accountID]: allPersonalDetails && allPersonalDetails[accountID] ? {} : null, + }, }, }); workspaceMembersChats.onyxSuccessData.push({ From 25cecda20fff07594b6f7ca437284d31ffeae0e8 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 4 Jun 2024 16:55:02 +0200 Subject: [PATCH 017/127] e2e: terminate app only when all network requests were finished --- src/libs/E2E/tests/appStartTimeTest.e2e.ts | 2 + src/libs/E2E/tests/chatOpeningTest.e2e.ts | 11 +++-- src/libs/E2E/tests/linkingTest.e2e.ts | 2 + .../E2E/tests/openChatFinderPageTest.e2e.ts | 11 +++-- src/libs/E2E/tests/reportTypingTest.e2e.ts | 5 ++- src/libs/E2E/utils/NetworkInterceptor.ts | 40 +++++++++++++++++++ tests/e2e/config.ts | 1 - tests/e2e/testRunner.ts | 2 +- 8 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/libs/E2E/tests/appStartTimeTest.e2e.ts b/src/libs/E2E/tests/appStartTimeTest.e2e.ts index 321fc3773d51..456c62b24ac9 100644 --- a/src/libs/E2E/tests/appStartTimeTest.e2e.ts +++ b/src/libs/E2E/tests/appStartTimeTest.e2e.ts @@ -3,6 +3,7 @@ import type {PerformanceEntry} from 'react-native-performance'; import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; +import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import Performance from '@libs/Performance'; const test = () => { @@ -30,6 +31,7 @@ const test = () => { }), ), ) + .then(waitForActiveRequestsToBeEmpty) .then(() => { console.debug('[E2E] Done, exiting…'); E2EClient.submitTestDone(); diff --git a/src/libs/E2E/tests/chatOpeningTest.e2e.ts b/src/libs/E2E/tests/chatOpeningTest.e2e.ts index 8e43c4ece564..c8887cab535d 100644 --- a/src/libs/E2E/tests/chatOpeningTest.e2e.ts +++ b/src/libs/E2E/tests/chatOpeningTest.e2e.ts @@ -5,6 +5,7 @@ import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; +import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; @@ -29,11 +30,13 @@ const test = (config: NativeConfig) => { const [renderChatPromise, renderChatResolve] = getPromiseWithResolve(); const [chatTTIPromise, chatTTIResolve] = getPromiseWithResolve(); - Promise.all([renderChatPromise, chatTTIPromise]).then(() => { - console.debug(`[E2E] Submitting!`); + Promise.all([renderChatPromise, chatTTIPromise]) + .then(waitForActiveRequestsToBeEmpty) + .then(() => { + console.debug(`[E2E] Submitting!`); - E2EClient.submitTestDone(); - }); + E2EClient.submitTestDone(); + }); Performance.subscribeToMeasurements((entry) => { if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { diff --git a/src/libs/E2E/tests/linkingTest.e2e.ts b/src/libs/E2E/tests/linkingTest.e2e.ts index c48ffb5a8057..8a1980a8b28a 100644 --- a/src/libs/E2E/tests/linkingTest.e2e.ts +++ b/src/libs/E2E/tests/linkingTest.e2e.ts @@ -6,6 +6,7 @@ import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; +import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; @@ -51,6 +52,7 @@ const test = (config: NativeConfig) => { name: 'Comment linking', duration: entry.duration, }) + .then(waitForActiveRequestsToBeEmpty) .then(() => { console.debug('[E2E] Test completed successfully, exiting…'); E2EClient.submitTestDone(); diff --git a/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts b/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts index 4ac7995b914f..1837d31f7110 100644 --- a/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts +++ b/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts @@ -3,6 +3,7 @@ import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; +import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; @@ -25,11 +26,13 @@ const test = () => { const [openSearchPagePromise, openSearchPageResolve] = getPromiseWithResolve(); const [loadSearchOptionsPromise, loadSearchOptionsResolve] = getPromiseWithResolve(); - Promise.all([openSearchPagePromise, loadSearchOptionsPromise]).then(() => { - console.debug(`[E2E] Submitting!`); + Promise.all([openSearchPagePromise, loadSearchOptionsPromise]) + .then(waitForActiveRequestsToBeEmpty) + .then(() => { + console.debug(`[E2E] Submitting!`); - E2EClient.submitTestDone(); - }); + E2EClient.submitTestDone(); + }); Performance.subscribeToMeasurements((entry) => { if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.ts b/src/libs/E2E/tests/reportTypingTest.e2e.ts index 817bda941611..fec5daa246fe 100644 --- a/src/libs/E2E/tests/reportTypingTest.e2e.ts +++ b/src/libs/E2E/tests/reportTypingTest.e2e.ts @@ -5,6 +5,7 @@ import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import waitForKeyboard from '@libs/E2E/actions/waitForKeyboard'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; +import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import {getRerenderCount, resetRerenderCount} from '@pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e'; @@ -54,7 +55,9 @@ const test = (config: NativeConfig) => { branch: Config.E2E_BRANCH, name: 'Composer typing rerender count', renderCount: rerenderCount, - }).then(E2EClient.submitTestDone); + }) + .then(waitForActiveRequestsToBeEmpty) + .then(E2EClient.submitTestDone); }, 3000); }) .catch((error) => { diff --git a/src/libs/E2E/utils/NetworkInterceptor.ts b/src/libs/E2E/utils/NetworkInterceptor.ts index 3a4a48f7db53..ad23afeb0c3b 100644 --- a/src/libs/E2E/utils/NetworkInterceptor.ts +++ b/src/libs/E2E/utils/NetworkInterceptor.ts @@ -1,4 +1,5 @@ /* eslint-disable @lwc/lwc/no-async-await */ +import {DeviceEventEmitter} from 'react-native'; import type {NetworkCacheEntry, NetworkCacheMap} from '@libs/E2E/types'; const LOG_TAG = `[E2E][NetworkInterceptor]`; @@ -93,6 +94,33 @@ function hashFetchArgs(args: Parameters) { return `${url}${JSON.stringify(headers)}`; } +let activeRequestsCount = 0; + +const ACTIVE_REQUESTS_QUEUE_IS_EMPTY_EVENT = 'activeRequestsQueueIsEmpty'; + +/** + * Assures that ongoing network requests are empty. **Highly desirable** to call this function before closing the app. + * Otherwise if some requests are persisted - they will be executed on the next app start. And it can lead to a situation + * where we can have `N * M` requests (where `N` is the number of app run per test and `M` is the number of test suites) + * and such big amount of requests can lead to a situation, where first app run (in test suite to cache network requests) + * may be blocked by spinners and lead to unbelievable big time execution, which eventually will be bigger than timeout and + * will lead to a test failure. + */ +function waitForActiveRequestsToBeEmpty(): Promise { + console.debug('Waiting for requests queue to be empty...', activeRequestsCount); + + if (activeRequestsCount === 0) { + return Promise.resolve(); + } + + return new Promise((resolve) => { + const subscription = DeviceEventEmitter.addListener(ACTIVE_REQUESTS_QUEUE_IS_EMPTY_EVENT, () => { + subscription.remove(); + resolve(); + }); + }); +} + /** * Install a network interceptor by overwriting the global fetch function: * - Overwrites fetch globally with a custom implementation @@ -145,6 +173,8 @@ export default function installNetworkInterceptor( console.debug('!!! Missed cache hit for url:', url); } + activeRequestsCount++; + return originalFetch(...args) .then(async (res) => { if (networkCache != null) { @@ -166,6 +196,16 @@ export default function installNetworkInterceptor( .then((res) => { console.debug(LOG_TAG, 'Network cache updated!'); return res; + }) + .finally(() => { + console.debug('Active requests count:', activeRequestsCount); + + activeRequestsCount--; + + if (activeRequestsCount === 0) { + DeviceEventEmitter.emit(ACTIVE_REQUESTS_QUEUE_IS_EMPTY_EVENT); + } }); }; } +export {waitForActiveRequestsToBeEmpty}; diff --git a/tests/e2e/config.ts b/tests/e2e/config.ts index 4120019de692..62f73c454631 100644 --- a/tests/e2e/config.ts +++ b/tests/e2e/config.ts @@ -68,7 +68,6 @@ export default { TESTS_CONFIG: { [TEST_NAMES.AppStartTime]: { name: TEST_NAMES.AppStartTime, - warmupRuns: 1, // ... any additional config you might need }, [TEST_NAMES.OpenChatFinderPage]: { diff --git a/tests/e2e/testRunner.ts b/tests/e2e/testRunner.ts index fcf93d525f8e..fc9cb069acad 100644 --- a/tests/e2e/testRunner.ts +++ b/tests/e2e/testRunner.ts @@ -184,7 +184,7 @@ const runTests = async (): Promise => { // by default we do 2 warmups: // - first warmup to pass a login flow // - second warmup to pass an actual flow and cache network requests - const iterations = test.warmupRuns ?? 2; + const iterations = 2; for (let i = 0; i < iterations; i++) { // Warmup the main app: await runTestIteration(config.MAIN_APP_PACKAGE, `[MAIN] ${warmupText}. Iteration ${i + 1}/${iterations}`); From ba60fea1b49ab0f2eafd8cdb95c5b9de16e1a921 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 5 Jun 2024 01:34:42 +0700 Subject: [PATCH 018/127] clean code --- src/components/AddressForm.tsx | 8 ++--- .../MoneyRequestConfirmationList.tsx | 25 +++++++++------ src/components/PDFView/PDFPasswordForm.tsx | 2 +- src/libs/OptionsListUtils.ts | 2 +- .../PersonalInfo/substeps/AddressStep.tsx | 25 ++++++++------- .../PersonalInfo/substeps/DateOfBirthStep.tsx | 25 ++++++++------- .../PersonalInfo/substeps/FullNameStep.tsx | 25 ++++++++------- .../PersonalInfo/substeps/PhoneNumberStep.tsx | 19 +++++++----- .../substeps/SocialSecurityNumberStep.tsx | 19 +++++++----- .../substeps/ConfirmationBusiness.tsx | 19 +++++++----- .../substeps/IncorporationDateBusiness.tsx | 23 ++++++++------ .../BusinessInfo/substeps/NameBusiness.tsx | 19 +++++++----- .../substeps/PhoneNumberBusiness.tsx | 19 +++++++----- .../BusinessInfo/substeps/TaxIdBusiness.tsx | 19 +++++++----- .../BusinessInfo/substeps/WebsiteBusiness.tsx | 20 ++++++------ .../substeps/ConfirmAgreements.tsx | 31 ++++++++++--------- .../PersonalInfo/substeps/Address.tsx | 25 ++++++++------- .../PersonalInfo/substeps/DateOfBirth.tsx | 25 ++++++++------- .../PersonalInfo/substeps/FullName.tsx | 25 ++++++++------- .../substeps/SocialSecurityNumber.tsx | 19 +++++++----- .../Profile/Contacts/NewContactMethodPage.tsx | 2 +- .../BaseTwoFactorAuthForm.tsx | 8 +++-- .../workspace/tags/WorkspaceEditTagsPage.tsx | 17 +++++----- 23 files changed, 239 insertions(+), 182 deletions(-) diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index 8601c3fa1e9f..006550664f93 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -116,18 +116,18 @@ function AddressForm({ 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]; + errors.zipPostCode = [translate('privatePersonalDetails.error.incorrectZipFormat', countryZipFormat)]; } else { - errors.zipPostCode = 'common.error.fieldRequired'; + errors.zipPostCode = translate('common.error.fieldRequired'); } } } else if (!CONST.GENERIC_ZIP_CODE_REGEX.test(values?.zipPostCode?.trim()?.toUpperCase() ?? '')) { - errors.zipPostCode = 'privatePersonalDetails.error.incorrectZipFormat'; + errors.zipPostCode = translate('privatePersonalDetails.error.incorrectZipFormat'); } return errors; diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 9f01d63cc439..a073b021207d 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -35,6 +35,7 @@ import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import * as IOU from '@userActions/IOU'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; @@ -322,7 +323,7 @@ function MoneyRequestConfirmationList({ const previousTransactionCurrency = usePrevious(transaction?.currency); const isFocused = useIsFocused(); - const [formError, debouncedFormError, setFormError] = useDebouncedState(''); + const [formError, debouncedFormError, setFormError] = useDebouncedState(''); const [didConfirm, setDidConfirm] = useState(false); const [didConfirmSplit, setDidConfirmSplit] = useState(false); @@ -349,8 +350,12 @@ function MoneyRequestConfirmationList({ const isCategoryRequired = !!policy?.requiresCategory; useEffect(() => { + if (shouldDisplayFieldError && hasSmartScanFailed) { + setFormError('iou.receiptScanningFailed'); + return; + } if (shouldDisplayFieldError && didConfirmSplit) { - setFormError(translate('iou.error.genericSmartscanFailureMessage')); + setFormError('iou.error.genericSmartscanFailureMessage'); return; } @@ -435,7 +440,7 @@ function MoneyRequestConfirmationList({ const shares: number[] = Object.values(splitSharesMap).map((splitShare) => splitShare?.amount ?? 0); const sumOfShares = shares?.reduce((prev, current): number => prev + current, 0); if (sumOfShares !== iouAmount) { - setFormError(translate('iou.error.invalidSplit')); + setFormError('iou.error.invalidSplit'); return; } @@ -445,7 +450,7 @@ function MoneyRequestConfirmationList({ // A split must have at least two participants with amounts bigger than 0 if (participantsWithAmount.length === 1) { - setFormError(translate('iou.error.invalidSplitParticipants')); + setFormError('iou.error.invalidSplitParticipants'); return; } @@ -702,11 +707,11 @@ function MoneyRequestConfirmationList({ return; } if (!isEditingSplitBill && isMerchantRequired && (isMerchantEmpty || (shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction ?? null)))) { - setFormError(translate('iou.error.invalidMerchant')); + setFormError('iou.error.invalidMerchant'); return; } if (iouCategory.length > CONST.API_TRANSACTION_CATEGORY_MAX_LENGTH) { - setFormError(translate('iou.error.invalidCategoryLength')); + setFormError('iou.error.invalidCategoryLength'); return; } if (iouType !== CONST.IOU.TYPE.PAY) { @@ -751,7 +756,6 @@ function MoneyRequestConfirmationList({ formError, iouType, setFormError, - translate, onSendMoney, iouCurrencyCode, isDistanceRequest, @@ -808,7 +812,7 @@ function MoneyRequestConfirmationList({ )} @@ -825,10 +829,11 @@ function MoneyRequestConfirmationList({ policyID, splitOrRequestOptions, formError, - debouncedFormError, - shouldShowReadOnlySplits, styles.ph1, styles.mb2, + shouldShowReadOnlySplits, + debouncedFormError, + translate, ]); // An intermediate structure that helps us classify the fields as "primary" and "supplementary". diff --git a/src/components/PDFView/PDFPasswordForm.tsx b/src/components/PDFView/PDFPasswordForm.tsx index e212d79b27d6..4b6491addbef 100644 --- a/src/components/PDFView/PDFPasswordForm.tsx +++ b/src/components/PDFView/PDFPasswordForm.tsx @@ -90,7 +90,7 @@ function PDFPasswordForm({isFocused, isPasswordInvalid = false, shouldShowLoadin return true; } if (!password) { - setValidationErrorText('attachmentView.passwordRequired'); + setValidationErrorText(translate('attachmentView.passwordRequired')); } return false; }; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 7cc0b49a88d9..6f475af474ec 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -539,7 +539,7 @@ function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry< } } else if ((ReportUtils.isIOUReport(report) || ReportUtils.isExpenseReport(report)) && report?.ownerAccountID === currentUserAccountID) { if (ReportUtils.shouldShowRBRForMissingSmartscanFields(report?.reportID ?? '') && !ReportUtils.isSettled(report?.reportID)) { - reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericSmartscanFailureMessage'); + reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); } } else if (ReportUtils.hasSmartscanError(Object.values(reportActions ?? {}))) { reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx index 22347f1b3bff..56703fb4b5c4 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -38,19 +38,22 @@ function AddressStep({onNext, isEditing}: SubStepProps) { zipCode: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.ZIP_CODE] ?? '', }; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { - errors.addressStreet = translate('bankAccount.error.addressStreet'); - } + if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { + errors.addressStreet = translate('bankAccount.error.addressStreet'); + } - if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { - errors.addressZipCode = translate('bankAccount.error.zipCode'); - } + if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { + errors.addressZipCode = translate('bankAccount.error.zipCode'); + } - return errors; - }; + return errors; + }, + [translate], + ); const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx index 7c4f5f43f2a1..d476fdcc5c86 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx @@ -1,5 +1,5 @@ import {subYears} from 'date-fns'; -import React from 'react'; +import React, {useCallback} from 'react'; import {useOnyx} from 'react-native-onyx'; import DatePicker from '@components/DatePicker'; import FormProvider from '@components/Form/FormProvider'; @@ -26,19 +26,22 @@ function DateOfBirthStep({onNext, isEditing}: SubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.dob) { - if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { - errors.dob = translate('bankAccount.error.dob'); - } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { - errors.dob = translate('bankAccount.error.age'); + if (values.dob) { + if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { + errors.dob = translate('bankAccount.error.dob'); + } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { + errors.dob = translate('bankAccount.error.age'); + } } - } - return errors; - }; + return errors; + }, + [translate], + ); const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx index 89240720f533..b40fb2202943 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -30,17 +30,20 @@ function FullNameStep({onNext, isEditing}: SubStepProps) { lastName: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.LAST_NAME] ?? '', }; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.legalFirstName && !ValidationUtils.isValidLegalName(values.legalFirstName)) { - errors.legalFirstName = translate('bankAccount.error.firstName'); - } + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + if (values.legalFirstName && !ValidationUtils.isValidLegalName(values.legalFirstName)) { + errors.legalFirstName = translate('bankAccount.error.firstName'); + } - if (values.legalLastName && !ValidationUtils.isValidLegalName(values.legalLastName)) { - errors.legalLastName = translate('bankAccount.error.lastName'); - } - return errors; - }; + if (values.legalLastName && !ValidationUtils.isValidLegalName(values.legalLastName)) { + errors.legalLastName = translate('bankAccount.error.lastName'); + } + return errors; + }, + [translate], + ); const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx index a5326504a1c6..60bfa431ca78 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -27,14 +27,17 @@ function PhoneNumberStep({onNext, isEditing}: SubStepProps) { const defaultPhoneNumber = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.PHONE_NUMBER] ?? ''; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.phoneNumber && !ValidationUtils.isValidUSPhone(values.phoneNumber, true)) { - errors.phoneNumber = translate('bankAccount.error.phoneNumber'); - } - return errors; - }; + if (values.phoneNumber && !ValidationUtils.isValidUSPhone(values.phoneNumber, true)) { + errors.phoneNumber = translate('bankAccount.error.phoneNumber'); + } + return errors; + }, + [translate], + ); const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx index cb3f2fa263f7..bdaa3fe98f67 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -23,15 +23,18 @@ function SocialSecurityNumberStep({onNext, isEditing}: SubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.ssn && !ValidationUtils.isValidSSNLastFour(values.ssn)) { - errors.ssn = translate('bankAccount.error.ssnLast4'); - } + if (values.ssn && !ValidationUtils.isValidSSNLastFour(values.ssn)) { + errors.ssn = translate('bankAccount.error.ssnLast4'); + } - return errors; - }; + return errors; + }, + [translate], + ); const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx index fa660c98d8bc..fb6a246ce84e 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx @@ -1,5 +1,5 @@ import type {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; @@ -52,15 +52,18 @@ function ConfirmationBusiness({reimbursementAccount, reimbursementAccountDraft, const {translate} = useLocalize(); const styles = useThemeStyles(); - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, [BUSINESS_INFO_STEP_KEYS.HAS_NO_CONNECTION_TO_CANNABIS]); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, [BUSINESS_INFO_STEP_KEYS.HAS_NO_CONNECTION_TO_CANNABIS]); - if (!values.hasNoConnectionToCannabis) { - errors.hasNoConnectionToCannabis = translate('bankAccount.error.restrictedBusiness'); - } + if (!values.hasNoConnectionToCannabis) { + errors.hasNoConnectionToCannabis = translate('bankAccount.error.restrictedBusiness'); + } - return errors; - }; + return errors; + }, + [translate], + ); const values = useMemo(() => getSubstepValues(BUSINESS_INFO_STEP_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx index 58a1feb710c2..274e087cc415 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/IncorporationDateBusiness.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import DatePicker from '@components/DatePicker'; @@ -33,17 +33,20 @@ function IncorporationDateBusiness({reimbursementAccount, reimbursementAccountDr const {translate} = useLocalize(); const styles = useThemeStyles(); - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.incorporationDate && !ValidationUtils.isValidDate(values.incorporationDate)) { - errors.incorporationDate = translate('common.error.dateInvalid'); - } else if (values.incorporationDate && !ValidationUtils.isValidPastDate(values.incorporationDate)) { - errors.incorporationDate = translate('bankAccount.error.incorporationDateFuture'); - } + if (values.incorporationDate && !ValidationUtils.isValidDate(values.incorporationDate)) { + errors.incorporationDate = translate('common.error.dateInvalid'); + } else if (values.incorporationDate && !ValidationUtils.isValidPastDate(values.incorporationDate)) { + errors.incorporationDate = translate('bankAccount.error.incorporationDateFuture'); + } - return errors; - }; + return errors; + }, + [translate], + ); const defaultCompanyIncorporationDate = reimbursementAccount?.achData?.incorporationDate ?? reimbursementAccountDraft?.incorporationDate ?? ''; diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/NameBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/NameBusiness.tsx index 712f42578f36..d7be083df3a0 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/NameBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/NameBusiness.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -35,15 +35,18 @@ function NameBusiness({reimbursementAccount, onNext, isEditing}: NameBusinessPro const shouldDisableCompanyName = !!(bankAccountID && defaultCompanyName && reimbursementAccount?.achData?.state !== 'SETUP'); - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.companyName && !ValidationUtils.isValidCompanyName(values.companyName)) { - errors.companyName = translate('bankAccount.error.companyName'); - } + if (values.companyName && !ValidationUtils.isValidCompanyName(values.companyName)) { + errors.companyName = translate('bankAccount.error.companyName'); + } - return errors; - }; + return errors; + }, + [translate], + ); const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx index b22424d194f9..e0c83737b6f8 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -31,15 +31,18 @@ function PhoneNumberBusiness({reimbursementAccount, onNext, isEditing}: PhoneNum const styles = useThemeStyles(); const defaultCompanyPhoneNumber = reimbursementAccount?.achData?.companyPhone ?? ''; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true)) { - errors.companyPhone = translate('bankAccount.error.phoneNumber'); - } + if (values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true)) { + errors.companyPhone = translate('bankAccount.error.phoneNumber'); + } - return errors; - }; + return errors; + }, + [translate], + ); const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/TaxIdBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/TaxIdBusiness.tsx index 6fba24b901e3..8e3ec6d5ec6e 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/TaxIdBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/TaxIdBusiness.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -32,15 +32,18 @@ function TaxIdBusiness({reimbursementAccount, onNext, isEditing}: TaxIdBusinessP const bankAccountID = reimbursementAccount?.achData?.bankAccountID ?? 0; const shouldDisableCompanyTaxID = !!(bankAccountID && defaultCompanyTaxId && reimbursementAccount?.achData?.state !== 'SETUP'); - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.companyTaxID && !ValidationUtils.isValidTaxID(values.companyTaxID)) { - errors.companyTaxID = translate('bankAccount.error.taxID'); - } + if (values.companyTaxID && !ValidationUtils.isValidTaxID(values.companyTaxID)) { + errors.companyTaxID = translate('bankAccount.error.taxID'); + } - return errors; - }; + return errors; + }, + [translate], + ); const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/WebsiteBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/WebsiteBusiness.tsx index 7115e1a82158..541e47f6cb10 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/WebsiteBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/WebsiteBusiness.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -41,16 +41,18 @@ function WebsiteBusiness({reimbursementAccount, user, session, onNext, isEditing const defaultWebsiteExample = useMemo(() => getDefaultCompanyWebsite(session, user), [session, user]); const defaultCompanyWebsite = reimbursementAccount?.achData?.website ?? defaultWebsiteExample; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.website && !ValidationUtils.isValidWebsite(values.website)) { - errors.website = translate('bankAccount.error.website'); - } - - return errors; - }; + if (values.website && !ValidationUtils.isValidWebsite(values.website)) { + errors.website = translate('bankAccount.error.website'); + } + return errors; + }, + [translate], + ); const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, diff --git a/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx b/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx index 1e2f004b9c8d..71d7a80ab58b 100644 --- a/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx +++ b/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; @@ -58,23 +58,26 @@ function ConfirmAgreements({onNext, reimbursementAccount}: ConfirmAgreementsProp certifyTrueInformation: reimbursementAccount?.achData?.certifyTrueInformation ?? false, acceptTermsAndConditions: reimbursementAccount?.achData?.acceptTermsAndConditions ?? false, }; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (!ValidationUtils.isRequiredFulfilled(values.acceptTermsAndConditions)) { - errors.acceptTermsAndConditions = translate('common.error.acceptTerms'); - } + if (!ValidationUtils.isRequiredFulfilled(values.acceptTermsAndConditions)) { + errors.acceptTermsAndConditions = translate('common.error.acceptTerms'); + } - if (!ValidationUtils.isRequiredFulfilled(values.certifyTrueInformation)) { - errors.certifyTrueInformation = translate('completeVerificationStep.certifyTrueAndAccurateError'); - } + if (!ValidationUtils.isRequiredFulfilled(values.certifyTrueInformation)) { + errors.certifyTrueInformation = translate('completeVerificationStep.certifyTrueAndAccurateError'); + } - if (!ValidationUtils.isRequiredFulfilled(values.isAuthorizedToUseBankAccount)) { - errors.isAuthorizedToUseBankAccount = translate('completeVerificationStep.isAuthorizedToUseBankAccountError'); - } + if (!ValidationUtils.isRequiredFulfilled(values.isAuthorizedToUseBankAccount)) { + errors.isAuthorizedToUseBankAccount = translate('completeVerificationStep.isAuthorizedToUseBankAccountError'); + } - return errors; - }; + return errors; + }, + [translate], + ); return ( ): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.requestorAddressStreet && !ValidationUtils.isValidAddress(values.requestorAddressStreet)) { - errors.requestorAddressStreet = translate('bankAccount.error.addressStreet'); - } + if (values.requestorAddressStreet && !ValidationUtils.isValidAddress(values.requestorAddressStreet)) { + errors.requestorAddressStreet = translate('bankAccount.error.addressStreet'); + } - if (values.requestorAddressZipCode && !ValidationUtils.isValidZipCode(values.requestorAddressZipCode)) { - errors.requestorAddressZipCode = translate('bankAccount.error.zipCode'); - } + if (values.requestorAddressZipCode && !ValidationUtils.isValidZipCode(values.requestorAddressZipCode)) { + errors.requestorAddressZipCode = translate('bankAccount.error.zipCode'); + } - return errors; - }; + return errors; + }, + [translate], + ); const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx index c47139af8a90..6fe391bbe957 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx @@ -1,5 +1,5 @@ import {subYears} from 'date-fns'; -import React from 'react'; +import React, {useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import DatePicker from '@components/DatePicker'; @@ -36,19 +36,22 @@ function DateOfBirth({reimbursementAccount, reimbursementAccountDraft, onNext, i const {translate} = useLocalize(); const styles = useThemeStyles(); - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.dob) { - if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { - errors.dob = translate('bankAccount.error.dob'); - } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { - errors.dob = translate('bankAccount.error.age'); + if (values.dob) { + if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { + errors.dob = translate('bankAccount.error.dob'); + } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { + errors.dob = translate('bankAccount.error.age'); + } } - } - return errors; - }; + return errors; + }, + [translate], + ); const dobDefaultValue = reimbursementAccount?.achData?.[PERSONAL_INFO_DOB_KEY] ?? reimbursementAccountDraft?.[PERSONAL_INFO_DOB_KEY] ?? ''; diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx index 79da5ea4c707..bab467707fd4 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -36,17 +36,20 @@ function FullName({reimbursementAccount, onNext, isEditing}: FullNameProps) { lastName: reimbursementAccount?.achData?.[PERSONAL_INFO_STEP_KEY.LAST_NAME] ?? '', }; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.firstName && !ValidationUtils.isValidLegalName(values.firstName)) { - errors.firstName = translate('bankAccount.error.firstName'); - } + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + if (values.firstName && !ValidationUtils.isValidLegalName(values.firstName)) { + errors.firstName = translate('bankAccount.error.firstName'); + } - if (values.lastName && !ValidationUtils.isValidLegalName(values.lastName)) { - errors.lastName = translate('bankAccount.error.lastName'); - } - return errors; - }; + if (values.lastName && !ValidationUtils.isValidLegalName(values.lastName)) { + errors.lastName = translate('bankAccount.error.lastName'); + } + return errors; + }, + [translate], + ); const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx index d33a0ac6243a..390880fa65f2 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -34,15 +34,18 @@ function SocialSecurityNumber({reimbursementAccount, onNext, isEditing}: SocialS const defaultSsnLast4 = reimbursementAccount?.achData?.[PERSONAL_INFO_STEP_KEY.SSN_LAST_4] ?? ''; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.ssnLast4 && !ValidationUtils.isValidSSNLastFour(values.ssnLast4)) { - errors.ssnLast4 = translate('bankAccount.error.ssnLast4'); - } + if (values.ssnLast4 && !ValidationUtils.isValidSSNLastFour(values.ssnLast4)) { + errors.ssnLast4 = translate('bankAccount.error.ssnLast4'); + } - return errors; - }; + return errors; + }, + [translate], + ); const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx index 4366f8ff284d..72358e909446 100644 --- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx +++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx @@ -74,7 +74,7 @@ function NewContactMethodPage({loginList, route}: NewContactMethodPageProps) { // the loginList gets updated, causing this function to run again. // https://github.com/Expensify/App/issues/20610 // eslint-disable-next-line react-hooks/exhaustive-deps - [], + [translate], ); const onBackButtonPress = useCallback(() => { diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx index 33484c2aee35..cf226655ce32 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx @@ -3,6 +3,7 @@ import type {ForwardedRef, RefAttributes} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {AutoCompleteVariant, MagicCodeInputHandle} from '@components/MagicCodeInput'; import MagicCodeInput from '@components/MagicCodeInput'; +import useLocalize from '@hooks/useLocalize'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as ValidationUtils from '@libs/ValidationUtils'; import * as Session from '@userActions/Session'; @@ -14,6 +15,7 @@ type BaseTwoFactorAuthFormProps = BaseTwoFactorAuthFormOnyxProps & { }; function BaseTwoFactorAuthForm({account, autoComplete}: BaseTwoFactorAuthFormProps, ref: ForwardedRef) { + const {translate} = useLocalize(); const [formError, setFormError] = useState<{twoFactorAuthCode?: string}>({}); const [twoFactorAuthCode, setTwoFactorAuthCode] = useState(''); const inputRef = useRef(null); @@ -41,18 +43,18 @@ function BaseTwoFactorAuthForm({account, autoComplete}: BaseTwoFactorAuthFormPro inputRef.current.blur(); } if (!twoFactorAuthCode.trim()) { - setFormError({twoFactorAuthCode: 'twoFactorAuthForm.error.pleaseFillTwoFactorAuth'}); + setFormError({twoFactorAuthCode: translate('twoFactorAuthForm.error.pleaseFillTwoFactorAuth')}); return; } if (!ValidationUtils.isValidTwoFactorCode(twoFactorAuthCode)) { - setFormError({twoFactorAuthCode: 'twoFactorAuthForm.error.incorrect2fa'}); + setFormError({twoFactorAuthCode: translate('twoFactorAuthForm.error.incorrect2fa')}); return; } setFormError({}); Session.validateTwoFactorAuth(twoFactorAuthCode); - }, [twoFactorAuthCode]); + }, [twoFactorAuthCode, translate]); useImperativeHandle(ref, () => ({ validateAndSubmitForm() { diff --git a/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx b/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx index 91883cc989b0..feae955b10fb 100644 --- a/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx @@ -36,13 +36,16 @@ function WorkspaceEditTagsPage({route, policyTags}: WorkspaceEditTagsPageProps) const taglistName = useMemo(() => PolicyUtils.getTagListName(policyTags, route.params.orderWeight), [policyTags, route.params.orderWeight]); const {inputCallbackRef} = useAutoFocusInput(); - const validateTagName = (values: FormOnyxValues) => { - const errors: FormInputErrors = {}; - if (!values[INPUT_IDS.POLICY_TAGS_NAME] && values[INPUT_IDS.POLICY_TAGS_NAME].trim() === '') { - errors[INPUT_IDS.POLICY_TAGS_NAME] = translate('common.error.fieldRequired'); - } - return errors; - }; + const validateTagName = useCallback( + (values: FormOnyxValues) => { + const errors: FormInputErrors = {}; + if (!values[INPUT_IDS.POLICY_TAGS_NAME] && values[INPUT_IDS.POLICY_TAGS_NAME].trim() === '') { + errors[INPUT_IDS.POLICY_TAGS_NAME] = translate('common.error.fieldRequired'); + } + return errors; + }, + [translate], + ); const updateTaglistName = useCallback( (values: FormOnyxValues) => { From bf38db1dd69ec3e0a8b09ebb22426cdb40a6301b Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Tue, 4 Jun 2024 14:54:39 -0400 Subject: [PATCH 019/127] add seperate handler for applying onyx updates from airship --- src/libs/actions/OnyxUpdates.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 04656f1adfec..2f08338a929a 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -23,6 +23,8 @@ Onyx.connect({ // even when such events are received over multiple separate pusher updates. let pusherEventsPromise = Promise.resolve(); +let airshipEventsPromise = Promise.resolve(); + function applyHTTPSOnyxUpdates(request: Request, response: Response) { console.debug('[OnyxUpdateManager] Applying https update'); // For most requests we can immediately update Onyx. For write requests we queue the updates and apply them after the sequential queue has flushed to prevent a replay effect in @@ -71,6 +73,20 @@ function applyPusherOnyxUpdates(updates: OnyxUpdateEvent[]) { return pusherEventsPromise; } +function applyAirshipOnyxUpdates(updates: OnyxUpdateEvent[]) { + airshipEventsPromise = airshipEventsPromise.then(() => { + console.debug('[OnyxUpdateManager] Applying Airship updates'); + }); + + airshipEventsPromise = updates + .reduce((promise, update) => promise.then(() => Onyx.update(update.data)), airshipEventsPromise) + .then(() => { + console.debug('[OnyxUpdateManager] Done applying Airship updates'); + }); + + return airshipEventsPromise; +} + /** * @param [updateParams.request] Exists if updateParams.type === 'https' * @param [updateParams.response] Exists if updateParams.type === 'https' @@ -108,9 +124,12 @@ function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFrom if (type === CONST.ONYX_UPDATE_TYPES.HTTPS && request && response) { return applyHTTPSOnyxUpdates(request, response); } - if ((type === CONST.ONYX_UPDATE_TYPES.PUSHER || type === CONST.ONYX_UPDATE_TYPES.AIRSHIP) && updates) { + if (type === CONST.ONYX_UPDATE_TYPES.PUSHER && updates) { return applyPusherOnyxUpdates(updates); } + if (type === CONST.ONYX_UPDATE_TYPES.AIRSHIP && updates) { + return applyAirshipOnyxUpdates(updates); + } } /** From f20e04d515c0d943e30d1735d1554fdd321d2f76 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Tue, 4 Jun 2024 14:56:47 -0400 Subject: [PATCH 020/127] log warning for unhandled pusher events --- src/libs/PusherUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/PusherUtils.ts b/src/libs/PusherUtils.ts index 2bd79adef516..8a83bb012b3a 100644 --- a/src/libs/PusherUtils.ts +++ b/src/libs/PusherUtils.ts @@ -17,6 +17,7 @@ function subscribeToMultiEvent(eventType: string, callback: Callback) { function triggerMultiEventHandler(eventType: string, data: OnyxUpdate[]): Promise { if (!multiEventCallbackMapping[eventType]) { + Log.warn('[PusherUtils] Received unexpected multi-event', {eventType}); return Promise.resolve(); } return multiEventCallbackMapping[eventType](data); From 340038dd7504d2615a39f25aca7a9beae63fc275 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Tue, 4 Jun 2024 14:57:02 -0400 Subject: [PATCH 021/127] clear useless eventType --- .../PushNotification/subscribePushNotification/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Notification/PushNotification/subscribePushNotification/index.ts b/src/libs/Notification/PushNotification/subscribePushNotification/index.ts index ec18f776d2d2..792c49c8bbcc 100644 --- a/src/libs/Notification/PushNotification/subscribePushNotification/index.ts +++ b/src/libs/Notification/PushNotification/subscribePushNotification/index.ts @@ -56,7 +56,7 @@ function applyOnyxData({reportID, reportActionID, onyxData, lastUpdateID, previo previousUpdateID, updates: [ { - eventType: 'eventType', + eventType: '', // This is only needed for Pusher events data: onyxData, }, ], From 03af563416663b39f6c6fae4551f9b51a6db8939 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:18:40 +0300 Subject: [PATCH 022/127] add trip data type --- src/types/onyx/Report.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index e388ce2cd330..f6eb9f70464e 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -203,6 +203,13 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< fieldList?: Record; permissions?: Array>; + + /** The trip data for a trip room */ + tripData?: { + startDate: string; + endDate: string; + tripId: string; + }; }, PolicyReportField['fieldID'] >; From 5b0fd9266bd20c0649c0121e805dbfd5412b7ce4 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:19:01 +0300 Subject: [PATCH 023/127] get trip id from transaction --- src/libs/ReportUtils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d82fb224784b..c5c57f1cb62b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6638,6 +6638,10 @@ function getTripTransactions(tripRoomReportID: string | undefined): Transaction[ return tripTransactionReportIDs.flatMap((reportID) => TransactionUtils.getAllReportTransactions(reportID)); } +function getTripIDFromTransactionParentReport(transactionParentReport: OnyxEntry | undefined | null): string | undefined { + return getReport(transactionParentReport?.parentReportID)?.tripData?.tripId; +} + /** * Checks if report contains actions with errors */ @@ -7075,6 +7079,7 @@ export { updateReportPreview, temporary_getMoneyRequestOptions, getTripTransactions, + getTripIDFromTransactionParentReport, buildOptimisticInvoiceReport, getInvoiceChatByParticipants, shouldShowMerchantColumn, From b2d1aa3152d02b142b6e7bf9dad451be2d4df40b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:19:19 +0300 Subject: [PATCH 024/127] get trip id and should show trip details view --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 2987638c759d..c24617676f90 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -171,6 +171,8 @@ function MoneyRequestView({ const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true)); const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest); + const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction); + const tripID = ReportUtils.getTripIDFromTransactionParentReport(parentReport); const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback( From d2f6dad16a67e05d4467d6df5da4e2d973007be1 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:27:29 +0300 Subject: [PATCH 025/127] add menu item for trip details --- src/components/ReportActionItem/MoneyRequestView.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index c24617676f90..626b46e8d144 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -4,6 +4,7 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import ConfirmedRoute from '@components/ConfirmedRoute'; import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {useSession} from '@components/OnyxProvider'; @@ -551,6 +552,13 @@ function MoneyRequestView({ /> )} + {shouldShowViewTripDetails && ( + + )} {shouldShowBillable && ( From 285135379e68e0eb704e2936f691b5468ac41f7c Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:27:39 +0300 Subject: [PATCH 026/127] add luggage icon --- src/components/ReportActionItem/MoneyRequestView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 626b46e8d144..ca0faa4af499 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -555,6 +555,7 @@ function MoneyRequestView({ {shouldShowViewTripDetails && ( From c4f451267037bc8a58adcd47efefe7dbe59ea07b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:28:16 +0300 Subject: [PATCH 027/127] add suitcase icon --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index ca0faa4af499..2cc00e32aab0 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -555,7 +555,7 @@ function MoneyRequestView({ {shouldShowViewTripDetails && ( From a408b6667a7f94fe3dc51e20a0e7075ed7a169b3 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:37:52 +0300 Subject: [PATCH 028/127] fix type tripid --- src/types/onyx/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index f6eb9f70464e..2c45156f40ec 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -208,7 +208,7 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< tripData?: { startDate: string; endDate: string; - tripId: string; + tripID: string; }; }, PolicyReportField['fieldID'] From fb7fdec2d4f9027ebbc296acdbc443100ef7be36 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:38:06 +0300 Subject: [PATCH 029/127] fix type tripid --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c5c57f1cb62b..4df0939db0bb 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6639,7 +6639,7 @@ function getTripTransactions(tripRoomReportID: string | undefined): Transaction[ } function getTripIDFromTransactionParentReport(transactionParentReport: OnyxEntry | undefined | null): string | undefined { - return getReport(transactionParentReport?.parentReportID)?.tripData?.tripId; + return getReport(transactionParentReport?.parentReportID)?.tripData?.tripID; } /** From 68ba31dbfd87f3ce4a51bfefb1799b7b8159c266 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:42:08 +0300 Subject: [PATCH 030/127] add trip id url --- src/CONST.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.ts b/src/CONST.ts index bc8f627630a3..634e750bf0e0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3306,6 +3306,7 @@ const CONST = { CONCIERGE_TRAVEL_URL: 'https://community.expensify.com/discussion/7066/introducing-concierge-travel', BOOK_TRAVEL_DEMO_URL: 'https://calendly.com/d/ck2z-xsh-q97/expensify-travel-demo-travel-page', + TRIP_ID_URL: (tripID: string) => `https://travel.expensify.com/trips/${tripID}`, SCREEN_READER_STATES: { ALL: 'all', ACTIVE: 'active', From 09af1940a5d7147dedfaa5d46decac86d6deb932 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:42:26 +0300 Subject: [PATCH 031/127] navigate to trip id url --- src/components/ReportActionItem/MoneyRequestView.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 2cc00e32aab0..62033c7ea3cd 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -1,5 +1,5 @@ import React, {useCallback, useMemo} from 'react'; -import {View} from 'react-native'; +import {Linking, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import ConfirmedRoute from '@components/ConfirmedRoute'; @@ -175,6 +175,13 @@ function MoneyRequestView({ const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction); const tripID = ReportUtils.getTripIDFromTransactionParentReport(parentReport); + const navigateToTripID = () => { + if (!tripID) { + return; + } + Linking.openURL(CONST.TRIP_ID_URL(tripID)); + }; + const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback( (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0, @@ -552,12 +559,13 @@ function MoneyRequestView({ /> )} - {shouldShowViewTripDetails && ( + {shouldShowViewTripDetails && tripID && ( )} {shouldShowBillable && ( From 69cf344a59178eb4b58414150066dbd5cde45622 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 5 Jun 2024 01:47:53 +0300 Subject: [PATCH 032/127] handle staging url --- src/CONST.ts | 2 +- src/components/ReportActionItem/MoneyRequestView.tsx | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 634e750bf0e0..cfa5e52b57e0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3306,7 +3306,7 @@ const CONST = { CONCIERGE_TRAVEL_URL: 'https://community.expensify.com/discussion/7066/introducing-concierge-travel', BOOK_TRAVEL_DEMO_URL: 'https://calendly.com/d/ck2z-xsh-q97/expensify-travel-demo-travel-page', - TRIP_ID_URL: (tripID: string) => `https://travel.expensify.com/trips/${tripID}`, + TRIP_ID_URL: (tripID: string, isProduction: boolean) => (isProduction ? `https://travel.expensify.com/trips/${tripID}` : `https://staging.travel.expensify.com/trips/${tripID}`), SCREEN_READER_STATES: { ALL: 'all', ACTIVE: 'active', diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 62033c7ea3cd..1a6b3742bb41 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -13,6 +13,7 @@ import ReceiptEmptyState from '@components/ReceiptEmptyState'; import Switch from '@components/Switch'; import Text from '@components/Text'; import ViolationMessages from '@components/ViolationMessages'; +import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePermissions from '@hooks/usePermissions'; @@ -100,6 +101,8 @@ function MoneyRequestView({ const session = useSession(); const {isOffline} = useNetwork(); const {translate, toLocaleDigit} = useLocalize(); + const {isProduction} = useEnvironment(); + const parentReportAction = parentReportActions?.[report.parentReportActionID ?? ''] ?? null; const isTrackExpense = ReportUtils.isTrackExpenseReport(report); const {canUseViolations, canUseP2PDistanceRequests} = usePermissions(isTrackExpense ? CONST.IOU.TYPE.TRACK : undefined); @@ -179,7 +182,7 @@ function MoneyRequestView({ if (!tripID) { return; } - Linking.openURL(CONST.TRIP_ID_URL(tripID)); + Linking.openURL(CONST.TRIP_ID_URL(tripID, isProduction)); }; const {getViolationsForField} = useViolations(transactionViolations ?? []); From f985b2406af66b5d51d92690fc3063de0a30f0e7 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 5 Jun 2024 10:38:14 +0200 Subject: [PATCH 033/127] chore: changes after review --- src/libs/E2E/client.ts | 3 ++- src/libs/E2E/tests/appStartTimeTest.e2e.ts | 2 -- src/libs/E2E/tests/chatOpeningTest.e2e.ts | 11 ++++------- src/libs/E2E/tests/linkingTest.e2e.ts | 2 -- src/libs/E2E/tests/openChatFinderPageTest.e2e.ts | 11 ++++------- src/libs/E2E/tests/reportTypingTest.e2e.ts | 5 +---- 6 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/libs/E2E/client.ts b/src/libs/E2E/client.ts index 5aa999267ead..7a0259de7eef 100644 --- a/src/libs/E2E/client.ts +++ b/src/libs/E2E/client.ts @@ -1,6 +1,7 @@ import Config from '../../../tests/e2e/config'; import Routes from '../../../tests/e2e/server/routes'; import type {NetworkCacheMap, TestConfig, TestResult} from './types'; +import {waitForActiveRequestsToBeEmpty} from './utils/NetworkInterceptor'; type NativeCommandPayload = { text: string; @@ -57,7 +58,7 @@ const submitTestResults = (testResult: TestResult): Promise => { }); }; -const submitTestDone = () => fetch(`${SERVER_ADDRESS}${Routes.testDone}`, defaultRequestInit); +const submitTestDone = () => waitForActiveRequestsToBeEmpty().then(() => fetch(`${SERVER_ADDRESS}${Routes.testDone}`, defaultRequestInit)); let currentActiveTestConfig: TestConfig | null = null; diff --git a/src/libs/E2E/tests/appStartTimeTest.e2e.ts b/src/libs/E2E/tests/appStartTimeTest.e2e.ts index 456c62b24ac9..321fc3773d51 100644 --- a/src/libs/E2E/tests/appStartTimeTest.e2e.ts +++ b/src/libs/E2E/tests/appStartTimeTest.e2e.ts @@ -3,7 +3,6 @@ import type {PerformanceEntry} from 'react-native-performance'; import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; -import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import Performance from '@libs/Performance'; const test = () => { @@ -31,7 +30,6 @@ const test = () => { }), ), ) - .then(waitForActiveRequestsToBeEmpty) .then(() => { console.debug('[E2E] Done, exiting…'); E2EClient.submitTestDone(); diff --git a/src/libs/E2E/tests/chatOpeningTest.e2e.ts b/src/libs/E2E/tests/chatOpeningTest.e2e.ts index c8887cab535d..8e43c4ece564 100644 --- a/src/libs/E2E/tests/chatOpeningTest.e2e.ts +++ b/src/libs/E2E/tests/chatOpeningTest.e2e.ts @@ -5,7 +5,6 @@ import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; -import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; @@ -30,13 +29,11 @@ const test = (config: NativeConfig) => { const [renderChatPromise, renderChatResolve] = getPromiseWithResolve(); const [chatTTIPromise, chatTTIResolve] = getPromiseWithResolve(); - Promise.all([renderChatPromise, chatTTIPromise]) - .then(waitForActiveRequestsToBeEmpty) - .then(() => { - console.debug(`[E2E] Submitting!`); + Promise.all([renderChatPromise, chatTTIPromise]).then(() => { + console.debug(`[E2E] Submitting!`); - E2EClient.submitTestDone(); - }); + E2EClient.submitTestDone(); + }); Performance.subscribeToMeasurements((entry) => { if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { diff --git a/src/libs/E2E/tests/linkingTest.e2e.ts b/src/libs/E2E/tests/linkingTest.e2e.ts index 8a1980a8b28a..c48ffb5a8057 100644 --- a/src/libs/E2E/tests/linkingTest.e2e.ts +++ b/src/libs/E2E/tests/linkingTest.e2e.ts @@ -6,7 +6,6 @@ import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; -import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; @@ -52,7 +51,6 @@ const test = (config: NativeConfig) => { name: 'Comment linking', duration: entry.duration, }) - .then(waitForActiveRequestsToBeEmpty) .then(() => { console.debug('[E2E] Test completed successfully, exiting…'); E2EClient.submitTestDone(); diff --git a/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts b/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts index 1837d31f7110..4ac7995b914f 100644 --- a/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts +++ b/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts @@ -3,7 +3,6 @@ import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; -import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; @@ -26,13 +25,11 @@ const test = () => { const [openSearchPagePromise, openSearchPageResolve] = getPromiseWithResolve(); const [loadSearchOptionsPromise, loadSearchOptionsResolve] = getPromiseWithResolve(); - Promise.all([openSearchPagePromise, loadSearchOptionsPromise]) - .then(waitForActiveRequestsToBeEmpty) - .then(() => { - console.debug(`[E2E] Submitting!`); + Promise.all([openSearchPagePromise, loadSearchOptionsPromise]).then(() => { + console.debug(`[E2E] Submitting!`); - E2EClient.submitTestDone(); - }); + E2EClient.submitTestDone(); + }); Performance.subscribeToMeasurements((entry) => { if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.ts b/src/libs/E2E/tests/reportTypingTest.e2e.ts index fec5daa246fe..817bda941611 100644 --- a/src/libs/E2E/tests/reportTypingTest.e2e.ts +++ b/src/libs/E2E/tests/reportTypingTest.e2e.ts @@ -5,7 +5,6 @@ import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import waitForKeyboard from '@libs/E2E/actions/waitForKeyboard'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; -import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import {getRerenderCount, resetRerenderCount} from '@pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e'; @@ -55,9 +54,7 @@ const test = (config: NativeConfig) => { branch: Config.E2E_BRANCH, name: 'Composer typing rerender count', renderCount: rerenderCount, - }) - .then(waitForActiveRequestsToBeEmpty) - .then(E2EClient.submitTestDone); + }).then(E2EClient.submitTestDone); }, 3000); }) .catch((error) => { From 45243f852c93b89ed8428aee3f6721e3cc9bff85 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Wed, 5 Jun 2024 11:01:34 -0400 Subject: [PATCH 034/127] add OnyxUpdates test for applying airship updates --- tests/unit/OnyxUpdatesTest.ts | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tests/unit/OnyxUpdatesTest.ts diff --git a/tests/unit/OnyxUpdatesTest.ts b/tests/unit/OnyxUpdatesTest.ts new file mode 100644 index 000000000000..c05738396585 --- /dev/null +++ b/tests/unit/OnyxUpdatesTest.ts @@ -0,0 +1,79 @@ +import type {KeyValueMapping, OnyxEntry, OnyxKey} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import CONST from '@src/CONST'; +import * as OnyxUpdates from '@src/libs/actions/OnyxUpdates'; +import DateUtils from '@src/libs/DateUtils'; +import * as NumberUtils from '@src/libs/NumberUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxUpdatesFromServer} from '@src/types/onyx'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + +describe('OnyxUpdatesTest', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + }); + }); + + beforeEach(() => Onyx.clear().then(waitForBatchedUpdates)); + + it('applies Airship Onyx updates correctly', () => { + const reportID = NumberUtils.rand64(); + const reportActionID = NumberUtils.rand64(); + const created = DateUtils.getDBTime(); + + const reportValue = {reportID}; + const reportActionValue = { + [reportActionID]: { + reportActionID, + created, + }, + }; + + // Given an onyx update from an Airship push notification + const airshipUpdates: OnyxUpdatesFromServer = { + type: CONST.ONYX_UPDATE_TYPES.AIRSHIP, + previousUpdateID: 0, + lastUpdateID: 1, + updates: [ + { + eventType: '', + data: [ + { + onyxMethod: 'merge', + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: reportValue, + }, + { + onyxMethod: 'merge', + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: reportActionValue, + shouldShowPushNotification: true, + }, + ], + }, + ], + }; + + // When we apply the updates, then their values are updated correctly + return OnyxUpdates.apply(airshipUpdates) + .then(() => getOnyxValues(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`)) + .then(([report, reportAction]) => { + expect(report).toStrictEqual(reportValue); + expect(reportAction).toStrictEqual(reportActionValue); + }); + }); +}); + +function getOnyxValues(...keys: TKey[]) { + return Promise.all(keys.map((key) => getOnyxValue(key))); +} + +function getOnyxValue(key: TKey): Promise> { + return new Promise((resolve) => { + Onyx.connect({ + key, + callback: (value) => resolve(value), + }); + }); +} From 6f6b5aaa5f28db500df7bd843f1d9a447baeb442 Mon Sep 17 00:00:00 2001 From: nkdengineer <161821005+nkdengineer@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:26:09 +0700 Subject: [PATCH 035/127] Update src/components/MoneyRequestConfirmationList.tsx Co-authored-by: Fedi Rajhi --- src/components/MoneyRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 7a95f9d1b9a4..a75a16fcbc0d 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -813,7 +813,7 @@ function MoneyRequestConfirmationList({ )} From c8bb635e96daecb17f5b7aa7308aa39fec0c6875 Mon Sep 17 00:00:00 2001 From: nkdengineer <161821005+nkdengineer@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:27:39 +0700 Subject: [PATCH 036/127] Update src/components/AddressForm.tsx Co-authored-by: Fedi Rajhi --- src/components/AddressForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index 006550664f93..b428db2fc9dd 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -121,7 +121,7 @@ function AddressForm({ if (countrySpecificZipRegex) { if (!countrySpecificZipRegex.test(values.zipPostCode?.trim().toUpperCase())) { if (ValidationUtils.isRequiredFulfilled(values.zipPostCode?.trim())) { - errors.zipPostCode = [translate('privatePersonalDetails.error.incorrectZipFormat', countryZipFormat)]; + errors.zipPostCode = translate('privatePersonalDetails.error.incorrectZipFormat', countryZipFormat); } else { errors.zipPostCode = translate('common.error.fieldRequired'); } From 041629763a4c4c6a7bceccf6dba6ab570180b7b8 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 6 Jun 2024 15:59:42 +0700 Subject: [PATCH 037/127] remove func translateIfPhraseKey and clean code --- .../MoneyRequestConfirmationList.tsx | 4 --- src/libs/ErrorUtils.ts | 8 +++-- src/libs/Localize/index.ts | 29 +------------------ .../BusinessInfo/substeps/AddressBusiness.tsx | 6 ++-- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 2 +- .../settings/Wallet/ExpensifyCardPage.tsx | 2 +- src/pages/workspace/WorkspaceNewRoomPage.tsx | 2 +- 7 files changed, 13 insertions(+), 40 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index a75a16fcbc0d..5b494fdcd4c2 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -351,10 +351,6 @@ function MoneyRequestConfirmationList({ const isCategoryRequired = !!policy?.requiresCategory; useEffect(() => { - if (shouldDisplayFieldError && hasSmartScanFailed) { - setFormError('iou.receiptScanningFailed'); - return; - } if (shouldDisplayFieldError && didConfirmSplit) { setFormError('iou.error.genericSmartscanFailureMessage'); return; diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 8d0752660dd3..be917d8f32ad 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -37,13 +37,17 @@ function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatO } /** - * Method used to get an error object with microsecond as the key. - * @param error - error key or message to be saved + * Creates an error object with a timestamp (in microseconds) as the key and the translated error message as the value. + * @param error - The translation key for the error message. */ function getMicroSecondOnyxErrorWithTranslationKey(error: TranslationPaths, errorKey?: number): Errors { return {[errorKey ?? DateUtils.getMicroseconds()]: Localize.translateLocal(error)}; } +/** + * Creates an error object with a timestamp (in microseconds) as the key and the error message as the value. + * @param error - The error message. + */ function getMicroSecondOnyxErrorWithMessage(error: string, errorKey?: number): Errors { return {[errorKey ?? DateUtils.getMicroseconds()]: error}; } diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 8c13e1648d88..306831bebb55 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -9,7 +9,6 @@ import translations from '@src/languages/translations'; import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Locale} from '@src/types/onyx'; -import type {ReceiptError} from '@src/types/onyx/Transaction'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; @@ -177,32 +176,6 @@ function translateLocal(phrase: TKey, ...variable type MaybePhraseKey = string | null | [string, Record & {isTranslated?: boolean}] | []; -/** - * Return translated string for given error. - */ -function translateIfPhraseKey(message: MaybePhraseKey): string; -function translateIfPhraseKey(message: ReceiptError): ReceiptError; -function translateIfPhraseKey(message: MaybePhraseKey | ReceiptError): string | ReceiptError; -function translateIfPhraseKey(message: MaybePhraseKey | ReceiptError): string | ReceiptError { - if (!message || (Array.isArray(message) && message.length === 0)) { - return ''; - } - - try { - // check if error message has a variable parameter - const [phrase, variables] = Array.isArray(message) ? message : [message]; - - // This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys. - if (variables?.isTranslated) { - return phrase; - } - - return translateLocal(phrase as TranslationPaths, variables as never); - } catch (error) { - return Array.isArray(message) ? message[0] : message; - } -} - function getPreferredListFormat(): Intl.ListFormat { if (!CONJUNCTION_LIST_FORMATS_FOR_LOCALES) { init(); @@ -254,5 +227,5 @@ function getDevicePreferredLocale(): Locale { return RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES])?.languageTag ?? CONST.LOCALES.DEFAULT; } -export {translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale}; +export {translate, translateLocal, formatList, formatMessageElementList, getDevicePreferredLocale}; export type {PhraseParameters, Phrase, MaybePhraseKey}; diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx index 8454a82969e7..66cca41bd58c 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React,{useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -36,7 +36,7 @@ function AddressBusiness({reimbursementAccount, onNext, isEditing}: AddressBusin const {translate} = useLocalize(); const styles = useThemeStyles(); - const validate = (values: FormOnyxValues): FormInputErrors => { + const validate = useCallback((values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { @@ -52,7 +52,7 @@ function AddressBusiness({reimbursementAccount, onNext, isEditing}: AddressBusin } return errors; - }; + }, [translate]) const defaultValues = { street: reimbursementAccount?.achData?.addressStreet ?? '', diff --git a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx index fc4ec0220b41..313a5a066496 100644 --- a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -206,7 +206,7 @@ function BaseValidateCodeForm({account = {}, contactMethod, hasMagicCodeBeenSent type="success" style={[styles.mt6, styles.flex0]} // eslint-disable-next-line @typescript-eslint/naming-convention - messages={{0: 'resendValidationForm.linkHasBeenResent'}} + messages={{0: translate('resendValidationForm.linkHasBeenResent')}} /> )} diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage.tsx index 49686e19852c..fe41a250ea9b 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.tsx +++ b/src/pages/settings/Wallet/ExpensifyCardPage.tsx @@ -173,7 +173,7 @@ function ExpensifyCardPage({ )} diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 3693b7164025..8e11acf3f055 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -193,7 +193,7 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli } if (!values.policyID) { - errors.policyID = 'newRoomPage.pleaseSelectWorkspace'; + errors.policyID = translate('newRoomPage.pleaseSelectWorkspace'); } return errors; From 727b30afd6795a12778d967d7ceba7cbca363f92 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 6 Jun 2024 16:16:09 +0700 Subject: [PATCH 038/127] fix lint --- .../BusinessInfo/substeps/AddressBusiness.tsx | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx index 66cca41bd58c..bd9524626a14 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/AddressBusiness.tsx @@ -1,4 +1,4 @@ -import React,{useCallback} from 'react'; +import React, {useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -36,23 +36,26 @@ function AddressBusiness({reimbursementAccount, onNext, isEditing}: AddressBusin const {translate} = useLocalize(); const styles = useThemeStyles(); - const validate = useCallback((values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { - errors.addressStreet = translate('bankAccount.error.addressStreet'); - } + if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { + errors.addressStreet = translate('bankAccount.error.addressStreet'); + } - if (values.addressCity && !ValidationUtils.isValidAddress(values.addressCity)) { - errors.addressCity = translate('bankAccount.error.addressCity'); - } + if (values.addressCity && !ValidationUtils.isValidAddress(values.addressCity)) { + errors.addressCity = translate('bankAccount.error.addressCity'); + } - if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { - errors.addressZipCode = translate('bankAccount.error.zipCode'); - } + if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { + errors.addressZipCode = translate('bankAccount.error.zipCode'); + } - return errors; - }, [translate]) + return errors; + }, + [translate], + ); const defaultValues = { street: reimbursementAccount?.achData?.addressStreet ?? '', From d065b7c9bf9dc84e91f41498cc3f301f9163ef2f Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 6 Jun 2024 16:45:00 +0200 Subject: [PATCH 039/127] e2e: fixed typing test --- .../ComposerWithSuggestions/index.e2e.tsx | 24 +++++++++++++++---- tests/e2e/config.ts | 17 +++++++------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx index 7f169ef15918..3c898fb4882b 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx @@ -2,6 +2,7 @@ import type {ForwardedRef} from 'react'; import React, {forwardRef, useEffect} from 'react'; import E2EClient from '@libs/E2E/client'; import type {ComposerRef} from '@pages/home/report/ReportActionCompose/ReportActionCompose'; +import {Keyboard} from 'react-native'; import type {ComposerWithSuggestionsProps} from './ComposerWithSuggestions'; import ComposerWithSuggestions from './ComposerWithSuggestions'; @@ -26,11 +27,26 @@ function ComposerWithSuggestionsE2e(props: ComposerWithSuggestionsProps, ref: Fo // We need to wait for the component to be mounted before focusing setTimeout(() => { - if (!(ref && 'current' in ref)) { - return; - } + const setFocus = () => { + if (!(ref && 'current' in ref)) { + return; + } - ref.current?.focus(true); + ref.current?.focus(true); + + setTimeout(() => { + // and actually let's verify that the keyboard is visible + if (Keyboard.isVisible()) { + return + } + + ref.current?.blur(); + setFocus(); + // 500ms is enough time for any keyboard to open + }, 500); + }; + + setFocus(); }, 1); }, [ref]); diff --git a/tests/e2e/config.ts b/tests/e2e/config.ts index 4120019de692..c7cb9d0ed3c7 100644 --- a/tests/e2e/config.ts +++ b/tests/e2e/config.ts @@ -74,15 +74,14 @@ export default { [TEST_NAMES.OpenChatFinderPage]: { name: TEST_NAMES.OpenChatFinderPage, }, - // TODO: Fix text and enable again - // [TEST_NAMES.ReportTyping]: { - // name: TEST_NAMES.ReportTyping, - // reportScreen: { - // autoFocus: true, - // }, - // // Crowded Policy (Do Not Delete) Report, has a input bar available: - // reportID: '8268282951170052', - // }, + [TEST_NAMES.ReportTyping]: { + name: TEST_NAMES.ReportTyping, + reportScreen: { + autoFocus: true, + }, + // Crowded Policy (Do Not Delete) Report, has a input bar available: + reportID: '8268282951170052', + }, [TEST_NAMES.ChatOpening]: { name: TEST_NAMES.ChatOpening, // #announce Chat with many messages From d0bc4fed3a434a7641f6f584aa1431467bb3890a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 7 Jun 2024 03:19:17 +0300 Subject: [PATCH 040/127] use translate --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 1a6b3742bb41..04fc6aeddee8 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -564,7 +564,7 @@ function MoneyRequestView({ )} {shouldShowViewTripDetails && tripID && ( Date: Fri, 7 Jun 2024 03:22:01 +0300 Subject: [PATCH 041/127] simplify --- src/components/ReportActionItem/MoneyRequestView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 04fc6aeddee8..0ae9289e6572 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -175,8 +175,8 @@ function MoneyRequestView({ const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true)); const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest); - const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction); const tripID = ReportUtils.getTripIDFromTransactionParentReport(parentReport); + const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction) && tripID; const navigateToTripID = () => { if (!tripID) { @@ -562,7 +562,7 @@ function MoneyRequestView({ /> )} - {shouldShowViewTripDetails && tripID && ( + {shouldShowViewTripDetails && ( Date: Fri, 7 Jun 2024 03:27:15 +0300 Subject: [PATCH 042/127] simplify travel const --- src/CONST.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index cfa5e52b57e0..8d77aa2eecf8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4,6 +4,7 @@ import dateSubtract from 'date-fns/sub'; import Config from 'react-native-config'; import * as KeyCommand from 'react-native-key-command'; import type {ValueOf} from 'type-fest'; +import {isProduction} from '@libs/Environment/Environment'; import BankAccount from './libs/models/BankAccount'; import * as Url from './libs/Url'; import SCREENS from './SCREENS'; @@ -3306,7 +3307,8 @@ const CONST = { CONCIERGE_TRAVEL_URL: 'https://community.expensify.com/discussion/7066/introducing-concierge-travel', BOOK_TRAVEL_DEMO_URL: 'https://calendly.com/d/ck2z-xsh-q97/expensify-travel-demo-travel-page', - TRIP_ID_URL: (tripID: string, isProduction: boolean) => (isProduction ? `https://travel.expensify.com/trips/${tripID}` : `https://staging.travel.expensify.com/trips/${tripID}`), + TRAVEL_DOT_URL: (isProduction: boolean) => (isProduction ? 'https://travel.expensify.com' : 'https://staging.travel.expensify.com'), + TRIP_ID_URL: (tripID: string, isProduction: boolean) => `${CONST.TRAVEL_DOT_URL(isProduction)}/trips/${tripID}`, SCREEN_READER_STATES: { ALL: 'all', ACTIVE: 'active', From 927288fc7ef9eb777b89e49efd38bbbd6ada7e18 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 7 Jun 2024 17:18:34 +0700 Subject: [PATCH 043/127] fix typecheck --- src/libs/actions/Policy/Member.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts index 14cebee27da1..ba32967e972e 100644 --- a/src/libs/actions/Policy/Member.ts +++ b/src/libs/actions/Policy/Member.ts @@ -617,7 +617,7 @@ function inviteMemberToWorkspace(policyID: string, inviterEmail: string) { { onyxMethod: Onyx.METHOD.MERGE, key: memberJoinKey, - value: {...failureMembersState, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericEditFailureMessage')}, + value: {...failureMembersState, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.common.genericEditFailureMessage')}, }, ]; From 61e22e07bbe8c6f726d2fd56321354f53cfbf669 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 21:15:18 +0300 Subject: [PATCH 044/127] rm import --- src/CONST.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 37bda26a374a..26cd1d33f359 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4,7 +4,6 @@ import dateSubtract from 'date-fns/sub'; import Config from 'react-native-config'; import * as KeyCommand from 'react-native-key-command'; import type {ValueOf} from 'type-fest'; -import {isProduction} from '@libs/Environment/Environment'; import BankAccount from './libs/models/BankAccount'; import * as Url from './libs/Url'; import SCREENS from './SCREENS'; From 750856b039bcbe85bfa62a7087101c67c7cc83b2 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 21:34:17 +0300 Subject: [PATCH 045/127] add generate spotnana command param type --- src/libs/API/parameters/GenerateSpotnanaTokenParams.ts | 5 +++++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 ++ src/libs/actions/Link.ts | 4 ++++ 4 files changed, 12 insertions(+) create mode 100644 src/libs/API/parameters/GenerateSpotnanaTokenParams.ts diff --git a/src/libs/API/parameters/GenerateSpotnanaTokenParams.ts b/src/libs/API/parameters/GenerateSpotnanaTokenParams.ts new file mode 100644 index 000000000000..bc67e9197502 --- /dev/null +++ b/src/libs/API/parameters/GenerateSpotnanaTokenParams.ts @@ -0,0 +1,5 @@ +type GenerateSpotnanaTokenParams = { + policyID: string; +}; + +export default GenerateSpotnanaTokenParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index b853f134b315..5e81ef3e6efa 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -225,3 +225,4 @@ export type {default as SendInvoiceParams} from './SendInvoiceParams'; export type {default as PayInvoiceParams} from './PayInvoiceParams'; export type {default as MarkAsCashParams} from './MarkAsCashParams'; export type {default as SignUpUserParams} from './SignUpUserParams'; +export type {default as GenerateSpotnanaTokenParams} from './GenerateSpotnanaTokenParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 6b34e04e1937..ef978ad7d4dc 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -548,6 +548,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = { GET_MISSING_ONYX_MESSAGES: 'GetMissingOnyxMessages', JOIN_POLICY_VIA_INVITE_LINK: 'JoinWorkspaceViaInviteLink', RECONNECT_APP: 'ReconnectApp', + GENERATE_SPOTNANA_TOKEN: 'GenerateSpotnanaToken', } as const; type SideEffectRequestCommand = ValueOf; @@ -560,6 +561,7 @@ type SideEffectRequestCommandParameters = { [SIDE_EFFECT_REQUEST_COMMANDS.GET_MISSING_ONYX_MESSAGES]: Parameters.GetMissingOnyxMessagesParams; [SIDE_EFFECT_REQUEST_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams; [SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP]: Parameters.ReconnectAppParams; + [SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN]: Parameters.GenerateSpotnanaTokenParams; }; type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters; diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index c83c946aed46..f29971878288 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -64,6 +64,10 @@ function openOldDotLink(url: string) { ); } +function openTravelDotLink(policyID: string) { + API.makeRequestWithSideEffects() +} + function getInternalNewExpensifyPath(href: string) { const attrPath = Url.getPathFromURL(href); return (Url.hasSameExpensifyOrigin(href, CONST.NEW_EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONST.STAGING_NEW_EXPENSIFY_URL) || href.startsWith(CONST.DEV_NEW_EXPENSIFY_URL)) && From 8ad2e06f19a0d93b51c1f7abd7f56d8f14c07250 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 22:06:46 +0300 Subject: [PATCH 046/127] add tmc id spotnananananananna --- src/CONST.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.ts b/src/CONST.ts index 26cd1d33f359..fca984de495d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3309,6 +3309,7 @@ const CONST = { BOOK_TRAVEL_DEMO_URL: 'https://calendly.com/d/ck2z-xsh-q97/expensify-travel-demo-travel-page', TRAVEL_DOT_URL: (isProduction: boolean) => (isProduction ? 'https://travel.expensify.com' : 'https://staging.travel.expensify.com'), TRIP_ID_URL: (tripID: string, isProduction: boolean) => `${CONST.TRAVEL_DOT_URL(isProduction)}/trips/${tripID}`, + SPOTNANA_TMC_ID: '7a290c6e-5328-4107-aff6-e48765845b81', SCREEN_READER_STATES: { ALL: 'all', ACTIVE: 'active', From 407b93a74c4dc3d3ffeaab13b84878f94cf16c1b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 22:07:28 +0300 Subject: [PATCH 047/127] add spotnana token in api response --- src/types/onyx/Response.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/types/onyx/Response.ts b/src/types/onyx/Response.ts index aa060223637b..c26280652254 100644 --- a/src/types/onyx/Response.ts +++ b/src/types/onyx/Response.ts @@ -56,6 +56,9 @@ type Response = { /** Short lived auth token generated by API */ shortLivedAuthToken?: string; + /** Short lived token generated by spotnana for authenticating travelDot */ + spotnanaToken?: string; + /** User authorization token to authorize Pusher connections */ auth?: string; From 53e65c929db31173c5f80ba0dbadb9901dc8b23a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 22:08:01 +0300 Subject: [PATCH 048/127] get travel dot from env --- src/libs/Environment/Environment.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/libs/Environment/Environment.ts b/src/libs/Environment/Environment.ts index 204e78aa5458..a862c4bc0f4e 100644 --- a/src/libs/Environment/Environment.ts +++ b/src/libs/Environment/Environment.ts @@ -17,6 +17,13 @@ const OLDDOT_ENVIRONMENT_URLS = { [CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_EXPENSIFY_URL, }; +const TRAVELDOT_ENVIRONMENT_URLS = { + [CONST.ENVIRONMENT.DEV]: CONST.TRAVEL_DOT_URL(false), + [CONST.ENVIRONMENT.STAGING]: CONST.TRAVEL_DOT_URL(false), + [CONST.ENVIRONMENT.PRODUCTION]: CONST.TRAVEL_DOT_URL(true), + [CONST.ENVIRONMENT.ADHOC]: CONST.TRAVEL_DOT_URL(false), +}; + /** * Are we running the app in development? */ @@ -54,4 +61,8 @@ function getOldDotEnvironmentURL(): Promise { return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment]); } -export {getEnvironment, isInternalTestBuild, isDevelopment, isProduction, getEnvironmentURL, getOldDotEnvironmentURL}; +function getTravelDotEnvironmentURL(): Promise { + return getEnvironment().then((environment) => TRAVELDOT_ENVIRONMENT_URLS[environment]); +} + +export {getEnvironment, isInternalTestBuild, isDevelopment, isProduction, getEnvironmentURL, getOldDotEnvironmentURL, getTravelDotEnvironmentURL}; From 05a9cc65ae0d81ea369008c49b1071cbf4ee5fc7 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 22:08:18 +0300 Subject: [PATCH 049/127] build travel dot url --- src/libs/actions/Link.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index f29971878288..61fa66c9f4bf 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -1,5 +1,6 @@ import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; +import type {GenerateSpotnanaTokenParams} from '@libs/API/parameters'; import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types'; import asyncOpenURL from '@libs/asyncOpenURL'; import * as Environment from '@libs/Environment/Environment'; @@ -64,8 +65,24 @@ function openOldDotLink(url: string) { ); } +function buildTravelDotURL(spotnanaToken: string): Promise { + return Environment.getTravelDotEnvironmentURL().then((environmentURL) => { + const travelDotDomain = Url.addTrailingForwardSlash(environmentURL); + return `${travelDotDomain}auth/code?authCode=${spotnanaToken}&tmcId=${CONST.SPOTNANA_TMC_ID}`; + }); +} + function openTravelDotLink(policyID: string) { - API.makeRequestWithSideEffects() + const parameters: GenerateSpotnanaTokenParams = { + policyID, + }; + asyncOpenURL( + // eslint-disable-next-line rulesdir/no-api-side-effects-method + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN, parameters, {}).then( + (response) => response.spotnanaToken && buildTravelDotURL(response.spotnanaToken), + ), + (travelDotURL) => travelDotURL, + ); } function getInternalNewExpensifyPath(href: string) { @@ -125,4 +142,4 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { openExternalLink(href); } -export {buildOldDotURL, openOldDotLink, openExternalLink, openLink, getInternalNewExpensifyPath, getInternalExpensifyPath}; +export {buildOldDotURL, openOldDotLink, openExternalLink, openLink, getInternalNewExpensifyPath, getInternalExpensifyPath, openTravelDotLink}; From c0950833b988c3f1e0b710ee20cc470d37c28d7e Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 22:16:01 +0300 Subject: [PATCH 050/127] make spotnana token optional --- src/libs/actions/Link.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 61fa66c9f4bf..bbdccd76a876 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -65,10 +65,11 @@ function openOldDotLink(url: string) { ); } -function buildTravelDotURL(spotnanaToken: string): Promise { +function buildTravelDotURL(spotnanaToken?: string): Promise { + const authCode = spotnanaToken ? `authCode=${spotnanaToken}` : ''; return Environment.getTravelDotEnvironmentURL().then((environmentURL) => { const travelDotDomain = Url.addTrailingForwardSlash(environmentURL); - return `${travelDotDomain}auth/code?authCode=${spotnanaToken}&tmcId=${CONST.SPOTNANA_TMC_ID}`; + return `${travelDotDomain}auth/code?${authCode}&tmcId=${CONST.SPOTNANA_TMC_ID}`; }); } @@ -78,9 +79,9 @@ function openTravelDotLink(policyID: string) { }; asyncOpenURL( // eslint-disable-next-line rulesdir/no-api-side-effects-method - API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN, parameters, {}).then( - (response) => response.spotnanaToken && buildTravelDotURL(response.spotnanaToken), - ), + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN, parameters, {}) + .then((response) => (response?.spotnanaToken ? buildTravelDotURL(response.spotnanaToken) : buildTravelDotURL())) + .catch(() => buildTravelDotURL()), (travelDotURL) => travelDotURL, ); } From 5ee49dd304782e4f48a730389da4a3fb88165cb8 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 22:23:03 +0300 Subject: [PATCH 051/127] add postlogin url --- src/libs/actions/Link.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index bbdccd76a876..0e6fb7422553 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -65,22 +65,29 @@ function openOldDotLink(url: string) { ); } -function buildTravelDotURL(spotnanaToken?: string): Promise { +function buildTravelDotURL(spotnanaToken?: string, postLoginURL?: string): Promise { const authCode = spotnanaToken ? `authCode=${spotnanaToken}` : ''; + const tmcID = `tmcId=${CONST.SPOTNANA_TMC_ID}`; + const redirectURL = postLoginURL ? `redirectUrl=${postLoginURL}` : ''; + + const paramsArray = [authCode, tmcID, redirectURL]; + const params = paramsArray.filter(Boolean).join('&'); + return Environment.getTravelDotEnvironmentURL().then((environmentURL) => { const travelDotDomain = Url.addTrailingForwardSlash(environmentURL); - return `${travelDotDomain}auth/code?${authCode}&tmcId=${CONST.SPOTNANA_TMC_ID}`; + return `${travelDotDomain}auth/code?${params}`; }); } -function openTravelDotLink(policyID: string) { +function openTravelDotLink(policyID: string, postLoginURL?: string) { const parameters: GenerateSpotnanaTokenParams = { policyID, }; + asyncOpenURL( // eslint-disable-next-line rulesdir/no-api-side-effects-method API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN, parameters, {}) - .then((response) => (response?.spotnanaToken ? buildTravelDotURL(response.spotnanaToken) : buildTravelDotURL())) + .then((response) => (response?.spotnanaToken ? buildTravelDotURL(response.spotnanaToken, postLoginURL) : buildTravelDotURL())) .catch(() => buildTravelDotURL()), (travelDotURL) => travelDotURL, ); From 25400ac7ceef9c6a39f4fcd166cc99302698c73a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 22:23:27 +0300 Subject: [PATCH 052/127] get rid of env --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 9b1a3f84bae7..d995865ead68 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -12,7 +12,6 @@ import ReceiptEmptyState from '@components/ReceiptEmptyState'; import Switch from '@components/Switch'; import Text from '@components/Text'; import ViolationMessages from '@components/ViolationMessages'; -import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePermissions from '@hooks/usePermissions'; @@ -100,7 +99,6 @@ function MoneyRequestView({ const session = useSession(); const {isOffline} = useNetwork(); const {translate, toLocaleDigit} = useLocalize(); - const {isProduction} = useEnvironment(); const parentReportAction = parentReportActions?.[report.parentReportActionID ?? ''] ?? null; const isTrackExpense = ReportUtils.isTrackExpenseReport(report); From 0f17458ccb118bb6aceb09c928aab6a8e4b480ef Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 22:32:32 +0300 Subject: [PATCH 053/127] add early return and add login url for trip id --- src/CONST.ts | 2 +- .../ReportActionItem/MoneyRequestView.tsx | 15 +++++---------- src/libs/actions/Link.ts | 7 ++++++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index fca984de495d..b48f0f6db91d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3308,7 +3308,7 @@ const CONST = { CONCIERGE_TRAVEL_URL: 'https://community.expensify.com/discussion/7066/introducing-concierge-travel', BOOK_TRAVEL_DEMO_URL: 'https://calendly.com/d/ck2z-xsh-q97/expensify-travel-demo-travel-page', TRAVEL_DOT_URL: (isProduction: boolean) => (isProduction ? 'https://travel.expensify.com' : 'https://staging.travel.expensify.com'), - TRIP_ID_URL: (tripID: string, isProduction: boolean) => `${CONST.TRAVEL_DOT_URL(isProduction)}/trips/${tripID}`, + TRIP_ID_PATH: (tripID: string) => `trips/${tripID}`, SPOTNANA_TMC_ID: '7a290c6e-5328-4107-aff6-e48765845b81', SCREEN_READER_STATES: { ALL: 'all', diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index d995865ead68..7c6ac9f4c3ed 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useMemo} from 'react'; -import {Linking, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {View} from 'react-native'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; @@ -34,6 +34,7 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import Navigation from '@navigation/Navigation'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import * as IOU from '@userActions/IOU'; +import * as Link from '@userActions/Link'; import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -99,6 +100,7 @@ function MoneyRequestView({ const session = useSession(); const {isOffline} = useNetwork(); const {translate, toLocaleDigit} = useLocalize(); + const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const parentReportAction = parentReportActions?.[report.parentReportActionID ?? ''] ?? null; const isTrackExpense = ReportUtils.isTrackExpenseReport(report); @@ -173,13 +175,6 @@ function MoneyRequestView({ const tripID = ReportUtils.getTripIDFromTransactionParentReport(parentReport); const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction) && tripID; - const navigateToTripID = () => { - if (!tripID) { - return; - } - Linking.openURL(CONST.TRIP_ID_URL(tripID, isProduction)); - }; - const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback( (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0, @@ -555,7 +550,7 @@ function MoneyRequestView({ icon={Expensicons.Suitcase} iconRight={Expensicons.NewWindow} shouldShowRightIcon - onPress={navigateToTripID} + onPress={() => Link.openTravelDotLink(activePolicyID, CONST.TRIP_ID_PATH(tripID))} /> )} {shouldShowBillable && ( diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 0e6fb7422553..24c5d0be8274 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import * as API from '@libs/API'; import type {GenerateSpotnanaTokenParams} from '@libs/API/parameters'; import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types'; @@ -79,7 +80,11 @@ function buildTravelDotURL(spotnanaToken?: string, postLoginURL?: string): Promi }); } -function openTravelDotLink(policyID: string, postLoginURL?: string) { +function openTravelDotLink(policyID: OnyxEntry, postLoginURL?: string) { + if (policyID === null) { + return; + } + const parameters: GenerateSpotnanaTokenParams = { policyID, }; From 17fba2d657ed4b58f0a28f3fe578a7eb719ff91a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 23:08:10 +0300 Subject: [PATCH 054/127] add docs --- src/types/onyx/Report.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 6caebc1b05f7..9b0b35a6da7c 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -277,8 +277,13 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** The trip data for a trip room */ tripData?: { + /** The start date of a trip */ startDate: string; + + /** The end date of a trip */ endDate: string; + + /** The trip ID in spotnana */ tripID: string; }; }, From deb01aa09f77ac67587a2e22dada43b65720324c Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 23:10:35 +0300 Subject: [PATCH 055/127] add docs --- src/libs/actions/Link.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 24c5d0be8274..07c94a7c4fc8 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -80,6 +80,9 @@ function buildTravelDotURL(spotnanaToken?: string, postLoginURL?: string): Promi }); } +/** + * @param postLoginURL When provided, we will redirect the user to this url post login on travelDot. + */ function openTravelDotLink(policyID: OnyxEntry, postLoginURL?: string) { if (policyID === null) { return; From 57da999ca732e2d6b989e7985a3be0c76b17bb2d Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 23:13:14 +0300 Subject: [PATCH 056/127] rm dependency of env for travel url --- src/CONST.ts | 3 ++- src/libs/Environment/Environment.ts | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index b48f0f6db91d..c06760216a52 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3307,7 +3307,8 @@ const CONST = { CONCIERGE_TRAVEL_URL: 'https://community.expensify.com/discussion/7066/introducing-concierge-travel', BOOK_TRAVEL_DEMO_URL: 'https://calendly.com/d/ck2z-xsh-q97/expensify-travel-demo-travel-page', - TRAVEL_DOT_URL: (isProduction: boolean) => (isProduction ? 'https://travel.expensify.com' : 'https://staging.travel.expensify.com'), + TRAVEL_DOT_URL: 'https://travel.expensify.com', + STAGING_TRAVEL_DOT_URL: 'https://staging.travel.expensify.com', TRIP_ID_PATH: (tripID: string) => `trips/${tripID}`, SPOTNANA_TMC_ID: '7a290c6e-5328-4107-aff6-e48765845b81', SCREEN_READER_STATES: { diff --git a/src/libs/Environment/Environment.ts b/src/libs/Environment/Environment.ts index a862c4bc0f4e..e7fa66e37437 100644 --- a/src/libs/Environment/Environment.ts +++ b/src/libs/Environment/Environment.ts @@ -18,10 +18,10 @@ const OLDDOT_ENVIRONMENT_URLS = { }; const TRAVELDOT_ENVIRONMENT_URLS = { - [CONST.ENVIRONMENT.DEV]: CONST.TRAVEL_DOT_URL(false), - [CONST.ENVIRONMENT.STAGING]: CONST.TRAVEL_DOT_URL(false), - [CONST.ENVIRONMENT.PRODUCTION]: CONST.TRAVEL_DOT_URL(true), - [CONST.ENVIRONMENT.ADHOC]: CONST.TRAVEL_DOT_URL(false), + [CONST.ENVIRONMENT.DEV]: CONST.STAGING_TRAVEL_DOT_URL, + [CONST.ENVIRONMENT.STAGING]: CONST.STAGING_TRAVEL_DOT_URL, + [CONST.ENVIRONMENT.PRODUCTION]: CONST.TRAVEL_DOT_URL, + [CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_TRAVEL_DOT_URL, }; /** From d5176fdd4b2113d983b6d2223eea489472b00557 Mon Sep 17 00:00:00 2001 From: dominictb Date: Mon, 10 Jun 2024 03:14:45 +0700 Subject: [PATCH 057/127] fix: another solution for cursor position after paste Signed-off-by: dominictb --- src/components/Composer/index.tsx | 3 ++- src/hooks/useHtmlPaste/index.ts | 11 +++++++++-- src/hooks/useHtmlPaste/types.ts | 6 +++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 69e2fd909010..c4728cd6635e 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -9,6 +9,7 @@ import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; import Text from '@components/Text'; import useHtmlPaste from '@hooks/useHtmlPaste'; +import UseHtmlPaste from '@hooks/useHtmlPaste/types'; import useIsScrollBarVisible from '@hooks/useIsScrollBarVisible'; import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -251,7 +252,7 @@ function Composer( // eslint-disable-next-line react-hooks/exhaustive-deps }, [isComposerFullSize]); - useHtmlPaste(textInput, handlePaste, true, false); + useHtmlPaste(textInput as Parameters[0], handlePaste, true, false); useEffect(() => { if (typeof ref === 'function') { diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 11b70a6c4a11..645d6be076e5 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -45,13 +45,20 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi insertByCommand(text); } + if (!textInputRef.current?.isFocused()) { + textInputRef.current?.focus(); + return; + } + // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. // If shouldRefocusAfterPaste = false, we want to call `focus()` only as it won't change the focus state of the input since it is already focused if (shouldRefocusAfterPaste) { textInputRef.current?.blur(); + textInputRef.current?.focus(); + return; } - - textInputRef.current?.focus(); + // just restore the selection position if the input is already focused + textInputRef.current?.restoreSelectionPosition?.(); // eslint-disable-next-line no-empty } catch (e) {} // We only need to set the callback once. diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index f2dd41eb2488..377b0d6a7703 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -1,8 +1,12 @@ import type {MutableRefObject} from 'react'; import type {TextInput} from 'react-native'; +export type WithSelectionRestoreAbility = { + restoreSelectionPosition?: () => void; +}; + type UseHtmlPaste = ( - textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput) | TextInput | null>, + textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput & WithSelectionRestoreAbility) | (TextInput & WithSelectionRestoreAbility) | null>, preHtmlPasteCallback?: (event: ClipboardEvent) => boolean, removeListenerOnScreenBlur?: boolean, shouldRefocusAfterPaste?: boolean, From 99569002e225ce596ab805206474e85ec52caa09 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 9 Jun 2024 23:16:13 +0300 Subject: [PATCH 058/127] rename to path for clarity --- src/libs/actions/Link.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 07c94a7c4fc8..6104982da412 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -66,10 +66,10 @@ function openOldDotLink(url: string) { ); } -function buildTravelDotURL(spotnanaToken?: string, postLoginURL?: string): Promise { +function buildTravelDotURL(spotnanaToken?: string, postLoginPath?: string): Promise { const authCode = spotnanaToken ? `authCode=${spotnanaToken}` : ''; const tmcID = `tmcId=${CONST.SPOTNANA_TMC_ID}`; - const redirectURL = postLoginURL ? `redirectUrl=${postLoginURL}` : ''; + const redirectURL = postLoginPath ? `redirectUrl=${postLoginPath}` : ''; const paramsArray = [authCode, tmcID, redirectURL]; const params = paramsArray.filter(Boolean).join('&'); @@ -81,9 +81,9 @@ function buildTravelDotURL(spotnanaToken?: string, postLoginURL?: string): Promi } /** - * @param postLoginURL When provided, we will redirect the user to this url post login on travelDot. + * @param postLoginPath When provided, we will redirect the user to this path post login on travelDot. eg: 'trips/:tripID' */ -function openTravelDotLink(policyID: OnyxEntry, postLoginURL?: string) { +function openTravelDotLink(policyID: OnyxEntry, postLoginPath?: string) { if (policyID === null) { return; } @@ -95,7 +95,7 @@ function openTravelDotLink(policyID: OnyxEntry, postLoginURL?: string) { asyncOpenURL( // eslint-disable-next-line rulesdir/no-api-side-effects-method API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN, parameters, {}) - .then((response) => (response?.spotnanaToken ? buildTravelDotURL(response.spotnanaToken, postLoginURL) : buildTravelDotURL())) + .then((response) => (response?.spotnanaToken ? buildTravelDotURL(response.spotnanaToken, postLoginPath) : buildTravelDotURL())) .catch(() => buildTravelDotURL()), (travelDotURL) => travelDotURL, ); From a47bee0ed4ef10e00b81dc37c83c6a032ad0387f Mon Sep 17 00:00:00 2001 From: dominictb Date: Mon, 10 Jun 2024 03:30:04 +0700 Subject: [PATCH 059/127] fix: lint issue Signed-off-by: dominictb --- src/components/Composer/index.tsx | 3 +-- src/hooks/useHtmlPaste/types.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index c4728cd6635e..69e2fd909010 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -9,7 +9,6 @@ import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; import Text from '@components/Text'; import useHtmlPaste from '@hooks/useHtmlPaste'; -import UseHtmlPaste from '@hooks/useHtmlPaste/types'; import useIsScrollBarVisible from '@hooks/useIsScrollBarVisible'; import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -252,7 +251,7 @@ function Composer( // eslint-disable-next-line react-hooks/exhaustive-deps }, [isComposerFullSize]); - useHtmlPaste(textInput as Parameters[0], handlePaste, true, false); + useHtmlPaste(textInput, handlePaste, true, false); useEffect(() => { if (typeof ref === 'function') { diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index 377b0d6a7703..10ad40d5e4c0 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -1,7 +1,7 @@ import type {MutableRefObject} from 'react'; import type {TextInput} from 'react-native'; -export type WithSelectionRestoreAbility = { +type WithSelectionRestoreAbility = { restoreSelectionPosition?: () => void; }; From fad7da260c98202e56c4c05dcead8d79a393b90b Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 10 Jun 2024 10:58:19 +0700 Subject: [PATCH 060/127] fix: App allows splitting when the sum of splits is not equal to total amount --- .../MoneyRequestConfirmationList.tsx | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index e7c799fea3b6..cd6869b0d050 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -348,14 +348,14 @@ function MoneyRequestConfirmationList({ const isCategoryRequired = !!policy?.requiresCategory; useEffect(() => { - if (shouldDisplayFieldError && hasSmartScanFailed) { - setFormError('iou.receiptScanningFailed'); - return; - } if (shouldDisplayFieldError && didConfirmSplit) { setFormError('iou.error.genericSmartscanFailureMessage'); return; } + if (shouldDisplayFieldError && hasSmartScanFailed) { + setFormError('iou.receiptScanningFailed'); + return; + } // reset the form error whenever the screen gains or loses focus setFormError(''); @@ -718,20 +718,7 @@ function MoneyRequestConfirmationList({ return; } - if (formError) { - return; - } - - if (iouType === CONST.IOU.TYPE.PAY) { - if (!paymentMethod) { - return; - } - - setDidConfirm(true); - - Log.info(`[IOU] Sending money via: ${paymentMethod}`); - onSendMoney?.(paymentMethod); - } else { + if (iouType !== CONST.IOU.TYPE.PAY) { // validate the amount for distance expenses const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode); if (isDistanceRequest && !isDistanceRequestWithPendingRoute && !MoneyRequestUtils.validateAmount(String(iouAmount), decimals)) { @@ -745,9 +732,25 @@ function MoneyRequestConfirmationList({ return; } + if (formError) { + return; + } + playSound(SOUNDS.DONE); setDidConfirm(true); onConfirm?.(selectedParticipants); + } else { + if (!paymentMethod) { + return; + } + if (formError) { + return; + } + + setDidConfirm(true); + + Log.info(`[IOU] Sending money via: ${paymentMethod}`); + onSendMoney?.(paymentMethod); } }, [ From 506ac9a782413a16c6c5ef94fd9784d6ce724730 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 10 Jun 2024 16:41:16 +0700 Subject: [PATCH 061/127] fix ts error --- src/libs/actions/Policy/Member.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts index ba32967e972e..a92332bc9be1 100644 --- a/src/libs/actions/Policy/Member.ts +++ b/src/libs/actions/Policy/Member.ts @@ -617,7 +617,7 @@ function inviteMemberToWorkspace(policyID: string, inviterEmail: string) { { onyxMethod: Onyx.METHOD.MERGE, key: memberJoinKey, - value: {...failureMembersState, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.common.genericEditFailureMessage')}, + value: {...failureMembersState, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage')}, }, ]; From 15e9eba77a9d4df7d669390eb0f68af6aa130868 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 10 Jun 2024 16:52:48 +0700 Subject: [PATCH 062/127] fix lint --- src/types/onyx/OnyxCommon.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index c1a2ba81e7b4..ee60b6dbf1fd 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -23,6 +23,7 @@ type OnyxValueWithOfflineFeedback = keyof TO /** Mapping of form fields with errors */ type ErrorFields = Record; +/** Mapping of form fields with error translation keys and variables */ type Errors = Record; /** From e15858ee9ae2517cbc0d29bd666f6e33da07a25b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 10 Jun 2024 16:58:39 +0300 Subject: [PATCH 063/127] Update MoneyRequestView.tsx --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 7c6ac9f4c3ed..4edacfffea50 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -173,7 +173,7 @@ function MoneyRequestView({ const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest); const tripID = ReportUtils.getTripIDFromTransactionParentReport(parentReport); - const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction) && tripID; + const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction) && Boolean(tripID); const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback( From a6d48f2aa223a6d1aeb24ed03b0317568a3391d4 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 10 Jun 2024 17:20:02 +0300 Subject: [PATCH 064/127] Apply suggestions from code review --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 4edacfffea50..4c721b950fed 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -173,7 +173,7 @@ function MoneyRequestView({ const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest); const tripID = ReportUtils.getTripIDFromTransactionParentReport(parentReport); - const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction) && Boolean(tripID); + const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction) && !!tripID; const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback( From f0d4fd880aaf33b98e59106a3e11b63cb99d55f1 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 10 Jun 2024 18:44:50 +0300 Subject: [PATCH 065/127] add prod tmc id --- src/CONST.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index c06760216a52..10eb6dd7bd01 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3310,7 +3310,8 @@ const CONST = { TRAVEL_DOT_URL: 'https://travel.expensify.com', STAGING_TRAVEL_DOT_URL: 'https://staging.travel.expensify.com', TRIP_ID_PATH: (tripID: string) => `trips/${tripID}`, - SPOTNANA_TMC_ID: '7a290c6e-5328-4107-aff6-e48765845b81', + SPOTNANA_TMC_ID: '8e8e7258-1cf3-48c0-9cd1-fe78a6e31eed', + STAGING_SPOTNANA_TMC_ID: '7a290c6e-5328-4107-aff6-e48765845b81', SCREEN_READER_STATES: { ALL: 'all', ACTIVE: 'active', From dae0dd4e0d27bf92a78b8b02aa2a9df706531a76 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 10 Jun 2024 18:45:13 +0300 Subject: [PATCH 066/127] tmc id as per environment --- src/libs/Environment/Environment.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/libs/Environment/Environment.ts b/src/libs/Environment/Environment.ts index e7fa66e37437..541243edf6b7 100644 --- a/src/libs/Environment/Environment.ts +++ b/src/libs/Environment/Environment.ts @@ -24,6 +24,13 @@ const TRAVELDOT_ENVIRONMENT_URLS = { [CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_TRAVEL_DOT_URL, }; +const SPOTNANA_ENVIRONMENT_TMC_ID = { + [CONST.ENVIRONMENT.DEV]: CONST.STAGING_SPOTNANA_TMC_ID, + [CONST.ENVIRONMENT.STAGING]: CONST.STAGING_SPOTNANA_TMC_ID, + [CONST.ENVIRONMENT.PRODUCTION]: CONST.SPOTNANA_TMC_ID, + [CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_SPOTNANA_TMC_ID, +}; + /** * Are we running the app in development? */ @@ -65,4 +72,8 @@ function getTravelDotEnvironmentURL(): Promise { return getEnvironment().then((environment) => TRAVELDOT_ENVIRONMENT_URLS[environment]); } -export {getEnvironment, isInternalTestBuild, isDevelopment, isProduction, getEnvironmentURL, getOldDotEnvironmentURL, getTravelDotEnvironmentURL}; +function getSpotnanaEnvironmentTMCID(): Promise { + return getEnvironment().then((environment) => SPOTNANA_ENVIRONMENT_TMC_ID[environment]); +} + +export {getEnvironment, isInternalTestBuild, isDevelopment, isProduction, getEnvironmentURL, getOldDotEnvironmentURL, getTravelDotEnvironmentURL, getSpotnanaEnvironmentTMCID}; From 938b60e91b486c0ef1d816ea66e8905ac5c595c8 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 10 Jun 2024 18:45:36 +0300 Subject: [PATCH 067/127] get tmc id as per env for spotnana --- src/libs/actions/Link.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 6104982da412..4bc0a61162ae 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -67,14 +67,13 @@ function openOldDotLink(url: string) { } function buildTravelDotURL(spotnanaToken?: string, postLoginPath?: string): Promise { - const authCode = spotnanaToken ? `authCode=${spotnanaToken}` : ''; - const tmcID = `tmcId=${CONST.SPOTNANA_TMC_ID}`; - const redirectURL = postLoginPath ? `redirectUrl=${postLoginPath}` : ''; + return Promise.all([Environment.getTravelDotEnvironmentURL(), Environment.getSpotnanaEnvironmentTMCID()]).then(([environmentURL, tmcID]) => { + const authCode = spotnanaToken ? `authCode=${spotnanaToken}` : ''; + const redirectURL = postLoginPath ? `redirectUrl=${postLoginPath}` : ''; + const tmcIDParam = `tmcId=${tmcID}`; - const paramsArray = [authCode, tmcID, redirectURL]; - const params = paramsArray.filter(Boolean).join('&'); - - return Environment.getTravelDotEnvironmentURL().then((environmentURL) => { + const paramsArray = [authCode, tmcIDParam, redirectURL]; + const params = paramsArray.filter(Boolean).join('&'); const travelDotDomain = Url.addTrailingForwardSlash(environmentURL); return `${travelDotDomain}auth/code?${params}`; }); From 89e850bc4ce9a5a9b3ce66fcbc7274ab5095496e Mon Sep 17 00:00:00 2001 From: dominictb Date: Tue, 11 Jun 2024 00:07:41 +0700 Subject: [PATCH 068/127] fix: fallback focus mechanism Signed-off-by: dominictb --- src/hooks/useHtmlPaste/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 645d6be076e5..cc8c47cbdcda 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -52,7 +52,7 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. // If shouldRefocusAfterPaste = false, we want to call `focus()` only as it won't change the focus state of the input since it is already focused - if (shouldRefocusAfterPaste) { + if (shouldRefocusAfterPaste || !textInputRef.current?.restoreSelectionPosition) { textInputRef.current?.blur(); textInputRef.current?.focus(); return; From 6895d38f71a8f46036762b57f05bd4ca84c95537 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 10 Jun 2024 20:32:29 +0300 Subject: [PATCH 069/127] add leading slash if not present to redirect url --- src/libs/Url.ts | 9 ++++++++- src/libs/actions/Link.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/libs/Url.ts b/src/libs/Url.ts index 69894147a242..970e6b3ed195 100644 --- a/src/libs/Url.ts +++ b/src/libs/Url.ts @@ -11,6 +11,13 @@ function addTrailingForwardSlash(url: string): string { return url; } +function addLeadingForwardSlash(url: string): string { + if (!url.startsWith('/')) { + return `/${url}`; + } + return url; +} + /** * Get path from URL string */ @@ -63,4 +70,4 @@ function hasURL(text: string) { return urlPattern.test(text); } -export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam, hasURL}; +export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam, hasURL, addLeadingForwardSlash}; diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 4bc0a61162ae..4b1261dc0872 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -69,7 +69,7 @@ function openOldDotLink(url: string) { function buildTravelDotURL(spotnanaToken?: string, postLoginPath?: string): Promise { return Promise.all([Environment.getTravelDotEnvironmentURL(), Environment.getSpotnanaEnvironmentTMCID()]).then(([environmentURL, tmcID]) => { const authCode = spotnanaToken ? `authCode=${spotnanaToken}` : ''; - const redirectURL = postLoginPath ? `redirectUrl=${postLoginPath}` : ''; + const redirectURL = postLoginPath ? `redirectUrl=${Url.addLeadingForwardSlash(postLoginPath)}` : ''; const tmcIDParam = `tmcId=${tmcID}`; const paramsArray = [authCode, tmcIDParam, redirectURL]; From ee2b19722c60cdfdf3cbe14287bc63a307a716d7 Mon Sep 17 00:00:00 2001 From: dominictb Date: Tue, 11 Jun 2024 10:02:49 +0700 Subject: [PATCH 070/127] fix: using dispatchEvent to avoid keyboard issue Signed-off-by: dominictb --- src/components/Composer/index.tsx | 2 +- src/hooks/useHtmlPaste/index.ts | 24 +++++++++++++++--------- src/hooks/useHtmlPaste/types.ts | 9 ++------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 69e2fd909010..14762b2d4bc1 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -251,7 +251,7 @@ function Composer( // eslint-disable-next-line react-hooks/exhaustive-deps }, [isComposerFullSize]); - useHtmlPaste(textInput, handlePaste, true, false); + useHtmlPaste(textInput, handlePaste, true); useEffect(() => { if (typeof ref === 'function') { diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index cc8c47cbdcda..8c70853543a1 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -29,7 +29,7 @@ const insertAtCaret = (target: HTMLElement, text: string) => { } }; -const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false, shouldRefocusAfterPaste = true) => { +const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false) => { const navigation = useNavigation(); /** @@ -51,14 +51,20 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi } // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. - // If shouldRefocusAfterPaste = false, we want to call `focus()` only as it won't change the focus state of the input since it is already focused - if (shouldRefocusAfterPaste || !textInputRef.current?.restoreSelectionPosition) { - textInputRef.current?.blur(); - textInputRef.current?.focus(); - return; - } - // just restore the selection position if the input is already focused - textInputRef.current?.restoreSelectionPosition?.(); + // to avoid the keyboard in mobile web if using blur() and focus() function, we just need to dispatch the event to trigger the onFocus handler + + textInputHTMLElement.dispatchEvent(new FocusEvent('focus', { + bubbles: true, + cancelable: true, + view: window + })) + + // need to trigger the focusin event to make sure the onFocus handler is triggered + textInputHTMLElement.dispatchEvent(new FocusEvent('focusin', { + bubbles: true, + cancelable: true, + view: window + })) // eslint-disable-next-line no-empty } catch (e) {} // We only need to set the callback once. diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index 10ad40d5e4c0..cdecf98135a0 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -1,15 +1,10 @@ import type {MutableRefObject} from 'react'; import type {TextInput} from 'react-native'; -type WithSelectionRestoreAbility = { - restoreSelectionPosition?: () => void; -}; - type UseHtmlPaste = ( - textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput & WithSelectionRestoreAbility) | (TextInput & WithSelectionRestoreAbility) | null>, + textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput) | TextInput | null>, preHtmlPasteCallback?: (event: ClipboardEvent) => boolean, - removeListenerOnScreenBlur?: boolean, - shouldRefocusAfterPaste?: boolean, + removeListenerOnScreenBlur?: boolean ) => void; export default UseHtmlPaste; From c1be42d49c9dc679e431511523b4b71b3ccaeacd Mon Sep 17 00:00:00 2001 From: dominictb Date: Tue, 11 Jun 2024 10:14:08 +0700 Subject: [PATCH 071/127] fix: lint Signed-off-by: dominictb --- src/hooks/useHtmlPaste/index.ts | 24 ++++++++++++++---------- src/hooks/useHtmlPaste/types.ts | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 8c70853543a1..87f684e58502 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -53,18 +53,22 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. // to avoid the keyboard in mobile web if using blur() and focus() function, we just need to dispatch the event to trigger the onFocus handler - textInputHTMLElement.dispatchEvent(new FocusEvent('focus', { - bubbles: true, - cancelable: true, - view: window - })) + textInputHTMLElement.dispatchEvent( + new FocusEvent('focus', { + bubbles: true, + cancelable: true, + view: window, + }), + ); // need to trigger the focusin event to make sure the onFocus handler is triggered - textInputHTMLElement.dispatchEvent(new FocusEvent('focusin', { - bubbles: true, - cancelable: true, - view: window - })) + textInputHTMLElement.dispatchEvent( + new FocusEvent('focusin', { + bubbles: true, + cancelable: true, + view: window, + }), + ); // eslint-disable-next-line no-empty } catch (e) {} // We only need to set the callback once. diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index cdecf98135a0..305ebe5fbd0f 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -4,7 +4,7 @@ import type {TextInput} from 'react-native'; type UseHtmlPaste = ( textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput) | TextInput | null>, preHtmlPasteCallback?: (event: ClipboardEvent) => boolean, - removeListenerOnScreenBlur?: boolean + removeListenerOnScreenBlur?: boolean, ) => void; export default UseHtmlPaste; From 0dee60889fedb8015e56122d3393521feb3e4706 Mon Sep 17 00:00:00 2001 From: dominictb Date: Tue, 11 Jun 2024 17:23:30 +0700 Subject: [PATCH 072/127] chore: upgrade rn-live-markdown 0.1.84 --- ios/Podfile.lock | 10 +++++----- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index aca46d6b18ed..b877c56a5581 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1852,7 +1852,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.83): + - RNLiveMarkdown (0.1.84): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1870,9 +1870,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.83) + - RNLiveMarkdown/common (= 0.1.84) - Yoga - - RNLiveMarkdown/common (0.1.83): + - RNLiveMarkdown/common (0.1.84): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2589,7 +2589,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 74b7b3d06d667ba0bbf41da7718f2607ae0dfe8f RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: 88030b7d9a31f5f6e67743df48ad952d64513b4a + RNLiveMarkdown: bf516c02a4549a059829a3fbb8f51c2e0a3110e7 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: df8fe93dbd251f25022f4023d31bc04160d4d65c RNPermissions: 0b61d30d21acbeafe25baaa47d9bae40a0c65216 @@ -2606,7 +2606,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 PODFILE CHECKSUM: 66a5c97ae1059e4da1993a4ad95abe5d819f555b diff --git a/package-lock.json b/package-lock.json index 66015a4955a1..7f00798e6d29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.83", + "@expensify/react-native-live-markdown": "0.1.84", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -3558,9 +3558,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.83", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.83.tgz", - "integrity": "sha512-xGn1P9FbFVueEF8BNKJJ4dQb0wPtsAvrrxND9pwVQT35ZL5cu1KZ4o6nzCqtesISPRB8Dw9Zx0ftIZy2uCQyzA==", + "version": "0.1.84", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.84.tgz", + "integrity": "sha512-gyRjmOozNlCCBKoQtEvohV+P4iR6VL4Z5QpuE3SXSE7J77WlCiaCMg5LjWFL8Q3Vn3ZApsQWhReLIXj3kfN9WA==", "workspaces": [ "parser", "example", diff --git a/package.json b/package.json index 697b9168dcd6..52b6b72f43ab 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.83", + "@expensify/react-native-live-markdown": "0.1.84", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", From 2e9d6229dd07d4a17be6decfd5cd2743c3534650 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 11 Jun 2024 17:50:20 +0300 Subject: [PATCH 073/127] Update src/languages/es.ts --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 3635723b5975..9c9cec64072f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1951,7 +1951,7 @@ export default { hotel: 'Hotel', car: 'Auto', viewTrip: 'Ver viaje', - viewTripDetails: 'View trip details', + viewTripDetails: 'Ver detalles del viaje', trip: 'Viaje', tripSummary: 'Resumen del viaje', departs: 'Sale', From cd0dce535c6ed391b62af24c142f96086da5501c Mon Sep 17 00:00:00 2001 From: dominictb Date: Wed, 12 Jun 2024 02:10:19 +0700 Subject: [PATCH 074/127] fix: check the existence of reportDraft avoid OpenReport API call if report draft exists Signed-off-by: dominictb --- src/pages/iou/request/step/withWritableReportOrNotFound.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx index ced2b28bb167..b840437d39fb 100644 --- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx +++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx @@ -54,7 +54,7 @@ export default function , keyof WithWritableReportOrNotFoundOnyxProps>> { // eslint-disable-next-line rulesdir/no-negated-variables function WithWritableReportOrNotFound(props: TProps, ref: ForwardedRef) { - const {report = {reportID: ''}, route, isLoadingApp = true} = props; + const {report = {reportID: ''}, route, isLoadingApp = true, reportDraft} = props; const iouTypeParamIsInvalid = !Object.values(CONST.IOU.TYPE) .filter((type) => shouldIncludeDeprecatedIOUType || (type !== CONST.IOU.TYPE.REQUEST && type !== CONST.IOU.TYPE.SEND)) .includes(route.params?.iouType); @@ -62,7 +62,7 @@ export default function { - if (!!report?.reportID || !route.params.reportID) { + if (!!report?.reportID || !route.params.reportID || !!reportDraft) { return; } From 82c6dd358c81481640d1a98d55dae53e3b250168 Mon Sep 17 00:00:00 2001 From: dominictb Date: Wed, 12 Jun 2024 09:16:59 +0700 Subject: [PATCH 075/127] chore: add onDismiss handler for TaxPicker Signed-off-by: dominictb Signed-off-by: dominictb --- src/components/TaxPicker.tsx | 19 ++++++++++++++++--- .../WorkspaceTaxesSettingsForeignCurrency.tsx | 5 +++++ ...orkspaceTaxesSettingsWorkspaceCurrency.tsx | 5 +++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/components/TaxPicker.tsx b/src/components/TaxPicker.tsx index fc29da30941d..c62b84911835 100644 --- a/src/components/TaxPicker.tsx +++ b/src/components/TaxPicker.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import type {EdgeInsets} from 'react-native-safe-area-context'; @@ -54,9 +54,11 @@ type TaxPickerProps = TaxPickerOnyxProps & { /** The type of IOU */ iouType?: ValueOf; + + onDismiss: () => void; }; -function TaxPicker({selectedTaxRate = '', policy, transaction, insets, onSubmit, action, splitDraftTransaction, iouType}: TaxPickerProps) { +function TaxPicker({selectedTaxRate = '', policy, transaction, insets, onSubmit, action, splitDraftTransaction, iouType, onDismiss}: TaxPickerProps) { const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); @@ -94,6 +96,17 @@ function TaxPicker({selectedTaxRate = '', policy, transaction, insets, onSubmit, const selectedOptionKey = useMemo(() => sections?.[0]?.data?.find((taxRate) => taxRate.searchText === selectedTaxRate)?.keyForList, [sections, selectedTaxRate]); + const handleSelectRow = useCallback( + (newSelectedOption: OptionsListUtils.TaxRatesOption) => { + if (selectedOptionKey === newSelectedOption.keyForList) { + onDismiss(); + return; + } + onSubmit(newSelectedOption); + }, + [onSubmit, onDismiss, selectedOptionKey], + ); + return ( { + Navigation.goBack(ROUTES.WORKSPACE_TAXES_SETTINGS.getRoute(policyID)); + }; + return ( diff --git a/src/pages/workspace/taxes/WorkspaceTaxesSettingsWorkspaceCurrency.tsx b/src/pages/workspace/taxes/WorkspaceTaxesSettingsWorkspaceCurrency.tsx index ebb479bee4b7..c46efe3fd243 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesSettingsWorkspaceCurrency.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesSettingsWorkspaceCurrency.tsx @@ -38,6 +38,10 @@ function WorkspaceTaxesSettingsWorkspaceCurrency({ Navigation.goBack(ROUTES.WORKSPACE_TAXES_SETTINGS.getRoute(policyID)); }; + const dismiss = () => { + Navigation.goBack(ROUTES.WORKSPACE_TAXES_SETTINGS.getRoute(policyID)); + }; + return ( From 443bc2b91f27c8982be6d32de6c205cb800ec4ab Mon Sep 17 00:00:00 2001 From: dominictb Date: Wed, 12 Jun 2024 09:28:40 +0700 Subject: [PATCH 076/127] fix: add dismiss func in IOURequestStepTaxRate Signed-off-by: dominictb --- src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx index a237b47777b2..94bd1ee4bc9d 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx @@ -113,6 +113,10 @@ function IOURequestStepTaxRatePage({ navigateBack(); }; + const dismiss = () => { + navigateBack(); + } + return ( ); From 52926172558e826a37eb7a12c5f590af0d47e990 Mon Sep 17 00:00:00 2001 From: dominictb Date: Wed, 12 Jun 2024 09:38:27 +0700 Subject: [PATCH 077/127] fix: prettier Signed-off-by: dominictb --- src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx index 94bd1ee4bc9d..af4d389f2733 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx @@ -115,7 +115,7 @@ function IOURequestStepTaxRatePage({ const dismiss = () => { navigateBack(); - } + }; return ( Date: Wed, 12 Jun 2024 13:17:31 +0700 Subject: [PATCH 078/127] chore: refine the target event Signed-off-by: dominictb --- src/hooks/useHtmlPaste/index.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 87f684e58502..53ea322610a6 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -53,20 +53,10 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. // to avoid the keyboard in mobile web if using blur() and focus() function, we just need to dispatch the event to trigger the onFocus handler - textInputHTMLElement.dispatchEvent( - new FocusEvent('focus', { - bubbles: true, - cancelable: true, - view: window, - }), - ); - - // need to trigger the focusin event to make sure the onFocus handler is triggered + // need to trigger the bubbled "focusin" event to make sure the onFocus handler is triggered textInputHTMLElement.dispatchEvent( new FocusEvent('focusin', { bubbles: true, - cancelable: true, - view: window, }), ); // eslint-disable-next-line no-empty From 32137bbec3e5a0c89e4803ca15368da71c3525bc Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 12 Jun 2024 12:41:45 +0530 Subject: [PATCH 079/127] fix: Distance rates - Order of distance rates is not preserved after clearing cache and restarting. Signed-off-by: Krishna Gupta --- .../distanceRates/PolicyDistanceRatesPage.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index e659750754a1..da5fbb777306 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -100,18 +100,20 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const distanceRatesList = useMemo( () => - Object.values(customUnitRates).map((value) => ({ - value: value.customUnitRateID ?? '', - text: `${CurrencyUtils.convertAmountToDisplayString(value.rate, value.currency ?? CONST.CURRENCY.USD)} / ${translate( - `common.${customUnit?.attributes?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}`, - )}`, - keyForList: value.customUnitRateID ?? '', - isSelected: selectedDistanceRates.find((rate) => rate.customUnitRateID === value.customUnitRateID) !== undefined, - isDisabled: value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - pendingAction: value.pendingAction ?? value.pendingFields?.rate ?? value.pendingFields?.enabled ?? value.pendingFields?.currency, - errors: value.errors ?? undefined, - rightElement: , - })), + Object.values(customUnitRates) + .sort((rateA, rateB) => (rateA?.rate ?? 0) - (rateB?.rate ?? 0)) + .map((value) => ({ + value: value.customUnitRateID ?? '', + text: `${CurrencyUtils.convertAmountToDisplayString(value.rate, value.currency ?? CONST.CURRENCY.USD)} / ${translate( + `common.${customUnit?.attributes?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}`, + )}`, + keyForList: value.customUnitRateID ?? '', + isSelected: selectedDistanceRates.find((rate) => rate.customUnitRateID === value.customUnitRateID) !== undefined, + isDisabled: value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + pendingAction: value.pendingAction ?? value.pendingFields?.rate ?? value.pendingFields?.enabled ?? value.pendingFields?.currency, + errors: value.errors ?? undefined, + rightElement: , + })), [customUnit?.attributes?.unit, customUnitRates, selectedDistanceRates, translate], ); From d90a9df272fad3d446fb117a1e18ef452ea3af86 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 12 Jun 2024 16:05:22 +0700 Subject: [PATCH 080/127] fix: add missing translation --- src/libs/PolicyDistanceRatesUtils.ts | 2 +- src/libs/SidebarUtils.ts | 3 ++- src/pages/ReimbursementAccount/AddressFormFields.tsx | 2 +- src/pages/iou/request/step/IOURequestStepWaypoint.tsx | 2 +- src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx | 2 +- src/pages/settings/Subscription/SubscriptionSize/utils.ts | 3 ++- src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx | 2 +- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts index 132d8a6682c6..7e2c0bb83320 100644 --- a/src/libs/PolicyDistanceRatesUtils.ts +++ b/src/libs/PolicyDistanceRatesUtils.ts @@ -31,7 +31,7 @@ function validateTaxClaimableValue(values: FormOnyxValues, r const errors: FormInputErrors = {}; if (rate.rate && Number(values.taxClaimableValue) > rate.rate / 100) { - errors.taxClaimableValue = 'workspace.taxes.error.updateTaxClaimableFailureMessage'; + errors.taxClaimableValue = Localize.translateLocal('workspace.taxes.error.updateTaxClaimableFailureMessage'); } return errors; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 119bbc3ba12a..43ca3b2b2f13 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -98,7 +98,8 @@ function getOrderedReportIDs( const isHidden = report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; - const hasErrorsOtherThanFailedReceipt = doesReportHaveViolations || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); + const hasErrorsOtherThanFailedReceipt = + doesReportHaveViolations || Object.values(allReportErrors).some((error) => error?.[0] !== Localize.translateLocal('iou.error.genericSmartscanFailureMessage')); const isSystemChat = ReportUtils.isSystemChat(report); const shouldOverrideHidden = hasErrorsOtherThanFailedReceipt || isFocused || isSystemChat || report.isPinned; if (isHidden && !shouldOverrideHidden) { diff --git a/src/pages/ReimbursementAccount/AddressFormFields.tsx b/src/pages/ReimbursementAccount/AddressFormFields.tsx index c586c71f3d47..a863d3cc5952 100644 --- a/src/pages/ReimbursementAccount/AddressFormFields.tsx +++ b/src/pages/ReimbursementAccount/AddressFormFields.tsx @@ -80,7 +80,7 @@ function AddressFormFields({shouldSaveDraft = false, defaultValues, values, erro value={values?.state as State} defaultValue={defaultValues?.state} onInputChange={(value) => onFieldChange?.({state: value})} - errorText={errors?.state ? 'bankAccount.error.addressState' : ''} + errorText={errors?.state ? translate('bankAccount.error.addressState') : ''} /> { textInput.current = e as unknown as TextInput; }} - hint={!isOffline ? 'distance.error.selectSuggestedAddress' : ''} + hint={!isOffline ? translate('distance.error.selectSuggestedAddress') : ''} containerStyles={[styles.mt4]} label={translate('distance.address')} defaultValue={waypointAddress} diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx index c5e725450264..99e9c910cbdf 100644 --- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx @@ -65,7 +65,7 @@ function LegalNamePage({privatePersonalDetails, isLoadingApp = true}: LegalNameP if (!ValidationUtils.isValidLegalName(values.legalLastName)) { ErrorUtils.addErrorMessage(errors, 'legalLastName', translate('privatePersonalDetails.error.hasInvalidCharacter')); } else if (!values.legalLastName) { - errors.legalLastName = 'common.error.fieldRequired'; + errors.legalLastName = translate('common.error.fieldRequired'); } else if (values.legalLastName.length > CONST.LEGAL_NAME.MAX_LENGTH) { ErrorUtils.addErrorMessage( errors, diff --git a/src/pages/settings/Subscription/SubscriptionSize/utils.ts b/src/pages/settings/Subscription/SubscriptionSize/utils.ts index 4b0395d6cab2..30e9a685540d 100644 --- a/src/pages/settings/Subscription/SubscriptionSize/utils.ts +++ b/src/pages/settings/Subscription/SubscriptionSize/utils.ts @@ -1,5 +1,6 @@ import {addMonths, format, startOfMonth} from 'date-fns'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import * as Localize from '@libs/Localize'; import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import type ONYXKEYS from '@src/ONYXKEYS'; @@ -8,7 +9,7 @@ import INPUT_IDS from '@src/types/form/SubscriptionSizeForm'; const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.SUBSCRIPTION_SIZE]); if (values[INPUT_IDS.SUBSCRIPTION_SIZE] && !ValidationUtils.isValidSubscriptionSize(values[INPUT_IDS.SUBSCRIPTION_SIZE])) { - errors.subscriptionSize = 'subscription.subscriptionSize.error.size'; + errors.subscriptionSize = Localize.translateLocal('subscription.subscriptionSize.error.size'); } return errors; diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx index 7aaa5a03576b..523c89874f9c 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -303,7 +303,7 @@ function BaseValidateCodeForm({account, credentials, session, autoComplete, isUs onChangeText={(text) => onTextInput(text, 'recoveryCode')} maxLength={CONST.FORM_CHARACTER_LIMIT} label={translate('recoveryCodeForm.recoveryCode')} - errorText={formError?.recoveryCode ?? ''} + errorText={formError?.recoveryCode ? translate(formError?.recoveryCode) : ''} hasError={hasError} onSubmitEditing={validateAndSubmitForm} autoFocus From de03632725cc01c8bccf93e4d6063947da1574d5 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 12 Jun 2024 17:25:33 +0800 Subject: [PATCH 081/127] always open to external link --- src/pages/ReimbursementAccount/BankAccountStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/BankAccountStep.tsx b/src/pages/ReimbursementAccount/BankAccountStep.tsx index 71de41be9ff1..c9665d908b58 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.tsx +++ b/src/pages/ReimbursementAccount/BankAccountStep.tsx @@ -135,7 +135,7 @@ function BankAccountStep({ {!!plaidDesktopMessage && ( - {translate(plaidDesktopMessage)} + Link.openExternalLink(bankAccountRoute)}>{translate(plaidDesktopMessage)} )}