From 6b6afd8326c14044681838a44c5e4b929ef77e26 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 15 Nov 2023 15:06:15 +0100 Subject: [PATCH 001/556] [TS migration] Migrate 'Onfido' component --- .../{BaseOnfidoWeb.js => BaseOnfidoWeb.tsx} | 50 ++++++++++--------- src/components/Onfido/index.desktop.js | 11 ---- .../{index.native.js => index.native.tsx} | 16 +++--- .../Onfido/{index.website.js => index.tsx} | 11 ++-- src/components/Onfido/onfidoPropTypes.js | 15 ------ src/components/Onfido/types.ts | 20 ++++++++ 6 files changed, 58 insertions(+), 65 deletions(-) rename src/components/Onfido/{BaseOnfidoWeb.js => BaseOnfidoWeb.tsx} (81%) delete mode 100644 src/components/Onfido/index.desktop.js rename src/components/Onfido/{index.native.js => index.native.tsx} (79%) rename src/components/Onfido/{index.website.js => index.tsx} (64%) delete mode 100644 src/components/Onfido/onfidoPropTypes.js create mode 100644 src/components/Onfido/types.ts diff --git a/src/components/Onfido/BaseOnfidoWeb.js b/src/components/Onfido/BaseOnfidoWeb.tsx similarity index 81% rename from src/components/Onfido/BaseOnfidoWeb.js rename to src/components/Onfido/BaseOnfidoWeb.tsx index 5c0f83902e55..79842823a975 100644 --- a/src/components/Onfido/BaseOnfidoWeb.js +++ b/src/components/Onfido/BaseOnfidoWeb.tsx @@ -1,7 +1,6 @@ -import lodashGet from 'lodash/get'; import * as OnfidoSDK from 'onfido-sdk-ui'; -import React, {forwardRef, useEffect} from 'react'; -import _ from 'underscore'; +import React, {ForwardedRef, forwardRef, useEffect} from 'react'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; import useLocalize from '@hooks/useLocalize'; import Log from '@libs/Log'; import fontFamily from '@styles/fontFamily'; @@ -10,9 +9,15 @@ import themeColors from '@styles/themes/default'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import './index.css'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoElement, OnfidoProps} from './types'; -function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate}) { +type LocaleProps = Pick; + +type OnfidoEvent = Event & { + detail?: Record; +}; + +function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate}: OnfidoProps & LocaleProps) { OnfidoSDK.init({ token: sdkToken, containerId: CONST.ONFIDO.CONTAINER_ID, @@ -22,7 +27,7 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo fontFamilySubtitle: `${fontFamily.EXP_NEUE}, -apple-system, serif`, fontFamilyBody: `${fontFamily.EXP_NEUE}, -apple-system, serif`, fontSizeTitle: `${variables.fontSizeLarge}px`, - fontWeightTitle: fontWeightBold, + fontWeightTitle: Number(fontWeightBold), fontWeightSubtitle: 400, fontSizeSubtitle: `${variables.fontSizeNormal}px`, colorContentTitle: themeColors.text, @@ -47,7 +52,6 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo colorBorderLinkUnderline: themeColors.link, colorBackgroundLinkHover: themeColors.link, colorBackgroundLinkActive: themeColors.link, - authAccentColor: themeColors.link, colorBackgroundInfoPill: themeColors.link, colorBackgroundSelector: themeColors.appBG, colorBackgroundDocTypeButton: themeColors.success, @@ -59,11 +63,10 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo { type: CONST.ONFIDO.TYPE.DOCUMENT, options: { - useLiveDocumentCapture: true, forceCrossDevice: true, hideCountrySelection: true, - country: 'USA', documentTypes: { + // eslint-disable-next-line @typescript-eslint/naming-convention driving_licence: { country: 'USA', }, @@ -78,17 +81,15 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, }, ], - smsNumberCountryCode: CONST.ONFIDO.SMS_NUMBER_COUNTRY_CODE.US, - showCountrySelection: false, onComplete: (data) => { - if (_.isEmpty(data)) { + if (!Object.keys(data).length) { Log.warn('Onfido completed with no data'); } onSuccess(data); }, onError: (error) => { - const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR); - const errorType = lodashGet(error, 'type'); + const errorMessage = error.message ?? CONST.ERROR.UNKNOWN_ERROR; + const errorType = error.type; Log.hmmm('Onfido error', {errorType, errorMessage}); onError(errorMessage); }, @@ -101,32 +102,33 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, language: { // We need to use ES_ES as locale key because the key `ES` is not a valid config key for Onfido - locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : preferredLocale, + locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : (preferredLocale as OnfidoSDK.SupportedLanguages), // Provide a custom phrase for the back button so that the first letter is capitalized, // and translate the phrase while we're at it. See the issue and documentation for more context. // https://github.com/Expensify/App/issues/17244 // https://documentation.onfido.com/sdk/web/#custom-languages phrases: { + // eslint-disable-next-line @typescript-eslint/naming-convention 'generic.back': translate('common.back'), }, }, }); } -function logOnFidoEvent(event) { +function logOnFidoEvent(event: OnfidoEvent) { Log.hmmm('Receiving Onfido analytic event', event.detail); } -const Onfido = forwardRef((props, ref) => { +function Onfido({sdkToken, onSuccess, onError, onUserExit}: OnfidoProps, ref: ForwardedRef) { const {preferredLocale, translate} = useLocalize(); useEffect(() => { initializeOnfido({ - sdkToken: props.sdkToken, - onSuccess: props.onSuccess, - onError: props.onError, - onUserExit: props.onUserExit, + sdkToken, + onSuccess, + onError, + onUserExit, preferredLocale, translate, }); @@ -143,8 +145,8 @@ const Onfido = forwardRef((props, ref) => { ref={ref} /> ); -}); +} Onfido.displayName = 'Onfido'; -Onfido.propTypes = onfidoPropTypes; -export default Onfido; + +export default forwardRef(Onfido); diff --git a/src/components/Onfido/index.desktop.js b/src/components/Onfido/index.desktop.js deleted file mode 100644 index e455eaf78d32..000000000000 --- a/src/components/Onfido/index.desktop.js +++ /dev/null @@ -1,11 +0,0 @@ -import BaseOnfidoWeb from './BaseOnfidoWeb'; -import onfidoPropTypes from './onfidoPropTypes'; - -// On desktop, we do not want to teardown onfido, because it causes a crash. -// See https://github.com/Expensify/App/issues/6082 -const Onfido = BaseOnfidoWeb; - -Onfido.propTypes = onfidoPropTypes; -Onfido.displayName = 'Onfido'; - -export default Onfido; diff --git a/src/components/Onfido/index.native.js b/src/components/Onfido/index.native.tsx similarity index 79% rename from src/components/Onfido/index.native.js rename to src/components/Onfido/index.native.tsx index ed0578187d3c..e09eeec4f322 100644 --- a/src/components/Onfido/index.native.js +++ b/src/components/Onfido/index.native.tsx @@ -1,15 +1,13 @@ import {OnfidoCaptureType, OnfidoCountryCode, OnfidoDocumentType, Onfido as OnfidoSDK} from '@onfido/react-native-sdk'; -import lodashGet from 'lodash/get'; import React, {useEffect} from 'react'; import {Alert, Linking} from 'react-native'; -import _ from 'underscore'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useLocalize from '@hooks/useLocalize'; import Log from '@libs/Log'; import CONST from '@src/CONST'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoProps} from './types'; -function Onfido({sdkToken, onUserExit, onSuccess, onError}) { +function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { const {translate} = useLocalize(); useEffect(() => { @@ -28,19 +26,20 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}) { }) .then(onSuccess) .catch((error) => { - const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR); - const errorType = lodashGet(error, 'type'); + const errorMessage = error.message ?? CONST.ERROR.UNKNOWN_ERROR; + const errorType = error.type; + Log.hmmm('Onfido error on native', {errorType, errorMessage}); // If the user cancels the Onfido flow we won't log this error as it's normal. In the React Native SDK the user exiting the flow will trigger this error which we can use as // our "user exited the flow" callback. On web, this event has it's own callback passed as a config so we don't need to bother with this there. - if (_.contains([CONST.ONFIDO.ERROR.USER_CANCELLED, CONST.ONFIDO.ERROR.USER_TAPPED_BACK, CONST.ONFIDO.ERROR.USER_EXITED], errorMessage)) { + if ([CONST.ONFIDO.ERROR.USER_CANCELLED, CONST.ONFIDO.ERROR.USER_TAPPED_BACK, CONST.ONFIDO.ERROR.USER_EXITED].includes(errorMessage)) { onUserExit(); return; } // Handle user camera permission on iOS and Android - if (_.contains([CONST.ONFIDO.ERROR.USER_CAMERA_PERMISSION, CONST.ONFIDO.ERROR.USER_CAMERA_DENINED, CONST.ONFIDO.ERROR.USER_CAMERA_CONSENT_DENIED], errorMessage)) { + if ([CONST.ONFIDO.ERROR.USER_CAMERA_PERMISSION, CONST.ONFIDO.ERROR.USER_CAMERA_DENINED, CONST.ONFIDO.ERROR.USER_CAMERA_CONSENT_DENIED].includes(errorMessage)) { Alert.alert( translate('onfidoStep.cameraPermissionsNotGranted'), translate('onfidoStep.cameraRequestMessage'), @@ -71,7 +70,6 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}) { return ; } -Onfido.propTypes = onfidoPropTypes; Onfido.displayName = 'Onfido'; export default Onfido; diff --git a/src/components/Onfido/index.website.js b/src/components/Onfido/index.tsx similarity index 64% rename from src/components/Onfido/index.website.js rename to src/components/Onfido/index.tsx index 12ad1edd8fb9..139dc3cec405 100644 --- a/src/components/Onfido/index.website.js +++ b/src/components/Onfido/index.tsx @@ -1,14 +1,14 @@ -import lodashGet from 'lodash/get'; import React, {useEffect, useRef} from 'react'; import BaseOnfidoWeb from './BaseOnfidoWeb'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoElement, OnfidoProps} from './types'; -function Onfido({sdkToken, onSuccess, onError, onUserExit}) { - const baseOnfidoRef = useRef(null); +function Onfido({sdkToken, onSuccess, onError, onUserExit}: OnfidoProps) { + const baseOnfidoRef = useRef(null); useEffect( () => () => { - const onfidoOut = lodashGet(baseOnfidoRef.current, 'onfidoOut'); + const onfidoOut = baseOnfidoRef.current?.onfidoOut; + if (!onfidoOut) { return; } @@ -29,7 +29,6 @@ function Onfido({sdkToken, onSuccess, onError, onUserExit}) { ); } -Onfido.propTypes = onfidoPropTypes; Onfido.displayName = 'Onfido'; export default Onfido; diff --git a/src/components/Onfido/onfidoPropTypes.js b/src/components/Onfido/onfidoPropTypes.js deleted file mode 100644 index ff0023c70058..000000000000 --- a/src/components/Onfido/onfidoPropTypes.js +++ /dev/null @@ -1,15 +0,0 @@ -import PropTypes from 'prop-types'; - -export default { - /** Token used to initialize the Onfido SDK */ - sdkToken: PropTypes.string.isRequired, - - /** Called when the user intentionally exits the flow without completing it */ - onUserExit: PropTypes.func.isRequired, - - /** Called when the user is totally done with Onfido */ - onSuccess: PropTypes.func.isRequired, - - /** Called when Onfido throws an error */ - onError: PropTypes.func.isRequired, -}; diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts new file mode 100644 index 000000000000..a4fe3d93f05e --- /dev/null +++ b/src/components/Onfido/types.ts @@ -0,0 +1,20 @@ +import {OnfidoError, OnfidoResult} from '@onfido/react-native-sdk'; +import * as OnfidoSDK from 'onfido-sdk-ui'; + +type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; + +type OnfidoProps = { + /** Token used to initialize the Onfido SDK */ + sdkToken: string; + + /** Called when the user intentionally exits the flow without completing it */ + onUserExit: (userExitCode?: OnfidoSDK.UserExitCode) => void; + + /** Called when the user is totally done with Onfido */ + onSuccess: (data: OnfidoSDK.SdkResponse | OnfidoResult | OnfidoError) => void; + + /** Called when Onfido throws an error */ + onError: (error?: string) => void; +}; + +export type {OnfidoProps, OnfidoElement}; From e643deb8b949db852e8aadc34c1fdd918cb9b095 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 31 Jan 2024 03:29:04 +0200 Subject: [PATCH 002/556] Migrate 'SettingsProfile' page to TypeScript --- src/ROUTES.ts | 2 +- src/components/Form/FormProvider.tsx | 6 +- src/components/Form/types.ts | 6 +- src/components/MenuItem.tsx | 2 +- src/libs/UserUtils.ts | 4 +- src/libs/ValidationUtils.ts | 2 +- .../{StatusPage.js => StatusPage.tsx} | 73 +++---- ...DisplayNamePage.js => DisplayNamePage.tsx} | 76 +++---- ...ungeAccessPage.js => LoungeAccessPage.tsx} | 36 ++-- src/pages/settings/Profile/ProfilePage.js | 186 ------------------ src/pages/settings/Profile/ProfilePage.tsx | 158 +++++++++++++++ ...InitialPage.js => TimezoneInitialPage.tsx} | 45 ++--- ...neSelectPage.js => TimezoneSelectPage.tsx} | 75 +++---- src/types/onyx/PersonalDetails.ts | 2 +- 14 files changed, 295 insertions(+), 378 deletions(-) rename src/pages/settings/Profile/CustomStatus/{StatusPage.js => StatusPage.tsx} (77%) rename src/pages/settings/Profile/{DisplayNamePage.js => DisplayNamePage.tsx} (65%) rename src/pages/settings/Profile/{LoungeAccessPage.js => LoungeAccessPage.tsx} (58%) delete mode 100755 src/pages/settings/Profile/ProfilePage.js create mode 100755 src/pages/settings/Profile/ProfilePage.tsx rename src/pages/settings/Profile/{TimezoneInitialPage.js => TimezoneInitialPage.tsx} (55%) rename src/pages/settings/Profile/{TimezoneSelectPage.js => TimezoneSelectPage.tsx} (52%) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9c4375b84ab6..4d77c8885871 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -31,7 +31,7 @@ const ROUTES = { }, PROFILE_AVATAR: { route: 'a/:accountID/avatar', - getRoute: (accountID: string) => `a/${accountID}/avatar` as const, + getRoute: (accountID: string | number) => `a/${accountID}/avatar` as const, }, TRANSITION_BETWEEN_APPS: 'transition', diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 424fd989291a..a9d20e31c286 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -15,7 +15,7 @@ import type {Errors} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import FormContext from './FormContext'; import FormWrapper from './FormWrapper'; -import type {BaseInputProps, FormProps, InputRefs, OnyxFormKeyWithoutDraft, OnyxFormValues, OnyxFormValuesFields, RegisterInput, ValueTypeKey} from './types'; +import type {BaseInputProps, FormProps, FormRef, InputRefs, OnyxFormKeyWithoutDraft, OnyxFormValues, OnyxFormValuesFields, RegisterInput, ValueTypeKey} from './types'; // In order to prevent Checkbox focus loss when the user are focusing a TextInput and proceeds to toggle a CheckBox in web and mobile web. // 200ms delay was chosen as a result of empirical testing. @@ -63,10 +63,6 @@ type FormProviderProps = FormProvider shouldValidateOnChange?: boolean; }; -type FormRef = { - resetForm: (optionalValue: OnyxFormValues) => void; -}; - function FormProvider( { formID, diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 447f3205ad68..580e15a5d4d6 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -86,8 +86,12 @@ type FormProps = { footerContent?: ReactNode; }; +type FormRef = { + resetForm: (optionalValue: OnyxFormValues) => void; +}; + type RegisterInput = (inputID: keyof Form, inputProps: TInputProps) => TInputProps; type InputRefs = Record>; -export type {InputWrapperProps, FormProps, RegisterInput, ValidInputs, BaseInputProps, ValueTypeKey, OnyxFormValues, OnyxFormValuesFields, InputRefs, OnyxFormKeyWithoutDraft}; +export type {InputWrapperProps, FormRef, FormProps, RegisterInput, ValidInputs, BaseInputProps, ValueTypeKey, OnyxFormValues, OnyxFormValuesFields, InputRefs, OnyxFormKeyWithoutDraft}; diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 334fa9895205..1a6398c95afb 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -189,7 +189,7 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { isSmallAvatarSubscriptMenu?: boolean; /** The type of brick road indicator to show. */ - brickRoadIndicator?: ValueOf; + brickRoadIndicator?: ValueOf | '' | null; /** Should render the content in HTML format */ shouldRenderAsHTML?: boolean; diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index 6ec386679a32..3d34d570d2df 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -16,7 +16,7 @@ type AvatarRange = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | type AvatarSource = IconAsset | string; -type LoginListIndicator = ValueOf | ''; +type LoginListIndicator = ValueOf | undefined; let allPersonalDetails: OnyxEntry; Onyx.connect({ @@ -69,7 +69,7 @@ function getLoginListBrickRoadIndicator(loginList: Record): Login if (hasLoginListInfo(loginList)) { return CONST.BRICK_ROAD_INDICATOR_STATUS.INFO; } - return ''; + return undefined; } /** diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index 7eff51c354df..305131d7731a 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -338,7 +338,7 @@ function isValidPersonName(value: string) { /** * Checks if the provided string includes any of the provided reserved words */ -function doesContainReservedWord(value: string, reservedWords: string[]): boolean { +function doesContainReservedWord(value: string, reservedWords: readonly string[]): boolean { const valueToCheck = value.trim().toLowerCase(); return reservedWords.some((reservedWord) => valueToCheck.includes(reservedWord.toLowerCase())); } diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx similarity index 77% rename from src/pages/settings/Profile/CustomStatus/StatusPage.js rename to src/pages/settings/Profile/CustomStatus/StatusPage.tsx index 0183e0cc8a2d..9e9d34f8ae30 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -1,10 +1,12 @@ -import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {ValueOf} from 'type-fest'; import EmojiPickerButtonDropdown from '@components/EmojiPicker/EmojiPickerButtonDropdown'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormRef, OnyxFormValuesFields} from '@components/Form/types'; import HeaderPageLayout from '@components/HeaderPageLayout'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -13,13 +15,13 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as User from '@userActions/User'; @@ -27,43 +29,45 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; +import type {Status} from '@src/types/onyx/PersonalDetails'; const INPUT_IDS = { EMOJI_CODE: 'emojiCode', STATUS_TEXT: 'statusText', +} as const; +type StatusPageOnyxProps = { + draftStatus: OnyxEntry; }; -const propTypes = { - ...withCurrentUserPersonalDetailsPropTypes, -}; +type StatusPageProps = StatusPageOnyxProps & WithCurrentUserPersonalDetailsProps; const initialEmoji = '💬'; -function StatusPage({draftStatus, currentUserPersonalDetails}) { +function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const formRef = useRef(null); - const [brickRoadIndicator, setBrickRoadIndicator] = useState(''); - const currentUserEmojiCode = lodashGet(currentUserPersonalDetails, 'status.emojiCode', ''); - const currentUserStatusText = lodashGet(currentUserPersonalDetails, 'status.text', ''); - const currentUserClearAfter = lodashGet(currentUserPersonalDetails, 'status.clearAfter', ''); - const draftEmojiCode = lodashGet(draftStatus, 'emojiCode'); - const draftText = lodashGet(draftStatus, 'text'); - const draftClearAfter = lodashGet(draftStatus, 'clearAfter'); - - const defaultEmoji = draftEmojiCode || currentUserEmojiCode; - const defaultText = draftText || currentUserStatusText; + const formRef = useRef(null); + const [brickRoadIndicator, setBrickRoadIndicator] = useState | '' | null>(''); + const currentUserEmojiCode = currentUserPersonalDetails?.status?.emojiCode ?? ''; + const currentUserStatusText = currentUserPersonalDetails?.status?.text ?? ''; + const currentUserClearAfter = currentUserPersonalDetails?.status?.clearAfter ?? ''; + const draftEmojiCode = draftStatus?.emojiCode; + const draftText = draftStatus?.text; + const draftClearAfter = draftStatus?.clearAfter; + + const defaultEmoji = draftEmojiCode ?? currentUserEmojiCode; + const defaultText = draftText ?? currentUserStatusText; const customClearAfter = useMemo(() => { - const dataToShow = draftClearAfter || currentUserClearAfter; + const dataToShow = draftClearAfter ?? currentUserClearAfter; return DateUtils.getLocalizedTimePeriodDescription(dataToShow); }, [draftClearAfter, currentUserClearAfter]); const isValidClearAfterDate = useCallback(() => { - const clearAfterTime = draftClearAfter || currentUserClearAfter; - if (clearAfterTime === CONST.CUSTOM_STATUS_TYPES.NEVER || clearAfterTime === '') { + const clearAfterTime = draftClearAfter ?? currentUserClearAfter; + if (clearAfterTime === CONST.CUSTOM_STATUS_TYPES.NEVER ?? clearAfterTime === '') { return true; } @@ -72,15 +76,16 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { const navigateBackToPreviousScreen = useCallback(() => Navigation.goBack(ROUTES.SETTINGS_PROFILE, false, true), []); const updateStatus = useCallback( - ({emojiCode, statusText}) => { - const clearAfterTime = draftClearAfter || currentUserClearAfter || CONST.CUSTOM_STATUS_TYPES.NEVER; + (values: OnyxFormValuesFields) => { + const {emojiCode, statusText} = values; + const clearAfterTime = draftClearAfter ?? currentUserClearAfter ?? CONST.CUSTOM_STATUS_TYPES.NEVER; const isValid = DateUtils.isTimeAtLeastOneMinuteInFuture({dateTimeString: clearAfterTime}); if (!isValid && clearAfterTime !== CONST.CUSTOM_STATUS_TYPES.NEVER) { setBrickRoadIndicator(isValidClearAfterDate() ? null : CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR); return; } User.updateCustomStatus({ - text: statusText, + text: values.statusText, emojiCode: !emojiCode && statusText ? initialEmoji : emojiCode, clearAfter: clearAfterTime !== CONST.CUSTOM_STATUS_TYPES.NEVER ? clearAfterTime : '', }); @@ -100,7 +105,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { emojiCode: '', clearAfter: DateUtils.getEndOfToday(), }); - formRef.current.resetForm({[INPUT_IDS.EMOJI_CODE]: ''}); + formRef.current?.resetForm?.({[INPUT_IDS.EMOJI_CODE]: ''}); InteractionManager.runAfterInteractions(() => { navigateBackToPreviousScreen(); }); @@ -110,9 +115,9 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { useEffect(() => { if (!currentUserEmojiCode && !currentUserClearAfter && !draftClearAfter) { - User.updateDraftCustomStatus({clearAfter: DateUtils.getEndOfToday()}); + User.updateDraftCustomStatus({clearAfter: DateUtils.getEndOfToday()} as Status); } else { - User.updateDraftCustomStatus({clearAfter: currentUserClearAfter}); + User.updateDraftCustomStatus({clearAfter: currentUserClearAfter} as Status); } return () => User.clearDraftCustomStatus(); @@ -164,7 +169,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { /> - {(!!currentUserEmojiCode || !!currentUserStatusText) && ( + {(!!currentUserEmojiCode ?? !!currentUserStatusText) && ( ({ draftStatus: { key: () => ONYXKEYS.CUSTOM_STATUS_DRAFT, }, - }), -)(StatusPage); + })(StatusPage), +); diff --git a/src/pages/settings/Profile/DisplayNamePage.js b/src/pages/settings/Profile/DisplayNamePage.tsx similarity index 65% rename from src/pages/settings/Profile/DisplayNamePage.js rename to src/pages/settings/Profile/DisplayNamePage.tsx index 3269fc401c01..0906ca90133d 100644 --- a/src/pages/settings/Profile/DisplayNamePage.js +++ b/src/pages/settings/Profile/DisplayNamePage.tsx @@ -1,19 +1,19 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {OnyxFormValuesFields} from '@components/Form/types'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -21,40 +21,29 @@ import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; -const propTypes = { - ...withLocalizePropTypes, - ...withCurrentUserPersonalDetailsPropTypes, - isLoadingApp: PropTypes.bool, +type DisplayNamePageOnyxProps = { + isLoadingApp: OnyxEntry; }; -const defaultProps = { - ...withCurrentUserPersonalDetailsDefaultProps, - isLoadingApp: true, -}; +type DisplayNamePageProps = DisplayNamePageOnyxProps & WithCurrentUserPersonalDetailsProps; /** * Submit form to update user's first and last name (and display name) - * @param {Object} values - * @param {String} values.firstName - * @param {String} values.lastName */ -const updateDisplayName = (values) => { +const updateDisplayName = (values: OnyxFormValuesFields) => { PersonalDetails.updateDisplayName(values.firstName.trim(), values.lastName.trim()); }; -function DisplayNamePage(props) { +function DisplayNamePage({isLoadingApp = true, currentUserPersonalDetails}: DisplayNamePageProps) { const styles = useThemeStyles(); - const currentUserDetails = props.currentUserPersonalDetails || {}; + const {translate} = useLocalize(); + + const currentUserDetails = currentUserPersonalDetails ?? {}; - /** - * @param {Object} values - * @param {String} values.firstName - * @param {String} values.lastName - * @returns {Object} - An object containing the errors for each inputID - */ - const validate = (values) => { - const errors = {}; + const validate = (values: OnyxFormValuesFields) => { + const errors: Errors = {}; // First we validate the first name field if (!ValidationUtils.isValidDisplayName(values.firstName)) { @@ -73,7 +62,6 @@ function DisplayNamePage(props) { } return errors; }; - return ( Navigation.goBack(ROUTES.SETTINGS_PROFILE)} /> - {props.isLoadingApp ? ( + {isLoadingApp ? ( ) : ( - {props.translate('displayNamePage.isShownOnProfile')} + {translate('displayNamePage.isShownOnProfile')} @@ -116,10 +104,10 @@ function DisplayNamePage(props) { InputComponent={TextInput} inputID="lastName" name="lname" - label={props.translate('common.lastName')} - aria-label={props.translate('common.lastName')} + label={translate('common.lastName')} + aria-label={translate('common.lastName')} role={CONST.ROLE.PRESENTATION} - defaultValue={lodashGet(currentUserDetails, 'lastName', '')} + defaultValue={currentUserDetails?.lastName ?? ''} maxLength={CONST.DISPLAY_NAME.MAX_LENGTH} spellCheck={false} /> @@ -130,16 +118,12 @@ function DisplayNamePage(props) { ); } -DisplayNamePage.propTypes = propTypes; -DisplayNamePage.defaultProps = defaultProps; DisplayNamePage.displayName = 'DisplayNamePage'; -export default compose( - withLocalize, - withCurrentUserPersonalDetails, - withOnyx({ +export default withCurrentUserPersonalDetails( + withOnyx({ isLoadingApp: { key: ONYXKEYS.IS_LOADING_APP, }, - }), -)(DisplayNamePage); + })(DisplayNamePage), +); diff --git a/src/pages/settings/Profile/LoungeAccessPage.js b/src/pages/settings/Profile/LoungeAccessPage.tsx similarity index 58% rename from src/pages/settings/Profile/LoungeAccessPage.js rename to src/pages/settings/Profile/LoungeAccessPage.tsx index 60cb0896a4eb..7edc3c2956f6 100644 --- a/src/pages/settings/Profile/LoungeAccessPage.js +++ b/src/pages/settings/Profile/LoungeAccessPage.tsx @@ -1,35 +1,30 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; import IllustratedHeaderPageLayout from '@components/IllustratedHeaderPageLayout'; import LottieAnimations from '@components/LottieAnimations'; import Text from '@components/Text'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; -import userPropTypes from '@pages/settings/userPropTypes'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {User} from '@src/types/onyx'; -const propTypes = { - /** Current user details, which will hold whether or not they have Lounge Access */ - user: userPropTypes, - - ...withCurrentUserPersonalDetailsPropTypes, +type LoungeAccessPageOnyxProps = { + user: OnyxEntry; }; -const defaultProps = { - user: {}, - ...withCurrentUserPersonalDetailsDefaultProps, -}; +type LoungeAccessPageProps = LoungeAccessPageOnyxProps & WithCurrentUserPersonalDetailsProps; -function LoungeAccessPage(props) { +function LoungeAccessPage({user}: LoungeAccessPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - if (!props.user.hasLoungeAccess) { + if (!user?.hasLoungeAccess) { return ; } @@ -45,20 +40,17 @@ function LoungeAccessPage(props) { > {translate('loungeAccessPage.headline')} - {translate('loungeAccessPage.description')} + {translate('loungeAccessPage.description')} ); } -LoungeAccessPage.propTypes = propTypes; -LoungeAccessPage.defaultProps = defaultProps; LoungeAccessPage.displayName = 'LoungeAccessPage'; -export default compose( - withCurrentUserPersonalDetails, - withOnyx({ +export default withCurrentUserPersonalDetails( + withOnyx({ user: { key: ONYXKEYS.USER, }, - }), -)(LoungeAccessPage); + })(LoungeAccessPage), +); diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js deleted file mode 100755 index 99cc5cf7e35a..000000000000 --- a/src/pages/settings/Profile/ProfilePage.js +++ /dev/null @@ -1,186 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useEffect} from 'react'; -import {ScrollView, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import * as Expensicons from '@components/Icon/Expensicons'; -import MenuItem from '@components/MenuItem'; -import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import ScreenWrapper from '@components/ScreenWrapper'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import Navigation from '@libs/Navigation/Navigation'; -import * as UserUtils from '@libs/UserUtils'; -import userPropTypes from '@pages/settings/userPropTypes'; -import * as App from '@userActions/App'; -import * as PersonalDetails from '@userActions/PersonalDetails'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; - -const propTypes = { - /* Onyx Props */ - - /** Login list for the user that is signed in */ - loginList: PropTypes.objectOf( - PropTypes.shape({ - /** Date login was validated, used to show brickroad info status */ - validatedDate: PropTypes.string, - - /** Field-specific server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), - }), - ), - - user: userPropTypes, - - ...withLocalizePropTypes, - ...windowDimensionsPropTypes, - ...withCurrentUserPersonalDetailsPropTypes, -}; - -const defaultProps = { - loginList: {}, - user: {}, - ...withCurrentUserPersonalDetailsDefaultProps, -}; - -function ProfilePage(props) { - const styles = useThemeStyles(); - const getPronouns = () => { - let pronounsKey = lodashGet(props.currentUserPersonalDetails, 'pronouns', ''); - if (pronounsKey.startsWith(CONST.PRONOUNS.PREFIX)) { - pronounsKey = pronounsKey.slice(CONST.PRONOUNS.PREFIX.length); - } - - if (!pronounsKey) { - return props.translate('profilePage.selectYourPronouns'); - } - return props.translate(`pronouns.${pronounsKey}`); - }; - const currentUserDetails = props.currentUserPersonalDetails || {}; - const contactMethodBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(props.loginList); - const avatarURL = lodashGet(currentUserDetails, 'avatar', ''); - const accountID = lodashGet(currentUserDetails, 'accountID', ''); - const emojiCode = lodashGet(props, 'currentUserPersonalDetails.status.emojiCode', ''); - - const profileSettingsOptions = [ - { - description: props.translate('displayNamePage.headerTitle'), - title: lodashGet(currentUserDetails, 'displayName', ''), - pageRoute: ROUTES.SETTINGS_DISPLAY_NAME, - }, - { - description: props.translate('contacts.contactMethod'), - title: props.formatPhoneNumber(lodashGet(currentUserDetails, 'login', '')), - pageRoute: ROUTES.SETTINGS_CONTACT_METHODS.route, - brickRoadIndicator: contactMethodBrickRoadIndicator, - }, - ...[ - { - description: props.translate('statusPage.status'), - title: emojiCode ? `${emojiCode} ${lodashGet(props, 'currentUserPersonalDetails.status.text', '')}` : '', - pageRoute: ROUTES.SETTINGS_STATUS, - }, - ], - { - description: props.translate('pronounsPage.pronouns'), - title: getPronouns(), - pageRoute: ROUTES.SETTINGS_PRONOUNS, - }, - { - description: props.translate('timezonePage.timezone'), - title: `${lodashGet(currentUserDetails, 'timezone.selected', '')}`, - pageRoute: ROUTES.SETTINGS_TIMEZONE, - }, - ]; - - useEffect(() => { - App.openProfile(props.currentUserPersonalDetails); - }, [props.currentUserPersonalDetails]); - - return ( - - Navigation.goBack(ROUTES.SETTINGS)} - /> - - Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(accountID))} - previewSource={UserUtils.getFullSizeAvatar(avatarURL, accountID)} - originalFileName={currentUserDetails.originalFileName} - headerTitle={props.translate('profilePage.profileAvatar')} - style={[styles.mh5]} - fallbackIcon={lodashGet(currentUserDetails, 'fallbackIcon')} - /> - - {_.map(profileSettingsOptions, (detail, index) => ( - Navigation.navigate(detail.pageRoute)} - brickRoadIndicator={detail.brickRoadIndicator} - /> - ))} - - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS)} - shouldShowRightIcon - /> - {props.user.hasLoungeAccess && ( - Navigation.navigate(ROUTES.SETTINGS_LOUNGE_ACCESS)} - shouldShowRightIcon - /> - )} - - - ); -} - -ProfilePage.propTypes = propTypes; -ProfilePage.defaultProps = defaultProps; -ProfilePage.displayName = 'ProfilePage'; - -export default compose( - withLocalize, - withWindowDimensions, - withCurrentUserPersonalDetails, - withOnyx({ - loginList: { - key: ONYXKEYS.LOGIN_LIST, - }, - user: { - key: ONYXKEYS.USER, - }, - }), -)(ProfilePage); diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx new file mode 100755 index 000000000000..c9a6a1e680b0 --- /dev/null +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -0,0 +1,158 @@ +import React, {useEffect} from 'react'; +import {ScrollView, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; +import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScreenWrapper from '@components/ScreenWrapper'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; +import Navigation from '@libs/Navigation/Navigation'; +import * as UserUtils from '@libs/UserUtils'; +import * as App from '@userActions/App'; +import * as PersonalDetails from '@userActions/PersonalDetails'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {LoginList, PersonalDetails as PersonalDetailsType, User} from '@src/types/onyx'; +import type IconAsset from '@src/types/utils/IconAsset'; + +type ProfilePageOnyxProps = { + loginList: OnyxEntry; + user: OnyxEntry; +}; + +type ProfilePageProps = ProfilePageOnyxProps & WithCurrentUserPersonalDetailsProps; + +function ProfilePage({loginList, user, currentUserPersonalDetails}: ProfilePageProps) { + const styles = useThemeStyles(); + + const {translate} = useLocalize(); + const {windowWidth} = useWindowDimensions(); + + const getPronouns = (): string => { + const pronounsKey = currentUserPersonalDetails?.pronouns?.replace(CONST.PRONOUNS.PREFIX, '') ?? ''; + return pronounsKey ? translate(`pronouns.${pronounsKey}` as TranslationPaths) : translate('profilePage.selectYourPronouns'); + }; + + const contactMethodBrickRoadIndicator = loginList ? UserUtils.getLoginListBrickRoadIndicator(loginList) : undefined; + const avatarURL = currentUserPersonalDetails?.avatar ?? ''; + const accountID = currentUserPersonalDetails?.accountID ?? ''; + const emojiCode = currentUserPersonalDetails?.status?.emojiCode ?? ''; + + const profileSettingsOptions = [ + { + description: translate('displayNamePage.headerTitle'), + title: currentUserPersonalDetails?.displayName ?? '', + pageRoute: ROUTES.SETTINGS_DISPLAY_NAME, + }, + { + description: translate('contacts.contactMethod'), + title: LocalePhoneNumber.formatPhoneNumber(currentUserPersonalDetails?.login ?? ''), + pageRoute: ROUTES.SETTINGS_CONTACT_METHODS.route, + brickRoadIndicator: contactMethodBrickRoadIndicator, + }, + { + description: translate('statusPage.status'), + title: emojiCode ? `${emojiCode} ${currentUserPersonalDetails?.status?.text ?? ''}` : '', + pageRoute: ROUTES.SETTINGS_STATUS, + }, + { + description: translate('pronounsPage.pronouns'), + title: getPronouns(), + pageRoute: ROUTES.SETTINGS_PRONOUNS, + }, + { + description: translate('timezonePage.timezone'), + title: currentUserPersonalDetails?.timezone?.selected ?? '', + pageRoute: ROUTES.SETTINGS_TIMEZONE, + }, + ]; + + useEffect(() => { + App.openProfile(currentUserPersonalDetails as PersonalDetailsType); + }, [currentUserPersonalDetails]); + + return ( + + Navigation.goBack(ROUTES.SETTINGS)} + /> + + Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(accountID))} + previewSource={UserUtils.getFullSizeAvatar(avatarURL, accountID)} + originalFileName={currentUserPersonalDetails?.originalFileName} + headerTitle={translate('profilePage.profileAvatar')} + style={[styles.mh5]} + fallbackIcon={currentUserPersonalDetails?.fallbackIcon} + /> + + {profileSettingsOptions.map((detail, index) => ( + Navigation.navigate(detail.pageRoute)} + brickRoadIndicator={detail.brickRoadIndicator} + /> + ))} + + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS)} + shouldShowRightIcon + /> + {user?.hasLoungeAccess && ( + Navigation.navigate(ROUTES.SETTINGS_LOUNGE_ACCESS)} + shouldShowRightIcon + /> + )} + + + ); +} + +ProfilePage.displayName = 'ProfilePage'; + +export default withCurrentUserPersonalDetails( + withOnyx({ + loginList: { + key: ONYXKEYS.LOGIN_LIST, + }, + user: { + key: ONYXKEYS.USER, + }, + })(ProfilePage), +); diff --git a/src/pages/settings/Profile/TimezoneInitialPage.js b/src/pages/settings/Profile/TimezoneInitialPage.tsx similarity index 55% rename from src/pages/settings/Profile/TimezoneInitialPage.js rename to src/pages/settings/Profile/TimezoneInitialPage.tsx index bf86e8a5a077..cc63a985b6ba 100644 --- a/src/pages/settings/Profile/TimezoneInitialPage.js +++ b/src/pages/settings/Profile/TimezoneInitialPage.tsx @@ -1,4 +1,3 @@ -import lodashGet from 'lodash/get'; import React from 'react'; import {View} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -6,62 +5,56 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScreenWrapper from '@components/ScreenWrapper'; import Switch from '@components/Switch'; import Text from '@components/Text'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails'; -const propTypes = { - ...withLocalizePropTypes, - ...withCurrentUserPersonalDetailsPropTypes, -}; +type TimezoneInitialPageProps = WithCurrentUserPersonalDetailsProps; -const defaultProps = { - ...withCurrentUserPersonalDetailsDefaultProps, -}; - -function TimezoneInitialPage(props) { +function TimezoneInitialPage({currentUserPersonalDetails}: TimezoneInitialPageProps) { const styles = useThemeStyles(); - const timezone = lodashGet(props.currentUserPersonalDetails, 'timezone', CONST.DEFAULT_TIME_ZONE); + const timezone: Timezone = currentUserPersonalDetails?.timezone ?? CONST.DEFAULT_TIME_ZONE; + + const {translate} = useLocalize(); /** * Updates setting for automatic timezone selection. * Note: If we are updating automatically, we'll immediately calculate the user's timezone. - * - * @param {Boolean} isAutomatic */ - const updateAutomaticTimezone = (isAutomatic) => { + const updateAutomaticTimezone = (isAutomatic: boolean) => { PersonalDetails.updateAutomaticTimezone({ automatic: isAutomatic, - selected: isAutomatic ? Intl.DateTimeFormat().resolvedOptions().timeZone : timezone.selected, + selected: isAutomatic ? (Intl.DateTimeFormat().resolvedOptions().timeZone as SelectedTimezone) : timezone.selected, }); }; return ( Navigation.goBack(ROUTES.SETTINGS_PROFILE)} /> - {props.translate('timezonePage.isShownOnProfile')} + {translate('timezonePage.isShownOnProfile')} - {props.translate('timezonePage.getLocationAutomatically')} + {translate('timezonePage.getLocationAutomatically')} Navigation.navigate(ROUTES.SETTINGS_TIMEZONE_SELECT)} @@ -71,8 +64,6 @@ function TimezoneInitialPage(props) { ); } -TimezoneInitialPage.propTypes = propTypes; -TimezoneInitialPage.defaultProps = defaultProps; TimezoneInitialPage.displayName = 'TimezoneInitialPage'; -export default compose(withLocalize, withCurrentUserPersonalDetails)(TimezoneInitialPage); +export default withCurrentUserPersonalDetails(TimezoneInitialPage); diff --git a/src/pages/settings/Profile/TimezoneSelectPage.js b/src/pages/settings/Profile/TimezoneSelectPage.tsx similarity index 52% rename from src/pages/settings/Profile/TimezoneSelectPage.js rename to src/pages/settings/Profile/TimezoneSelectPage.tsx index 8280d9b5c604..933f1dfc5f2a 100644 --- a/src/pages/settings/Profile/TimezoneSelectPage.js +++ b/src/pages/settings/Profile/TimezoneSelectPage.tsx @@ -1,10 +1,9 @@ -import lodashGet from 'lodash/get'; import React, {useState} from 'react'; -import _ from 'underscore'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useInitialValue from '@hooks/useInitialValue'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; @@ -12,67 +11,45 @@ import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import TIMEZONES from '@src/TIMEZONES'; +import type {SelectedTimezone} from '@src/types/onyx/PersonalDetails'; -const propTypes = { - ...withCurrentUserPersonalDetailsPropTypes, -}; - -const defaultProps = { - ...withCurrentUserPersonalDetailsDefaultProps, -}; +type TimezoneSelectPageProps = Pick; /** * We add the current time to the key to fix a bug where the list options don't update unless the key is updated. - * @param {String} text - * @return {string} key for list item */ -const getKey = (text) => `${text}-${new Date().getTime()}`; +const getKey = (text: string): string => `${text}-${new Date().getTime()}`; -/** - * @param {Object} currentUserPersonalDetails - * @return {Object} user's timezone data - */ -const getUserTimezone = (currentUserPersonalDetails) => lodashGet(currentUserPersonalDetails, 'timezone', CONST.DEFAULT_TIME_ZONE); +const getUserTimezone = ({currentUserPersonalDetails}: Pick) => + currentUserPersonalDetails?.timezone ?? CONST.DEFAULT_TIME_ZONE; -function TimezoneSelectPage(props) { +function TimezoneSelectPage({currentUserPersonalDetails}: TimezoneSelectPageProps) { const {translate} = useLocalize(); - const timezone = getUserTimezone(props.currentUserPersonalDetails); + const timezone = getUserTimezone({currentUserPersonalDetails}); const allTimezones = useInitialValue(() => - _.chain(TIMEZONES) - .filter((tz) => !tz.startsWith('Etc/GMT')) - .map((text) => ({ - text, - keyForList: getKey(text), - isSelected: text === timezone.selected, - })) - .value(), + TIMEZONES.filter((tz: string) => !tz.startsWith('Etc/GMT')).map((text: string) => ({ + text, + keyForList: getKey(text), + isSelected: text === timezone.selected, + })), ); const [timezoneInputText, setTimezoneInputText] = useState(''); const [timezoneOptions, setTimezoneOptions] = useState(allTimezones); - /** - * @param {Object} timezone - * @param {String} timezone.text - */ - const saveSelectedTimezone = ({text}) => { - PersonalDetails.updateSelectedTimezone(text); + const saveSelectedTimezone = ({text}: {text: string}) => { + PersonalDetails.updateSelectedTimezone(text as SelectedTimezone); }; - /** - * @param {String} searchText - */ - const filterShownTimezones = (searchText) => { + const filterShownTimezones = (searchText: string) => { setTimezoneInputText(searchText); - const searchWords = searchText.toLowerCase().match(/[a-z0-9]+/g) || []; + const searchWords = searchText.toLowerCase().match(/[a-z0-9]+/g) ?? []; setTimezoneOptions( - _.filter(allTimezones, (tz) => - _.every( - searchWords, - (word) => - tz.text - .toLowerCase() - .replace(/[^a-z0-9]/g, ' ') - .indexOf(word) > -1, + allTimezones.filter((tz) => + searchWords.every((word) => + tz.text + .toLowerCase() + .replace(/[^a-z0-9]/g, ' ') + .includes(word), ), ), ); @@ -94,7 +71,7 @@ function TimezoneSelectPage(props) { onChangeText={filterShownTimezones} onSelectRow={saveSelectedTimezone} sections={[{data: timezoneOptions, indexOffset: 0, isDisabled: timezone.automatic}]} - initiallyFocusedOptionKey={_.get(_.filter(timezoneOptions, (tz) => tz.text === timezone.selected)[0], 'keyForList')} + initiallyFocusedOptionKey={timezoneOptions.find((tz) => tz.text === timezone.selected)?.keyForList} showScrollIndicator shouldShowTooltips={false} /> @@ -102,8 +79,6 @@ function TimezoneSelectPage(props) { ); } -TimezoneSelectPage.propTypes = propTypes; -TimezoneSelectPage.defaultProps = defaultProps; TimezoneSelectPage.displayName = 'TimezoneSelectPage'; export default withCurrentUserPersonalDetails(TimezoneSelectPage); diff --git a/src/types/onyx/PersonalDetails.ts b/src/types/onyx/PersonalDetails.ts index 8d02d7cf26fc..5e74743c8ac1 100644 --- a/src/types/onyx/PersonalDetails.ts +++ b/src/types/onyx/PersonalDetails.ts @@ -17,7 +17,7 @@ type Status = { emojiCode: string; /** The text of the draft status */ - text?: string; + text: string; /** The timestamp of when the status should be cleared */ clearAfter: string; // ISO 8601 format; From dae08c96c8d7d3008858edf666fa452c6269d310 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 31 Jan 2024 12:56:40 +0530 Subject: [PATCH 003/556] Remove MoneyRequestSelectorPage.js and copy any changes since Nov 27 into IOURequestStartPage.js. Signed-off-by: Krishna Gupta --- ...oraryForRefactorRequestConfirmationList.js | 4 +-- .../AttachmentPickerWithMenuItems.js | 3 +- .../FloatingActionButtonAndPopover.js | 11 ++++-- src/pages/iou/request/IOURequestStartPage.js | 36 ++++++++++--------- ...yForRefactorRequestParticipantsSelector.js | 2 +- .../step/IOURequestStepConfirmation.js | 17 ++++++--- .../step/IOURequestStepParticipants.js | 12 +++++-- 7 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 2aff0444a59e..f48820654768 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -266,8 +266,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // Do not hide fields in case of send money request const shouldShowAllFields = isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || isEditingSplitBill; - const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; - const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; + const shouldShowDate = (shouldShowSmartScanFields || isDistanceRequest) && !isTypeSend; + const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest && !isTypeSend; // Fetches the first tag list of the policy const policyTag = PolicyUtils.getTag(policyTags); diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 444dd939142b..4091016baeeb 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -20,7 +20,6 @@ import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; -import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; @@ -154,7 +153,7 @@ function AttachmentPickerWithMenuItems({ [CONST.IOU.TYPE.SEND]: { icon: Expensicons.Send, text: translate('iou.sendMoney'), - onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND, report.reportID), + onSelected: () => Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.SEND, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report.reportID)), }, }; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index bbcdc5cebef4..4ab4e5fbb081 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -15,7 +15,6 @@ import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; -import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy'; import * as Session from '@userActions/Session'; import * as Task from '@userActions/Task'; @@ -182,7 +181,15 @@ function FloatingActionButtonAndPopover(props) { { icon: Expensicons.Send, text: props.translate('iou.sendMoney'), - onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), + // onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), + onSelected: () => + interceptAnonymousUser(() => + Navigation.navigate( + // When starting to create a send money request from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used + // for all of the routes in the creation flow. + ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.SEND, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, ReportUtils.generateReportID()), + ), + ), }, ...[ { diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index 3d80ab89347d..c790b30c5f7d 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -156,22 +156,26 @@ function IOURequestStartPage({ title={tabTitles[iouType]} onBackButtonPress={navigateBack} /> - ( - - )} - > - {() => } - {() => } - {shouldDisplayDistanceRequest && {() => }} - + {iouType === CONST.IOU.TYPE.REQUEST || iouType === CONST.IOU.TYPE.SPLIT ? ( + ( + + )} + > + {() => } + {() => } + {shouldDisplayDistanceRequest && {() => }} + + ) : ( + + )} diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 15f98205839e..5d84b0d81b68 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -256,7 +256,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; - const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE; + const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE && iouType !== CONST.IOU.TYPE.SEND; const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST; const handleConfirmSelection = useCallback(() => { diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 6028a735d132..bdee619dba9f 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -92,7 +92,15 @@ function IOURequestStepConfirmation({ const transactionTaxCode = transaction.taxRate && transaction.taxRate.keyForList; const transactionTaxAmount = transaction.taxAmount; const requestType = TransactionUtils.getRequestType(transaction); - const headerTitle = iouType === CONST.IOU.TYPE.SPLIT ? translate('iou.split') : translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + const headerTitle = () => { + if (iouType === CONST.IOU.TYPE.SPLIT) { + return translate('iou.split'); + } + if (iouType === CONST.IOU.TYPE.SEND) { + return translate('common.send'); + } + return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + }; const participants = useMemo( () => _.map(transaction.participants, (participant) => { @@ -287,7 +295,7 @@ function IOURequestStepConfirmation({ const sendMoney = useCallback( (paymentMethodType) => { const currency = transaction.currency; - const trimmedComment = transaction.comment.trim(); + const trimmedComment = lodashGet(transaction, 'comment.comment', '').trim(); const participant = participants[0]; if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { @@ -299,8 +307,9 @@ function IOURequestStepConfirmation({ IOU.sendMoneyWithWallet(report, transaction.amount, currency, trimmedComment, currentUserPersonalDetails.accountID, participant); } }, - [transaction.amount, transaction.comment, participants, transaction.currency, currentUserPersonalDetails.accountID, report], + [transaction, participants, currentUserPersonalDetails.accountID, report], ); + const addNewParticipant = (option) => { const newParticipants = _.map(transaction.participants, (participant) => { if (participant.accountID === option.accountID) { @@ -327,7 +336,7 @@ function IOURequestStepConfirmation({ {({safeAreaPaddingBottomStyle}) => ( { + if (iouType === CONST.IOU.TYPE.SPLIT) { + return translate('iou.split'); + } + if (iouType === CONST.IOU.TYPE.SEND) { + return translate('common.send'); + } + translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + }; const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); @@ -81,7 +89,7 @@ function IOURequestStepParticipants({ return ( Date: Sat, 3 Feb 2024 16:27:01 +0530 Subject: [PATCH 004/556] remove redundant code and old component. Signed-off-by: Krishna Gupta --- .../AppNavigator/ModalStackNavigators.tsx | 1 - src/libs/Navigation/linkingConfig/config.ts | 18 -- src/libs/Navigation/types.ts | 1 - .../FloatingActionButtonAndPopover.js | 2 +- src/pages/iou/MoneyRequestSelectorPage.js | 169 ------------------ ...yForRefactorRequestParticipantsSelector.js | 1 - 6 files changed, 1 insertion(+), 191 deletions(-) delete mode 100644 src/pages/iou/MoneyRequestSelectorPage.js diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 4606f867c3fc..60b87fd85f3d 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -93,7 +93,6 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/request/step/IOURequestStepScan').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_TAG]: () => require('../../../pages/iou/request/step/IOURequestStepTag').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: () => require('../../../pages/iou/request/step/IOURequestStepWaypoint').default as React.ComponentType, - [SCREENS.MONEY_REQUEST.ROOT]: () => require('../../../pages/iou/MoneyRequestSelectorPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.AMOUNT]: () => require('../../../pages/iou/steps/NewRequestAmountPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.PARTICIPANTS]: () => require('../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.CONFIRMATION]: () => require('../../../pages/iou/steps/MoneyRequestConfirmPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index f1c9c316fe93..72c2c9d305f3 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -379,24 +379,6 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_SCAN]: ROUTES.MONEY_REQUEST_STEP_SCAN.route, [SCREENS.MONEY_REQUEST.STEP_TAG]: ROUTES.MONEY_REQUEST_STEP_TAG.route, [SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: ROUTES.MONEY_REQUEST_STEP_WAYPOINT.route, - [SCREENS.MONEY_REQUEST.ROOT]: { - path: ROUTES.MONEY_REQUEST.route, - exact: true, - screens: { - [SCREENS.MONEY_REQUEST.MANUAL_TAB]: { - path: ROUTES.MONEY_REQUEST_MANUAL_TAB, - exact: true, - }, - [SCREENS.MONEY_REQUEST.SCAN_TAB]: { - path: ROUTES.MONEY_REQUEST_SCAN_TAB, - exact: true, - }, - [SCREENS.MONEY_REQUEST.DISTANCE_TAB]: { - path: ROUTES.MONEY_REQUEST_DISTANCE_TAB.route, - exact: true, - }, - }, - }, [SCREENS.MONEY_REQUEST.AMOUNT]: ROUTES.MONEY_REQUEST_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_RATE]: ROUTES.MONEY_REQUEST_STEP_TAX_RATE.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3c4cf17853f1..13eeb972305a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -193,7 +193,6 @@ type RoomInviteNavigatorParamList = { }; type MoneyRequestNavigatorParamList = { - [SCREENS.MONEY_REQUEST.ROOT]: undefined; [SCREENS.MONEY_REQUEST.AMOUNT]: undefined; [SCREENS.MONEY_REQUEST.PARTICIPANTS]: { iouType: string; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 7a3390ab5478..b2844374b6ae 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -174,7 +174,7 @@ function FloatingActionButtonAndPopover(props) { }, { icon: Expensicons.Send, - text: props.translate('iou.sendMoney'), + text: translate('iou.sendMoney'), // onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), onSelected: () => interceptAnonymousUser(() => diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js deleted file mode 100644 index 0a0efc38313a..000000000000 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ /dev/null @@ -1,169 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useEffect, useState} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import DragAndDropProvider from '@components/DragAndDrop/Provider'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import TabSelector from '@components/TabSelector/TabSelector'; -import useLocalize from '@hooks/useLocalize'; -import usePrevious from '@hooks/usePrevious'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as IOUUtils from '@libs/IOUUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; -import * as ReportUtils from '@libs/ReportUtils'; -import withReportOrNotFound from '@pages/home/report/withReportOrNotFound'; -import reportPropTypes from '@pages/reportPropTypes'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import NewDistanceRequestPage from './NewDistanceRequestPage'; -import IOURequestStepScan from './request/step/IOURequestStepScan'; -import NewRequestAmountPage from './steps/NewRequestAmountPage'; - -const propTypes = { - /** React Navigation route */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string, - - /** The report ID of the IOU */ - reportID: PropTypes.string, - }), - }).isRequired, - - /** Report on which the money request is being created */ - report: reportPropTypes, - - /** The policy tied to the report */ - policy: PropTypes.shape({ - /** Type of the policy */ - type: PropTypes.string, - }), - - /** Which tab has been selected */ - selectedTab: PropTypes.string, -}; - -const defaultProps = { - selectedTab: CONST.TAB_REQUEST.SCAN, - report: {}, - policy: {}, -}; - -function MoneyRequestSelectorPage(props) { - const styles = useThemeStyles(); - const [isDraggingOver, setIsDraggingOver] = useState(false); - - const iouType = lodashGet(props.route, 'params.iouType', ''); - const reportID = lodashGet(props.route, 'params.reportID', ''); - const {translate} = useLocalize(); - - const title = { - [CONST.IOU.TYPE.REQUEST]: translate('iou.requestMoney'), - [CONST.IOU.TYPE.SEND]: translate('iou.sendMoney'), - [CONST.IOU.TYPE.SPLIT]: translate('iou.splitBill'), - }; - const isFromGlobalCreate = !reportID; - const isExpenseChat = ReportUtils.isPolicyExpenseChat(props.report); - const isExpenseReport = ReportUtils.isExpenseReport(props.report); - const shouldDisplayDistanceRequest = isExpenseChat || isExpenseReport || isFromGlobalCreate; - - const resetMoneyRequestInfo = () => { - const moneyRequestID = `${iouType}${reportID}`; - IOU.resetMoneyRequestInfo(moneyRequestID); - }; - - // Allow the user to create the request if we are creating the request in global menu or the report can create the request - const isAllowedToCreateRequest = _.isEmpty(props.report.reportID) || ReportUtils.canCreateRequest(props.report, props.policy, iouType); - const prevSelectedTab = usePrevious(props.selectedTab); - - useEffect(() => { - if (prevSelectedTab === props.selectedTab) { - return; - } - - resetMoneyRequestInfo(); - // resetMoneyRequestInfo function is not added as dependencies since they don't change between renders - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.selectedTab, prevSelectedTab]); - - return ( - - {({safeAreaPaddingBottomStyle}) => ( - - - - - {iouType === CONST.IOU.TYPE.REQUEST || iouType === CONST.IOU.TYPE.SPLIT ? ( - ( - - )} - > - - {() => } - {shouldDisplayDistanceRequest && ( - - )} - - ) : ( - - )} - - - - )} - - ); -} - -MoneyRequestSelectorPage.propTypes = propTypes; -MoneyRequestSelectorPage.defaultProps = defaultProps; -MoneyRequestSelectorPage.displayName = 'MoneyRequestSelectorPage'; - -export default compose( - withReportOrNotFound(false), - withOnyx({ - selectedTab: { - key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, - }, - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${lodashGet(report, 'policyID')}`, - }, - }), -)(MoneyRequestSelectorPage); diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 6e9de5b62a7e..a4807572bf56 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -258,7 +258,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE && iouType !== CONST.IOU.TYPE.SEND; - const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST; const handleConfirmSelection = useCallback(() => { if (shouldShowSplitBillErrorMessage) { From b915c4ef809709a29734f0a67a51d423b1eb8d2b Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 3 Feb 2024 16:53:52 +0530 Subject: [PATCH 005/556] remove redundant code and comment Signed-off-by: Krishna Gupta --- src/libs/actions/IOU.js | 11 ----------- .../SidebarScreen/FloatingActionButtonAndPopover.js | 1 - 2 files changed, 12 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 4db89a1e926b..053d48b7ed0f 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3572,16 +3572,6 @@ function setMoneyRequestParticipantsFromReport(transactionID, report) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true}); } -/** - * Initialize money request info and navigate to the MoneyRequest page - * @param {String} iouType - * @param {String} reportID - */ -function startMoneyRequest(iouType, reportID = '') { - resetMoneyRequestInfo(`${iouType}${reportID}`); - Navigation.navigate(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID)); -} - /** * @param {String} id */ @@ -3799,7 +3789,6 @@ export { submitReport, payMoneyRequest, sendMoneyWithWallet, - startMoneyRequest, startMoneyRequest_temporaryForRefactor, resetMoneyRequestCategory, resetMoneyRequestCategory_temporaryForRefactor, diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index b2844374b6ae..52251da7a67a 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -175,7 +175,6 @@ function FloatingActionButtonAndPopover(props) { { icon: Expensicons.Send, text: translate('iou.sendMoney'), - // onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), onSelected: () => interceptAnonymousUser(() => Navigation.navigate( From 0824fb95fa3df6b9f06e96d4d42266deb000c50d Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 11:08:40 +0530 Subject: [PATCH 006/556] update startMoneyRequest_temporaryForRefactor to startMoneyRequest. Signed-off-by: Krishna Gupta --- src/libs/actions/IOU.ts | 9 +-------- src/pages/iou/request/IOURequestStartPage.js | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 569b6b26b728..f20e0b0519c7 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -225,7 +225,7 @@ Onyx.connect({ * @param iouRequestType one of manual/scan/distance */ // eslint-disable-next-line @typescript-eslint/naming-convention -function startMoneyRequest_temporaryForRefactor(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) { +function startMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) { // Generate a brand new transactionID const newTransactionID = CONST.IOU.OPTIMISTIC_TRANSACTION_ID; // Disabling this line since currentDate can be an empty string @@ -3530,12 +3530,6 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true}); } -/** Initialize money request info and navigate to the MoneyRequest page */ -function startMoneyRequest(iouType: string, reportID = '') { - resetMoneyRequestInfo(`${iouType}${reportID}`); - Navigation.navigate(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID)); -} - function setMoneyRequestId(id: string) { Onyx.merge(ONYXKEYS.IOU, {id}); } @@ -3686,7 +3680,6 @@ export { payMoneyRequest, sendMoneyWithWallet, startMoneyRequest, - startMoneyRequest_temporaryForRefactor, resetMoneyRequestCategory, resetMoneyRequestCategory_temporaryForRefactor, resetMoneyRequestInfo, diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index c790b30c5f7d..dd6420bf2c1a 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -106,7 +106,7 @@ function IOURequestStartPage({ if (transaction.reportID === reportID) { return; } - IOU.startMoneyRequest_temporaryForRefactor(reportID, isFromGlobalCreate, transactionRequestType.current); + IOU.startMoneyRequest(reportID, isFromGlobalCreate, transactionRequestType.current); }, [transaction, reportID, iouType, isFromGlobalCreate]); const isExpenseChat = ReportUtils.isPolicyExpenseChat(report); @@ -125,7 +125,7 @@ function IOURequestStartPage({ if (newIouType === previousIOURequestType) { return; } - IOU.startMoneyRequest_temporaryForRefactor(reportID, isFromGlobalCreate, newIouType); + IOU.startMoneyRequest(reportID, isFromGlobalCreate, newIouType); transactionRequestType.current = newIouType; }, [previousIOURequestType, reportID, isFromGlobalCreate], From 4e69672e311a7509f05b5e2946814c131c5284b2 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 11:35:40 +0530 Subject: [PATCH 007/556] remove: unused routes from ROUTES.ts Signed-off-by: Krishna Gupta --- src/ROUTES.ts | 7 ------- src/pages/iou/request/step/IOURequestStepWaypoint.js | 6 +++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 016e4267803b..aabed6fdb0a1 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -301,13 +301,6 @@ const ROUTES = { route: ':iouType/new/address/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}` as const, }, - MONEY_REQUEST_DISTANCE_TAB: { - route: ':iouType/new/:reportID?/distance', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance` as const, - }, - MONEY_REQUEST_MANUAL_TAB: ':iouType/new/:reportID?/manual', - MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', - MONEY_REQUEST_CREATE: { route: 'create/:iouType/start/:transactionID/:reportID', getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}` as const, diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.js b/src/pages/iou/request/step/IOURequestStepWaypoint.js index 4c35951bc297..89e2b2cc297d 100644 --- a/src/pages/iou/request/step/IOURequestStepWaypoint.js +++ b/src/pages/iou/request/step/IOURequestStepWaypoint.js @@ -158,13 +158,13 @@ function IOURequestStepWaypoint({ } // Other flows will be handled by selecting a waypoint with selectWaypoint as this is mainly for the offline flow - Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID)); }; const deleteStopAndHideModal = () => { Transaction.removeWaypoint(transaction, pageIndex, true); setIsDeleteStopModalOpen(false); - Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID)); }; /** @@ -200,7 +200,7 @@ function IOURequestStepWaypoint({ title={translate(waypointDescriptionKey)} shouldShowBackButton onBackButtonPress={() => { - Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID)); }} shouldShowThreeDotsButton={shouldShowThreeDotsButton} shouldSetModalVisibility={false} From 013376d144397e0335f1eedb794be9fd81f12f88 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 13:59:30 +0530 Subject: [PATCH 008/556] minor fix. Signed-off-by: Krishna Gupta --- src/pages/iou/request/step/IOURequestStepParticipants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index daae4bcf0831..a11877739cb5 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -46,7 +46,7 @@ function IOURequestStepParticipants({ if (iouType === CONST.IOU.TYPE.SEND) { return translate('common.send'); } - translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); }; const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); From 423af4e488e11f3df71ce02f6b295e7292b978bc Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 14:14:51 +0530 Subject: [PATCH 009/556] updated sendMoney callback dependencies. Signed-off-by: Krishna Gupta --- .../iou/request/step/IOURequestStepConfirmation.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 70d5e1fceac7..c66dc91f4f64 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -308,7 +308,13 @@ function IOURequestStepConfirmation({ const sendMoney = useCallback( (paymentMethodType) => { const currency = transaction.currency; - const trimmedComment = lodashGet(transaction, 'comment.comment', '').trim(); + + let trimmedComment = ''; + + if (transaction.comment && transaction.comment.comment) { + trimmedComment = transaction.comment.comment.trim(); + } + const participant = participants[0]; if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { @@ -320,7 +326,7 @@ function IOURequestStepConfirmation({ IOU.sendMoneyWithWallet(report, transaction.amount, currency, trimmedComment, currentUserPersonalDetails.accountID, participant); } }, - [transaction, participants, currentUserPersonalDetails.accountID, report], + [transaction.amount, transaction.comment, transaction.currency, participants, currentUserPersonalDetails.accountID, report], ); const addNewParticipant = (option) => { From 21e3ec0ad48fc8a99f30c75c27fc809be6a5f1bf Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 6 Feb 2024 23:51:15 +0700 Subject: [PATCH 010/556] fix: app crashes when changing from auditor to employee --- src/pages/home/ReportScreen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index bfe27910c943..2e2d7251de6f 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -418,7 +418,7 @@ function ReportScreen({ !onyxReportID && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && (report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) || - ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport)) && _.isEmpty(report)) + ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && _.isEmpty(report)) ) { Navigation.dismissModal(); if (Navigation.getTopmostReportId() === prevOnyxReportID) { @@ -643,7 +643,7 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : 0}`, selector: (parentReportActions, props) => { const parentReportActionID = lodashGet(props, 'report.parentReportActionID'); - if (!parentReportActionID) { + if (!parentReportActionID || !parentReportActions) { return {}; } return lodashGet(parentReportActions, parentReportActionID); From 92789b565db5233a5f6dfdc243f266c92a406aa7 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 13 Feb 2024 20:55:10 +0530 Subject: [PATCH 011/556] Remove redundant constants for the old screen names. Signed-off-by: Krishna Gupta --- src/SCREENS.ts | 4 ---- src/pages/iou/request/step/IOURequestStepConfirmation.js | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 1626fdbd1898..6da12b453ef8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -122,9 +122,6 @@ const SCREENS = { SAML_SIGN_IN: 'SAMLSignIn', MONEY_REQUEST: { - MANUAL_TAB: 'manual', - SCAN_TAB: 'scan', - DISTANCE_TAB: 'distance', CREATE: 'Money_Request_Create', STEP_CONFIRMATION: 'Money_Request_Step_Confirmation', START: 'Money_Request_Start', @@ -141,7 +138,6 @@ const SCREENS = { STEP_WAYPOINT: 'Money_Request_Step_Waypoint', STEP_TAX_AMOUNT: 'Money_Request_Step_Tax_Amount', STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate', - ROOT: 'Money_Request', AMOUNT: 'Money_Request_Amount', PARTICIPANTS: 'Money_Request_Participants', CONFIRMATION: 'Money_Request_Confirmation', diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 038841027fae..6536541c289b 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -360,11 +360,7 @@ function IOURequestStepConfirmation({ (paymentMethodType) => { const currency = transaction.currency; - let trimmedComment = ''; - - if (transaction.comment && transaction.comment.comment) { - trimmedComment = transaction.comment.comment.trim(); - } + const trimmedComment = transaction.comment && transaction.comment.comment ? transaction.comment.comment.trim() : ''; const participant = participants[0]; From 42aa582deb02445448f4a01f48c3e08232f167dd Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 14 Feb 2024 03:12:23 +0200 Subject: [PATCH 012/556] Updating profile page --- src/pages/settings/Profile/ProfilePage.tsx | 175 +++++++++++++-------- 1 file changed, 109 insertions(+), 66 deletions(-) diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index c9a6a1e680b0..7a0c63fb102b 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -1,42 +1,61 @@ import React, {useEffect} from 'react'; import {ScrollView, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx/lib/types'; -import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import * as Expensicons from '@components/Icon/Expensicons'; -import MenuItem from '@components/MenuItem'; +import * as Illustrations from '@components/Icon/Illustrations'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScreenWrapper from '@components/ScreenWrapper'; +import Section from '@components/Section'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as UserUtils from '@libs/UserUtils'; import * as App from '@userActions/App'; -import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {LoginList, PersonalDetails as PersonalDetailsType, User} from '@src/types/onyx'; -import type IconAsset from '@src/types/utils/IconAsset'; +import type {LoginList, PersonalDetails, PrivatePersonalDetails} from '@src/types/onyx'; type ProfilePageOnyxProps = { loginList: OnyxEntry; - user: OnyxEntry; + /** User's private personal details */ + privatePersonalDetails: OnyxEntry; }; type ProfilePageProps = ProfilePageOnyxProps & WithCurrentUserPersonalDetailsProps; -function ProfilePage({loginList, user, currentUserPersonalDetails}: ProfilePageProps) { +function ProfilePage({ + loginList, + privatePersonalDetails = { + legalFirstName: '', + legalLastName: '', + dob: '', + address: { + street: '', + city: '', + state: '', + zip: '', + country: '', + }, + }, + currentUserPersonalDetails, +}: ProfilePageProps) { + const theme = useTheme(); const styles = useThemeStyles(); - + const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const {windowWidth} = useWindowDimensions(); + const {isSmallScreenWidth} = useWindowDimensions(); const getPronouns = (): string => { const pronounsKey = currentUserPersonalDetails?.pronouns?.replace(CONST.PRONOUNS.PREFIX, '') ?? ''; @@ -44,11 +63,13 @@ function ProfilePage({loginList, user, currentUserPersonalDetails}: ProfilePageP }; const contactMethodBrickRoadIndicator = loginList ? UserUtils.getLoginListBrickRoadIndicator(loginList) : undefined; - const avatarURL = currentUserPersonalDetails?.avatar ?? ''; - const accountID = currentUserPersonalDetails?.accountID ?? ''; const emojiCode = currentUserPersonalDetails?.status?.emojiCode ?? ''; + usePrivatePersonalDetails(); + const privateDetails = privatePersonalDetails ?? {}; + const legalName = `${privateDetails.legalFirstName ?? ''} ${privateDetails.legalLastName ?? ''}`.trim(); + const isLoadingPersonalDetails = privatePersonalDetails?.isLoading ?? true; - const profileSettingsOptions = [ + const publicOptions = [ { description: translate('displayNamePage.headerTitle'), title: currentUserPersonalDetails?.displayName ?? '', @@ -78,67 +99,89 @@ function ProfilePage({loginList, user, currentUserPersonalDetails}: ProfilePageP ]; useEffect(() => { - App.openProfile(currentUserPersonalDetails as PersonalDetailsType); + App.openProfile(currentUserPersonalDetails as PersonalDetails); }, [currentUserPersonalDetails]); + const privateOptions = [ + { + description: translate('privatePersonalDetails.legalName'), + title: legalName, + pageRoute: ROUTES.SETTINGS_LEGAL_NAME, + }, + { + description: translate('common.dob'), + title: privateDetails.dob ?? '', + pageRoute: ROUTES.SETTINGS_DATE_OF_BIRTH, + }, + { + description: translate('privatePersonalDetails.address'), + title: PersonalDetailsUtils.getFormattedAddress(privateDetails), + pageRoute: ROUTES.SETTINGS_ADDRESS, + }, + ]; + return ( Navigation.goBack(ROUTES.SETTINGS)} + onBackButtonPress={() => Navigation.goBack()} + shouldShowBackButton={isSmallScreenWidth} + icon={Illustrations.Profile} /> - - Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(accountID))} - previewSource={UserUtils.getFullSizeAvatar(avatarURL, accountID)} - originalFileName={currentUserPersonalDetails?.originalFileName} - headerTitle={translate('profilePage.profileAvatar')} - style={[styles.mh5]} - fallbackIcon={currentUserPersonalDetails?.fallbackIcon} - /> - - {profileSettingsOptions.map((detail, index) => ( - Navigation.navigate(detail.pageRoute)} - brickRoadIndicator={detail.brickRoadIndicator} - /> - ))} + + +
+ {publicOptions.map((detail, index) => ( + Navigation.navigate(detail.pageRoute)} + brickRoadIndicator={detail.brickRoadIndicator} + /> + ))} +
+
+ {isLoadingPersonalDetails ? ( + + ) : ( + <> + {privateOptions.map((detail, index) => ( + Navigation.navigate(detail.pageRoute)} + /> + ))} + + )} +
- Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS)} - shouldShowRightIcon - /> - {user?.hasLoungeAccess && ( - Navigation.navigate(ROUTES.SETTINGS_LOUNGE_ACCESS)} - shouldShowRightIcon - /> - )}
); @@ -151,8 +194,8 @@ export default withCurrentUserPersonalDetails( loginList: { key: ONYXKEYS.LOGIN_LIST, }, - user: { - key: ONYXKEYS.USER, + privatePersonalDetails: { + key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, }, })(ProfilePage), ); From ae6d226fbdbddf7a12f1495adb1d1876bdbcce75 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 14 Feb 2024 05:39:58 +0200 Subject: [PATCH 013/556] migrating the status page --- src/ONYXKEYS.ts | 2 +- .../Profile/CustomStatus/StatusPage.tsx | 20 +++++++++---------- .../settings/Profile/DisplayNamePage.tsx | 5 ++--- src/types/form/SettingsStatusSetForm.ts | 13 +++++++++++- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 0735bc53e56c..f007246c6763 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -403,7 +403,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.MONEY_REQUEST_DATE_FORM]: FormTypes.Form; [ONYXKEYS.FORMS.NEW_CONTACT_METHOD_FORM]: FormTypes.Form; [ONYXKEYS.FORMS.WAYPOINT_FORM]: FormTypes.Form; - [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM]: FormTypes.Form; + [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM]: FormTypes.SettingsStatusSetForm; [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_DATE_FORM]: FormTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM]: FormTypes.Form; [ONYXKEYS.FORMS.PRIVATE_NOTES_FORM]: FormTypes.PrivateNotesForm; diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index 629f174c8a2b..61e93324bb16 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -1,12 +1,12 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx/lib/types'; import type {ValueOf} from 'type-fest'; import EmojiPickerButtonDropdown from '@components/EmojiPicker/EmojiPickerButtonDropdown'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {FormRef, OnyxFormValuesFields} from '@components/Form/types'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderPageLayout from '@components/HeaderPageLayout'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -26,15 +26,13 @@ import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/SettingsStatusSetForm'; import type {Status} from '@src/types/onyx/PersonalDetails'; -const INPUT_IDS = { - EMOJI_CODE: 'emojiCode', - STATUS_TEXT: 'statusText', -} as const; type StatusPageOnyxProps = { draftStatus: OnyxEntry; }; @@ -48,7 +46,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const formRef = useRef(null); + const formRef = useRef(null); const [brickRoadIndicator, setBrickRoadIndicator] = useState | '' | null>(''); const currentUserEmojiCode = currentUserPersonalDetails?.status?.emojiCode ?? ''; const currentUserStatusText = currentUserPersonalDetails?.status?.text ?? ''; @@ -76,7 +74,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) const navigateBackToPreviousScreen = useCallback(() => Navigation.goBack(), []); const updateStatus = useCallback( - (values: OnyxFormValuesFields) => { + (values: FormOnyxValues) => { const {emojiCode, statusText} = values; const clearAfterTime = draftClearAfter ?? currentUserClearAfter ?? CONST.CUSTOM_STATUS_TYPES.NEVER; const isValid = DateUtils.isTimeAtLeastOneMinuteInFuture({dateTimeString: clearAfterTime}); @@ -124,9 +122,9 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const validateForm = useCallback(() => { + const validateForm = useCallback((): FormInputErrors => { if (brickRoadIndicator) { - return {clearAfter: ''}; + return {clearAfter: '' as TranslationPaths}; } return {}; }, [brickRoadIndicator]); @@ -169,7 +167,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) /> ; @@ -42,8 +41,8 @@ function DisplayNamePage({isLoadingApp = true, currentUserPersonalDetails}: Disp const currentUserDetails = currentUserPersonalDetails ?? {}; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors: Errors = {}; + const validate = (values: FormOnyxValues) => { + const errors: FormInputErrors = {}; // First we validate the first name field if (!ValidationUtils.isValidDisplayName(values.firstName)) { diff --git a/src/types/form/SettingsStatusSetForm.ts b/src/types/form/SettingsStatusSetForm.ts index 9aeec26c4887..dcb0211ece6f 100644 --- a/src/types/form/SettingsStatusSetForm.ts +++ b/src/types/form/SettingsStatusSetForm.ts @@ -1,6 +1,17 @@ import type Form from './Form'; -type SettingsStatusSetForm = Form; +const INPUT_IDS = { + EMOJI_CODE: 'emojiCode', + STATUS_TEXT: 'statusText', + clearAfter: 'clearAfter', +} as const; + +type SettingsStatusSetForm = Form<{ + [INPUT_IDS.EMOJI_CODE]: string; + [INPUT_IDS.STATUS_TEXT]: string; + [INPUT_IDS.clearAfter]?: string; +}>; // eslint-disable-next-line import/prefer-default-export export type {SettingsStatusSetForm}; +export default INPUT_IDS; From 40cd54db77e7a89c46163a4ba0074fe5c7c114dc Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 15:39:01 +0100 Subject: [PATCH 014/556] Migrate ReimbursementAccount to ts --- .../RestartBankAccountSetupParams.ts | 6 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../deleteFromBankAccountList.ts | 16 ++++ .../actions/ReimbursementAccount/errors.ts | 44 ++++++++++ .../actions/ReimbursementAccount/index.ts | 63 ++++++++++++++ .../ReimbursementAccount/navigation.ts | 24 ++++++ .../resetFreePlanBankAccount.ts | 84 +++++++++++++++++++ .../actions/ReimbursementAccount/store.ts | 69 +++++++++++++++ 9 files changed, 309 insertions(+) create mode 100644 src/libs/API/parameters/RestartBankAccountSetupParams.ts create mode 100644 src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts create mode 100644 src/libs/actions/ReimbursementAccount/errors.ts create mode 100644 src/libs/actions/ReimbursementAccount/index.ts create mode 100644 src/libs/actions/ReimbursementAccount/navigation.ts create mode 100644 src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts create mode 100644 src/libs/actions/ReimbursementAccount/store.ts diff --git a/src/libs/API/parameters/RestartBankAccountSetupParams.ts b/src/libs/API/parameters/RestartBankAccountSetupParams.ts new file mode 100644 index 000000000000..b338eac0dea1 --- /dev/null +++ b/src/libs/API/parameters/RestartBankAccountSetupParams.ts @@ -0,0 +1,6 @@ +type RestartBankAccountSetupParams = { + bankAccountID: number; + ownerEmail: string; +}; + +export default RestartBankAccountSetupParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 482c5e0336c4..90b27d825580 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -2,6 +2,7 @@ export type {default as ActivatePhysicalExpensifyCardParams} from './ActivatePhy export type {default as AddNewContactMethodParams} from './AddNewContactMethodParams'; export type {default as AddPaymentCardParams} from './AddPaymentCardParams'; export type {default as AddPersonalBankAccountParams} from './AddPersonalBankAccountParams'; +export type {default as RestartBankAccountSetupParams} from './RestartBankAccountSetupParams'; export type {default as AddSchoolPrincipalParams} from './AddSchoolPrincipalParams'; export type {default as AuthenticatePusherParams} from './AuthenticatePusherParams'; export type {default as BankAccountHandlePlaidErrorParams} from './BankAccountHandlePlaidErrorParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index f5d99d8cf40e..4f811b85e709 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -76,6 +76,7 @@ const WRITE_COMMANDS = { ADD_ATTACHMENT: 'AddAttachment', CONNECT_BANK_ACCOUNT_WITH_PLAID: 'ConnectBankAccountWithPlaid', ADD_PERSONAL_BANK_ACCOUNT: 'AddPersonalBankAccount', + RESTART_BANK_ACCOUNT_SETUP: 'RestartBankAccountSetup', OPT_IN_TO_PUSH_NOTIFICATIONS: 'OptInToPushNotifications', OPT_OUT_OF_PUSH_NOTIFICATIONS: 'OptOutOfPushNotifications', RECONNECT_TO_REPORT: 'ReconnectToReport', @@ -213,6 +214,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ADD_ATTACHMENT]: Parameters.AddCommentOrAttachementParams; [WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_WITH_PLAID]: Parameters.ConnectBankAccountWithPlaidParams; [WRITE_COMMANDS.ADD_PERSONAL_BANK_ACCOUNT]: Parameters.AddPersonalBankAccountParams; + [WRITE_COMMANDS.RESTART_BANK_ACCOUNT_SETUP]: Parameters.RestartBankAccountSetupParams; [WRITE_COMMANDS.OPT_IN_TO_PUSH_NOTIFICATIONS]: Parameters.OptInOutToPushNotificationsParams; [WRITE_COMMANDS.OPT_OUT_OF_PUSH_NOTIFICATIONS]: Parameters.OptInOutToPushNotificationsParams; [WRITE_COMMANDS.RECONNECT_TO_REPORT]: Parameters.ReconnectToReportParams; diff --git a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts new file mode 100644 index 000000000000..d9a2dd130d62 --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts @@ -0,0 +1,16 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import * as store from './store'; + +/** + * Deletes a bank account from bankAccountList + */ +function deleteFromBankAccountList(bankAccountID: number) { + // We should delete the bankAccountID key from the bankAccountList object before setting it in Onyx + const bankAccountList = store.getBankAccountList(); + delete bankAccountList?.[bankAccountID]; + + Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, bankAccountList); +} + +export default deleteFromBankAccountList; diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts new file mode 100644 index 000000000000..c65da17690bb --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -0,0 +1,44 @@ +import Onyx from 'react-native-onyx'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; + +/** + * Set the current fields with errors. + */ +function setPersonalBankAccountFormValidationErrorFields(errors: Errors) { + // We set 'errors' to null first because we don't have a way yet to replace a specific property without merging it + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors: null}); + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors}); +} + +/** + * Set the current fields with errors. + + */ +function setBankAccountFormValidationErrors(errors: Errors) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors: null}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors}); +} + +/** + * Clear validation messages from reimbursement account + */ +function resetReimbursementAccount() { + setBankAccountFormValidationErrors({}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { + errors: null, + pendingAction: null, + }); +} + +/** + * Set the current error message. + */ +function showBankAccountFormValidationError(error: string | null) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { + errors: ErrorUtils.getMicroSecondOnyxError(error), + }); +} + +export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, showBankAccountFormValidationError, resetReimbursementAccount}; diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts new file mode 100644 index 000000000000..5c9bf1c822d1 --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -0,0 +1,63 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReimbursementAccountForm} from '@src/types/form'; +import type {BankAccountSubStep} from '@src/types/onyx/ReimbursementAccount'; +import deleteFromBankAccountList from './deleteFromBankAccountList'; +import resetFreePlanBankAccount from './resetFreePlanBankAccount'; + +export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute} from './navigation'; +export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, resetReimbursementAccount, showBankAccountFormValidationError} from './errors'; + +/** + * Set the current sub step in first step of adding withdrawal bank account: + * - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually + * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber + * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid + * + * @param subStep + * @returns + */ +function setBankAccountSubStep(subStep: BankAccountSubStep) { + return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); +} + +function hideBankAccountErrors() { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); +} + +function setWorkspaceIDForReimbursementAccount(workspaceID: string) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); +} + +/** + * @param bankAccountData + */ +function updateReimbursementAccountDraft(bankAccountData: ReimbursementAccountForm) { + Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); +} + +/** + * Triggers a modal to open allowing the user to reset their bank account + */ +function requestResetFreePlanBankAccount() { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: true}); +} + +/** + * Hides modal allowing the user to reset their bank account + */ +function cancelResetFreePlanBankAccount() { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: false}); +} + +export { + resetFreePlanBankAccount, + setBankAccountSubStep, + hideBankAccountErrors, + setWorkspaceIDForReimbursementAccount, + updateReimbursementAccountDraft, + requestResetFreePlanBankAccount, + cancelResetFreePlanBankAccount, + deleteFromBankAccountList, +}; diff --git a/src/libs/actions/ReimbursementAccount/navigation.ts b/src/libs/actions/ReimbursementAccount/navigation.ts new file mode 100644 index 000000000000..2c3eb7cf0384 --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/navigation.ts @@ -0,0 +1,24 @@ +import Onyx from 'react-native-onyx'; +import Navigation from '@libs/Navigation/Navigation'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {BankAccountStep} from '@src/types/onyx/ReimbursementAccount'; + +/** + * Navigate to a specific step in the VBA flow + */ +function goToWithdrawalAccountSetupStep(stepID: BankAccountStep) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {currentStep: stepID}}); +} + +/** + * Navigate to the correct bank account route based on the bank account state and type + * + * @param policyID - The policy ID associated with the bank account. + * @param [backTo=''] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. + */ +function navigateToBankAccountRoute(policyID: string, backTo?: string) { + Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyID, backTo)); +} + +export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute}; diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts new file mode 100644 index 000000000000..3cc34db0846f --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts @@ -0,0 +1,84 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import * as API from '@libs/API'; +import {WRITE_COMMANDS} from '@libs/API/types'; +import * as PlaidDataProps from '@pages/ReimbursementAccount/plaidDataPropTypes'; +import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; + +/** + * Reset user's reimbursement account. This will delete the bank account. + */ +function resetFreePlanBankAccount(bankAccountID: number, session: OnyxEntry) { + if (!bankAccountID) { + throw new Error('Missing bankAccountID when attempting to reset free plan bank account'); + } + if (!session?.email) { + throw new Error('Missing credentials when attempting to reset free plan bank account'); + } + + API.write( + WRITE_COMMANDS.RESTART_BANK_ACCOUNT_SETUP, + { + bankAccountID, + ownerEmail: session.email, + }, + { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + shouldShowResetModal: false, + isLoading: true, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + achData: null, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.ONFIDO_TOKEN, + value: '', + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.ONFIDO_APPLICANT_ID, + value: '', + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.PLAID_DATA, + value: PlaidDataProps.plaidDataDefaultProps, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.PLAID_LINK_TOKEN, + value: '', + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: ReimbursementAccountProps.reimbursementAccountDefaultProps, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, + value: {}, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: {isLoading: false, pendingAction: null}, + }, + ], + }, + ); +} + +export default resetFreePlanBankAccount; diff --git a/src/libs/actions/ReimbursementAccount/store.ts b/src/libs/actions/ReimbursementAccount/store.ts new file mode 100644 index 000000000000..bdceb4e2ad5d --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/store.ts @@ -0,0 +1,69 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import BankAccount from '@libs/models/BankAccount'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {ACHData} from '@src/types/onyx/ReimbursementAccount'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; + +/** Reimbursement account actively being set up */ +let reimbursementAccountInSetup: ACHData | EmptyObject = {}; +Onyx.connect({ + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + callback: (val) => { + reimbursementAccountInSetup = val?.achData ?? {}; + }, +}); + +let reimbursementAccountWorkspaceID: OnyxEntry = null; +Onyx.connect({ + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, + callback: (val) => { + reimbursementAccountWorkspaceID = val; + }, +}); + +let bankAccountList: OnyxEntry = null; +Onyx.connect({ + key: ONYXKEYS.BANK_ACCOUNT_LIST, + callback: (val) => { + bankAccountList = val; + }, +}); + +let credentials: OnyxEntry = null; +Onyx.connect({ + key: ONYXKEYS.CREDENTIALS, + callback: (val) => { + credentials = val; + }, +}); + +function getReimbursementAccountInSetup() { + return reimbursementAccountInSetup; +} + +function getBankAccountList() { + return bankAccountList; +} + +function hasCreditBankAccount() { + if (!bankAccountList) { + return false; + } + + Object.entries(bankAccountList).some(([, bankAccountJSON]) => { + const bankAccount = new BankAccount(bankAccountJSON); + return bankAccount.isDefaultCredit(); + }); +} + +function getCredentials() { + return credentials; +} + +function getReimbursementAccountWorkspaceID() { + return reimbursementAccountWorkspaceID; +} + +export {getReimbursementAccountInSetup, getBankAccountList, getCredentials, getReimbursementAccountWorkspaceID, hasCreditBankAccount}; From 6a886fefb7d026fb767ec41172b226e1dfad4842 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 15:39:51 +0100 Subject: [PATCH 015/556] Remove old js implementations --- .../deleteFromBankAccountList.js | 18 ---- .../actions/ReimbursementAccount/errors.js | 47 ----------- .../actions/ReimbursementAccount/index.js | 61 -------------- .../ReimbursementAccount/navigation.js | 25 ------ .../resetFreePlanBankAccount.js | 83 ------------------- .../actions/ReimbursementAccount/store.js | 63 -------------- 6 files changed, 297 deletions(-) delete mode 100644 src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js delete mode 100644 src/libs/actions/ReimbursementAccount/errors.js delete mode 100644 src/libs/actions/ReimbursementAccount/index.js delete mode 100644 src/libs/actions/ReimbursementAccount/navigation.js delete mode 100644 src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js delete mode 100644 src/libs/actions/ReimbursementAccount/store.js diff --git a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js deleted file mode 100644 index 6161066c1c69..000000000000 --- a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js +++ /dev/null @@ -1,18 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import * as store from './store'; - -/** - * Deletes a bank account from bankAccountList - * - * @param {Number} bankAccountID - */ -function deleteFromBankAccountList(bankAccountID) { - // We should delete the bankAccountID key from the bankAccountList object before setting it in Onyx - const bankAccountList = store.getBankAccountList(); - delete bankAccountList[bankAccountID]; - - Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, bankAccountList); -} - -export default deleteFromBankAccountList; diff --git a/src/libs/actions/ReimbursementAccount/errors.js b/src/libs/actions/ReimbursementAccount/errors.js deleted file mode 100644 index fd2eaf852bce..000000000000 --- a/src/libs/actions/ReimbursementAccount/errors.js +++ /dev/null @@ -1,47 +0,0 @@ -import Onyx from 'react-native-onyx'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** - * Set the current fields with errors. - * @param {Object} errorFields - */ -function setPersonalBankAccountFormValidationErrorFields(errorFields) { - // We set 'errorFields' to null first because we don't have a way yet to replace a specific property without merging it - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); -} - -/** - * Set the current fields with errors. - * - * @param {Object} errorFields - */ -function setBankAccountFormValidationErrors(errorFields) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields: null}); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields}); -} - -/** - * Clear validation messages from reimbursement account - */ -function resetReimbursementAccount() { - setBankAccountFormValidationErrors({}); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { - errors: null, - pendingAction: null, - }); -} - -/** - * Set the current error message. - * - * @param {String} error - */ -function showBankAccountFormValidationError(error) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { - errors: ErrorUtils.getMicroSecondOnyxError(error), - }); -} - -export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, showBankAccountFormValidationError, resetReimbursementAccount}; diff --git a/src/libs/actions/ReimbursementAccount/index.js b/src/libs/actions/ReimbursementAccount/index.js deleted file mode 100644 index 12b5b940a0f2..000000000000 --- a/src/libs/actions/ReimbursementAccount/index.js +++ /dev/null @@ -1,61 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import deleteFromBankAccountList from './deleteFromBankAccountList'; -import resetFreePlanBankAccount from './resetFreePlanBankAccount'; - -export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute} from './navigation'; -export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, resetReimbursementAccount, showBankAccountFormValidationError} from './errors'; - -/** - * Set the current sub step in first step of adding withdrawal bank account: - * - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually - * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber - * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid - * - * @param {String | null} subStep - * @returns {Promise} - */ -function setBankAccountSubStep(subStep) { - return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); -} - -function hideBankAccountErrors() { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); -} - -function setWorkspaceIDForReimbursementAccount(workspaceID) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); -} - -/** - * @param {Object} bankAccountData - */ -function updateReimbursementAccountDraft(bankAccountData) { - Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); -} - -/** - * Triggers a modal to open allowing the user to reset their bank account - */ -function requestResetFreePlanBankAccount() { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: true}); -} - -/** - * Hides modal allowing the user to reset their bank account - */ -function cancelResetFreePlanBankAccount() { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: false}); -} - -export { - resetFreePlanBankAccount, - setBankAccountSubStep, - hideBankAccountErrors, - setWorkspaceIDForReimbursementAccount, - updateReimbursementAccountDraft, - requestResetFreePlanBankAccount, - cancelResetFreePlanBankAccount, - deleteFromBankAccountList, -}; diff --git a/src/libs/actions/ReimbursementAccount/navigation.js b/src/libs/actions/ReimbursementAccount/navigation.js deleted file mode 100644 index 6c82561c16ee..000000000000 --- a/src/libs/actions/ReimbursementAccount/navigation.js +++ /dev/null @@ -1,25 +0,0 @@ -import Onyx from 'react-native-onyx'; -import Navigation from '@libs/Navigation/Navigation'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; - -/** - * Navigate to a specific step in the VBA flow - * - * @param {String} stepID - */ -function goToWithdrawalAccountSetupStep(stepID) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {currentStep: stepID}}); -} - -/** - * Navigate to the correct bank account route based on the bank account state and type - * - * @param {string} policyID - The policy ID associated with the bank account. - * @param {string} [backTo=''] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. - */ -function navigateToBankAccountRoute(policyID, backTo) { - Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyID, backTo)); -} - -export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute}; diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js deleted file mode 100644 index 962800fb2e55..000000000000 --- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js +++ /dev/null @@ -1,83 +0,0 @@ -import Onyx from 'react-native-onyx'; -import * as API from '@libs/API'; -import * as PlaidDataProps from '@pages/ReimbursementAccount/plaidDataPropTypes'; -import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** - * Reset user's reimbursement account. This will delete the bank account. - * @param {Number} bankAccountID - * @param {Object} session - */ -function resetFreePlanBankAccount(bankAccountID, session) { - if (!bankAccountID) { - throw new Error('Missing bankAccountID when attempting to reset free plan bank account'); - } - if (!session.email) { - throw new Error('Missing credentials when attempting to reset free plan bank account'); - } - - API.write( - 'RestartBankAccountSetup', - { - bankAccountID, - ownerEmail: session.email, - }, - { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - shouldShowResetModal: false, - isLoading: true, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - achData: null, - }, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.ONFIDO_TOKEN, - value: '', - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.ONFIDO_APPLICANT_ID, - value: '', - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.PLAID_DATA, - value: PlaidDataProps.plaidDataDefaultProps, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.PLAID_LINK_TOKEN, - value: '', - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: ReimbursementAccountProps.reimbursementAccountDefaultProps, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, - value: {}, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: {isLoading: false, pendingAction: null}, - }, - ], - }, - ); -} - -export default resetFreePlanBankAccount; diff --git a/src/libs/actions/ReimbursementAccount/store.js b/src/libs/actions/ReimbursementAccount/store.js deleted file mode 100644 index 4b8549b60b2e..000000000000 --- a/src/libs/actions/ReimbursementAccount/store.js +++ /dev/null @@ -1,63 +0,0 @@ -import lodashGet from 'lodash/get'; -import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import BankAccount from '@libs/models/BankAccount'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** Reimbursement account actively being set up */ -let reimbursementAccountInSetup = {}; -Onyx.connect({ - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - callback: (val) => { - reimbursementAccountInSetup = lodashGet(val, 'achData', {}); - }, -}); - -let reimbursementAccountWorkspaceID = null; -Onyx.connect({ - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, - callback: (val) => { - reimbursementAccountWorkspaceID = val; - }, -}); - -let bankAccountList = null; -Onyx.connect({ - key: ONYXKEYS.BANK_ACCOUNT_LIST, - callback: (val) => { - bankAccountList = val; - }, -}); - -let credentials; -Onyx.connect({ - key: ONYXKEYS.CREDENTIALS, - callback: (val) => { - credentials = val || {}; - }, -}); - -function getReimbursementAccountInSetup() { - return reimbursementAccountInSetup; -} - -function getBankAccountList() { - return bankAccountList; -} - -function hasCreditBankAccount() { - return _.some(bankAccountList, (bankAccountJSON) => { - const bankAccount = new BankAccount(bankAccountJSON); - return bankAccount.isDefaultCredit(); - }); -} - -function getCredentials() { - return credentials; -} - -function getReimbursementAccountWorkspaceID() { - return reimbursementAccountWorkspaceID; -} - -export {getReimbursementAccountInSetup, getBankAccountList, getCredentials, getReimbursementAccountWorkspaceID, hasCreditBankAccount}; From 8b6fb5e1e3639f3532c1183e953f416417c6151c Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 15:58:46 +0100 Subject: [PATCH 016/556] Add ErrorFields to PersonalBankAccount type --- src/libs/actions/ReimbursementAccount/errors.ts | 14 +++++++------- src/types/onyx/PersonalBankAccount.ts | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts index c65da17690bb..f85426f8d4fe 100644 --- a/src/libs/actions/ReimbursementAccount/errors.ts +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -1,24 +1,24 @@ import Onyx from 'react-native-onyx'; import * as ErrorUtils from '@libs/ErrorUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Errors} from '@src/types/onyx/OnyxCommon'; +import type {ErrorFields} from '@src/types/onyx/OnyxCommon'; /** * Set the current fields with errors. */ -function setPersonalBankAccountFormValidationErrorFields(errors: Errors) { +function setPersonalBankAccountFormValidationErrorFields(errorFields: ErrorFields) { // We set 'errors' to null first because we don't have a way yet to replace a specific property without merging it - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors: null}); - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors}); + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); } /** * Set the current fields with errors. */ -function setBankAccountFormValidationErrors(errors: Errors) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors: null}); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors}); +function setBankAccountFormValidationErrors(errorFields: ErrorFields) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields: null}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields}); } /** diff --git a/src/types/onyx/PersonalBankAccount.ts b/src/types/onyx/PersonalBankAccount.ts index 3714cc9f314b..3e52a3cf59f3 100644 --- a/src/types/onyx/PersonalBankAccount.ts +++ b/src/types/onyx/PersonalBankAccount.ts @@ -5,6 +5,9 @@ type PersonalBankAccount = { /** An error message to display to the user */ errors?: OnyxCommon.Errors; + /** Error objects keyed by field name containing errors keyed by microtime */ + errorFields?: OnyxCommon.ErrorFields; + /** Whether we should show the view that the bank account was successfully added */ shouldShowSuccess?: boolean; From 53fb383b51d8382c2cfc3e8fc9068adbddd82c07 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 16:03:55 +0100 Subject: [PATCH 017/556] Adjust types --- src/libs/actions/ReimbursementAccount/index.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts index 5c9bf1c822d1..416c5e956189 100644 --- a/src/libs/actions/ReimbursementAccount/index.ts +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -13,11 +13,8 @@ export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidation * - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid - * - * @param subStep - * @returns */ -function setBankAccountSubStep(subStep: BankAccountSubStep) { +function setBankAccountSubStep(subStep: BankAccountSubStep | null) { return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); } @@ -25,13 +22,10 @@ function hideBankAccountErrors() { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); } -function setWorkspaceIDForReimbursementAccount(workspaceID: string) { +function setWorkspaceIDForReimbursementAccount(workspaceID: string | null) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); } -/** - * @param bankAccountData - */ function updateReimbursementAccountDraft(bankAccountData: ReimbursementAccountForm) { Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); From dcc5adff694c41c31a48acfbe042bea0e72dac17 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 14 Feb 2024 16:07:13 +0000 Subject: [PATCH 018/556] refactor(typescript): migrate settings preferences --- src/libs/LocaleUtils.ts | 17 ++++++ .../{LanguagePage.js => LanguagePage.tsx} | 26 ++++----- ...PreferencesPage.js => PreferencesPage.tsx} | 39 ++++++-------- ...iorityModePage.js => PriorityModePage.tsx} | 54 ++++++++----------- .../{ThemePage.js => ThemePage.tsx} | 25 ++++----- 5 files changed, 76 insertions(+), 85 deletions(-) create mode 100644 src/libs/LocaleUtils.ts rename src/pages/settings/Preferences/{LanguagePage.js => LanguagePage.tsx} (53%) rename src/pages/settings/Preferences/{PreferencesPage.js => PreferencesPage.tsx} (87%) rename src/pages/settings/Preferences/{PriorityModePage.js => PriorityModePage.tsx} (50%) rename src/pages/settings/Preferences/{ThemePage.js => ThemePage.tsx} (71%) diff --git a/src/libs/LocaleUtils.ts b/src/libs/LocaleUtils.ts new file mode 100644 index 000000000000..2bcbb946c7c0 --- /dev/null +++ b/src/libs/LocaleUtils.ts @@ -0,0 +1,17 @@ +import type {ValueOf} from 'type-fest'; +import CONST from '@src/CONST'; + +const getLanguageFromLocale = (locale: ValueOf): (typeof CONST.LANGUAGES)[number] => { + switch (locale) { + case CONST.LOCALES.ES_ES: + case CONST.LOCALES.ES_ES_ONFIDO: + case CONST.LOCALES.ES: + return CONST.LOCALES.ES; + case CONST.LOCALES.EN: + return CONST.LOCALES.EN; + default: + return CONST.LOCALES.DEFAULT; + } +}; + +export default {getLanguageFromLocale}; diff --git a/src/pages/settings/Preferences/LanguagePage.js b/src/pages/settings/Preferences/LanguagePage.tsx similarity index 53% rename from src/pages/settings/Preferences/LanguagePage.js rename to src/pages/settings/Preferences/LanguagePage.tsx index ce93e94222b5..68ceeb0a1d81 100644 --- a/src/pages/settings/Preferences/LanguagePage.js +++ b/src/pages/settings/Preferences/LanguagePage.tsx @@ -1,27 +1,20 @@ -import PropTypes from 'prop-types'; import React from 'react'; -import _ from 'underscore'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; import * as App from '@userActions/App'; import CONST from '@src/CONST'; -const propTypes = { - ...withLocalizePropTypes, +function LanguagePage() { + const {translate, preferredLocale} = useLocalize(); - /** The preferred language of the App */ - preferredLocale: PropTypes.string.isRequired, -}; - -function LanguagePage(props) { - const localesToLanguages = _.map(CONST.LANGUAGES, (language) => ({ + const localesToLanguages = CONST.LANGUAGES.map((language) => ({ value: language, - text: props.translate(`languagePage.languages.${language}.label`), + text: translate(`languagePage.languages.${language}.label`), keyForList: language, - isSelected: props.preferredLocale === language, + isSelected: preferredLocale === language, })); return ( @@ -30,19 +23,18 @@ function LanguagePage(props) { testID={LanguagePage.displayName} > Navigation.goBack()} /> App.setLocaleAndNavigate(language.value)} - initiallyFocusedOptionKey={_.find(localesToLanguages, (locale) => locale.isSelected).keyForList} + initiallyFocusedOptionKey={localesToLanguages.find((locale) => locale.isSelected)?.keyForList} />
); } LanguagePage.displayName = 'LanguagePage'; -LanguagePage.propTypes = propTypes; -export default withLocalize(LanguagePage); +export default LanguagePage; diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.tsx similarity index 87% rename from src/pages/settings/Preferences/PreferencesPage.js rename to src/pages/settings/Preferences/PreferencesPage.tsx index 5ac78f6d20c6..4b93b330e6e9 100755 --- a/src/pages/settings/Preferences/PreferencesPage.js +++ b/src/pages/settings/Preferences/PreferencesPage.tsx @@ -1,8 +1,8 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; import {ScrollView, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Illustrations from '@components/Icon/Illustrations'; import LottieAnimations from '@components/LottieAnimations'; @@ -16,33 +16,28 @@ import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import LocaleUtils from '@libs/LocaleUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {User as UserType} from '@src/types/onyx'; -const propTypes = { +type PreferencesPageOnyxProps = { /** The chat priority mode */ - priorityMode: PropTypes.string, + priorityMode: OnyxEntry>; /** The app's color theme */ - preferredTheme: PropTypes.string, + preferredTheme: OnyxEntry>; /** The details about the user that is signed in */ - user: PropTypes.shape({ - /** Whether or not the user is subscribed to news updates */ - isSubscribedToNewsletter: PropTypes.bool, - }), + user: OnyxEntry; }; -const defaultProps = { - priorityMode: CONST.PRIORITY_MODE.DEFAULT, - preferredTheme: CONST.DEFAULT_THEME, - user: {}, -}; +type PreferencesPageProps = PreferencesPageOnyxProps; -function PreferencesPage(props) { +function PreferencesPage({priorityMode, preferredTheme, user}: PreferencesPageProps) { const styles = useThemeStyles(); const {isProduction} = useEnvironment(); const {translate, preferredLocale} = useLocalize(); @@ -85,7 +80,7 @@ function PreferencesPage(props) { @@ -97,28 +92,28 @@ function PreferencesPage(props) {
Navigation.navigate(ROUTES.SETTINGS_PRIORITY_MODE)} wrapperStyle={styles.sectionMenuItemTopDescription} /> Navigation.navigate(ROUTES.SETTINGS_LANGUAGE)} wrapperStyle={styles.sectionMenuItemTopDescription} /> Navigation.navigate(ROUTES.SETTINGS_THEME)} wrapperStyle={styles.sectionMenuItemTopDescription} @@ -144,11 +139,9 @@ function PreferencesPage(props) { ); } -PreferencesPage.propTypes = propTypes; -PreferencesPage.defaultProps = defaultProps; PreferencesPage.displayName = 'PreferencesPage'; -export default withOnyx({ +export default withOnyx({ priorityMode: { key: ONYXKEYS.NVP_PRIORITY_MODE, }, diff --git a/src/pages/settings/Preferences/PriorityModePage.js b/src/pages/settings/Preferences/PriorityModePage.tsx similarity index 50% rename from src/pages/settings/Preferences/PriorityModePage.js rename to src/pages/settings/Preferences/PriorityModePage.tsx index 983e3cb26746..e6c94c73021e 100644 --- a/src/pages/settings/Preferences/PriorityModePage.js +++ b/src/pages/settings/Preferences/PriorityModePage.tsx @@ -1,48 +1,45 @@ -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _, {compose} from 'underscore'; +import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -const propTypes = { +type PriorityModePageOnyxProps = { /** The chat priority mode */ - priorityMode: PropTypes.string, - - ...withLocalizePropTypes, + priorityMode: OnyxEntry>; }; -const defaultProps = { - priorityMode: CONST.PRIORITY_MODE.DEFAULT, -}; +type PriorityModePageProps = PriorityModePageOnyxProps; -function PriorityModePage(props) { +function PriorityModePage({priorityMode}: PriorityModePageProps) { + const {translate} = useLocalize(); const styles = useThemeStyles(); - const priorityModes = _.map(_.values(CONST.PRIORITY_MODE), (mode) => ({ + const priorityModes = Object.values(CONST.PRIORITY_MODE).map((mode) => ({ value: mode, - text: props.translate(`priorityModePage.priorityModes.${mode}.label`), - alternateText: props.translate(`priorityModePage.priorityModes.${mode}.description`), + text: translate(`priorityModePage.priorityModes.${mode}.label`), + alternateText: translate(`priorityModePage.priorityModes.${mode}.description`), keyForList: mode, - isSelected: props.priorityMode === mode, + isSelected: priorityMode === mode, })); const updateMode = useCallback( - (mode) => { - if (mode.value === props.priorityMode) { + (mode: (typeof priorityModes)[number]) => { + if (mode.value === priorityMode) { Navigation.goBack(); return; } User.updateChatPriorityMode(mode.value); }, - [props.priorityMode], + [priorityMode], ); return ( @@ -51,28 +48,23 @@ function PriorityModePage(props) { testID={PriorityModePage.displayName} > Navigation.goBack()} /> - {props.translate('priorityModePage.explainerText')} + {translate('priorityModePage.explainerText')} mode.isSelected).keyForList} + initiallyFocusedOptionKey={priorityModes.find((mode) => mode.isSelected)?.keyForList} />
); } PriorityModePage.displayName = 'PriorityModePage'; -PriorityModePage.propTypes = propTypes; -PriorityModePage.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - }, - }), -)(PriorityModePage); +export default withOnyx({ + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + }, +})(PriorityModePage); diff --git a/src/pages/settings/Preferences/ThemePage.js b/src/pages/settings/Preferences/ThemePage.tsx similarity index 71% rename from src/pages/settings/Preferences/ThemePage.js rename to src/pages/settings/Preferences/ThemePage.tsx index 4907056be761..4d89e600770b 100644 --- a/src/pages/settings/Preferences/ThemePage.js +++ b/src/pages/settings/Preferences/ThemePage.tsx @@ -1,7 +1,7 @@ -import PropTypes from 'prop-types'; import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; @@ -13,23 +13,22 @@ import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -const propTypes = { +type ThemePageOnyxProps = { /** The theme of the app */ - preferredTheme: PropTypes.string, + preferredTheme: OnyxEntry>; }; -const defaultProps = { - preferredTheme: CONST.THEME.DEFAULT, -}; +type ThemePageProps = ThemePageOnyxProps; -function ThemePage(props) { +function ThemePage({preferredTheme}: ThemePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const localesToThemes = _.map(_.values(_.omit(CONST.THEME, 'DEFAULT', 'FALLBACK')), (theme) => ({ + const {DEFAULT, FALLBACK, ...themes} = CONST.THEME; + const localesToThemes = Object.values(themes).map((theme) => ({ value: theme, text: translate(`themePage.themes.${theme}.label`), keyForList: theme, - isSelected: (props.preferredTheme || CONST.THEME.DEFAULT) === theme, + isSelected: (preferredTheme ?? CONST.THEME.DEFAULT) === theme, })); return ( @@ -49,17 +48,15 @@ function ThemePage(props) { User.updateTheme(theme.value)} - initiallyFocusedOptionKey={_.find(localesToThemes, (theme) => theme.isSelected).keyForList} + initiallyFocusedOptionKey={localesToThemes.find((theme) => theme.isSelected)?.keyForList} /> ); } ThemePage.displayName = 'ThemePage'; -ThemePage.propTypes = propTypes; -ThemePage.defaultProps = defaultProps; -export default withOnyx({ +export default withOnyx({ preferredTheme: { key: ONYXKEYS.PREFERRED_THEME, }, From dfd6b8bedea3616571ba853945abef2913504d35 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 15 Feb 2024 01:59:35 +0530 Subject: [PATCH 019/556] removed MoneyRequest route. Signed-off-by: Krishna Gupta --- src/ROUTES.ts | 6 ------ src/pages/iou/IOUCurrencySelection.js | 4 +--- src/pages/iou/request/step/IOURequestStepParticipants.js | 9 +++++---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 615d7c708d1d..691ff5c78551 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -260,12 +260,6 @@ const ROUTES = { route: 'r/:reportID/invite', getRoute: (reportID: string) => `r/${reportID}/invite` as const, }, - - // To see the available iouType, please refer to CONST.IOU.TYPE - MONEY_REQUEST: { - route: ':iouType/new/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}` as const, - }, MONEY_REQUEST_AMOUNT: { route: ':iouType/new/amount/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/amount/${reportID}` as const, diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 2a48897bfc85..50833534cb0e 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -71,8 +71,6 @@ function IOUCurrencySelection(props) { const [searchValue, setSearchValue] = useState(''); const optionsSelectorRef = useRef(); const selectedCurrencyCode = (lodashGet(props.route, 'params.currency', props.iou.currency) || CONST.CURRENCY.USD).toUpperCase(); - const iouType = lodashGet(props.route, 'params.iouType', CONST.IOU.TYPE.REQUEST); - const reportID = lodashGet(props.route, 'params.reportID', ''); const threadReportID = lodashGet(props.route, 'params.threadReportID', ''); // Decides whether to allow or disallow editing a money request @@ -161,7 +159,7 @@ function IOUCurrencySelection(props) { <> Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID))} + onBackButtonPress={() => Navigation.goBack(ROUTES.EDIT_REQUEST.getRoute(threadReportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} /> { + const headerTitle = useMemo(() => { if (iouType === CONST.IOU.TYPE.SPLIT) { return translate('iou.split'); } @@ -47,7 +47,8 @@ function IOURequestStepParticipants({ return translate('common.send'); } return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); - }; + }, [iouType, transaction, translate]); + const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); @@ -89,7 +90,7 @@ function IOURequestStepParticipants({ return ( Date: Thu, 15 Feb 2024 02:04:51 +0530 Subject: [PATCH 020/556] minor fix. Signed-off-by: Krishna Gupta --- src/pages/iou/IOUCurrencySelection.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 50833534cb0e..125b95046d67 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -17,7 +17,6 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import {iouDefaultProps, iouPropTypes} from './propTypes'; /** @@ -72,6 +71,7 @@ function IOUCurrencySelection(props) { const optionsSelectorRef = useRef(); const selectedCurrencyCode = (lodashGet(props.route, 'params.currency', props.iou.currency) || CONST.CURRENCY.USD).toUpperCase(); const threadReportID = lodashGet(props.route, 'params.threadReportID', ''); + const backTo = lodashGet(props.route, 'params.backTo', ''); // Decides whether to allow or disallow editing a money request useEffect(() => { @@ -96,7 +96,6 @@ function IOUCurrencySelection(props) { const confirmCurrencySelection = useCallback( (option) => { - const backTo = lodashGet(props.route, 'params.backTo', ''); Keyboard.dismiss(); // When we refresh the web, the money request route gets cleared from the navigation stack. @@ -108,7 +107,7 @@ function IOUCurrencySelection(props) { Navigation.navigate(`${props.route.params.backTo}?currency=${option.currencyCode}`); } }, - [props.route, props.navigation], + [props.route, props.navigation, backTo], ); const {translate, currencyList} = props; @@ -159,7 +158,7 @@ function IOUCurrencySelection(props) { <> Navigation.goBack(ROUTES.EDIT_REQUEST.getRoute(threadReportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} + onBackButtonPress={() => Navigation.goBack(backTo)} /> Date: Fri, 16 Feb 2024 12:59:10 +0100 Subject: [PATCH 021/556] ref: move SignInOrAvatarWithOptionalStatus to TS --- ...js => SignInOrAvatarWithOptionalStatus.tsx} | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) rename src/pages/home/sidebar/{SignInOrAvatarWithOptionalStatus.js => SignInOrAvatarWithOptionalStatus.tsx} (61%) diff --git a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx similarity index 61% rename from src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js rename to src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx index 0ea6195cd713..2a9356d78232 100644 --- a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js +++ b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx @@ -1,6 +1,3 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import * as Session from '@userActions/Session'; @@ -8,18 +5,13 @@ import AvatarWithOptionalStatus from './AvatarWithOptionalStatus'; import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; import SignInButton from './SignInButton'; -const propTypes = { - /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, +type SignInOrAvatarWithOptionalStatusProps = { + isCreateMenuOpen?: boolean; }; -const defaultProps = { - isCreateMenuOpen: false, -}; - -function SignInOrAvatarWithOptionalStatus({isCreateMenuOpen}) { +function SignInOrAvatarWithOptionalStatus({isCreateMenuOpen = false}: SignInOrAvatarWithOptionalStatusProps) { const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const emojiStatus = lodashGet(currentUserPersonalDetails, 'status.emojiCode', ''); + const emojiStatus = currentUserPersonalDetails.status?.emojiCode ?? ''; if (Session.isAnonymousUser()) { return ; @@ -35,7 +27,5 @@ function SignInOrAvatarWithOptionalStatus({isCreateMenuOpen}) { return ; } -SignInOrAvatarWithOptionalStatus.propTypes = propTypes; -SignInOrAvatarWithOptionalStatus.defaultProps = defaultProps; SignInOrAvatarWithOptionalStatus.displayName = 'SignInOrAvatarWithOptionalStatus'; export default SignInOrAvatarWithOptionalStatus; From 6bd5633ec4631b4a1ef76b03c5a7e576d7d15ca8 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 13:04:18 +0100 Subject: [PATCH 022/556] ref: move SignInButton to TS --- src/pages/home/sidebar/{SignInButton.js => SignInButton.tsx} | 1 - 1 file changed, 1 deletion(-) rename src/pages/home/sidebar/{SignInButton.js => SignInButton.tsx} (95%) diff --git a/src/pages/home/sidebar/SignInButton.js b/src/pages/home/sidebar/SignInButton.tsx similarity index 95% rename from src/pages/home/sidebar/SignInButton.js rename to src/pages/home/sidebar/SignInButton.tsx index f89deb6f65b2..1dc65bfd5050 100644 --- a/src/pages/home/sidebar/SignInButton.js +++ b/src/pages/home/sidebar/SignInButton.tsx @@ -1,4 +1,3 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ import React from 'react'; import {View} from 'react-native'; import Button from '@components/Button'; From 5ed0bc2c9fc59b181d3067e88b7c10c36e0ed497 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 13:05:47 +0100 Subject: [PATCH 023/556] ref: removed SidebarNavigationContext --- src/pages/home/sidebar/SidebarNavigationContext.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/pages/home/sidebar/SidebarNavigationContext.js diff --git a/src/pages/home/sidebar/SidebarNavigationContext.js b/src/pages/home/sidebar/SidebarNavigationContext.js deleted file mode 100644 index e69de29bb2d1..000000000000 From 5443274f38ae358c00e9013f8100f7195ebe77e5 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:19:53 +0100 Subject: [PATCH 024/556] ref: move SidebarLinksData to TS --- src/libs/ReportUtils.ts | 2 +- src/libs/SidebarUtils.ts | 14 +- ...debarLinksData.js => SidebarLinksData.tsx} | 296 +++++++----------- 3 files changed, 127 insertions(+), 185 deletions(-) rename src/pages/home/sidebar/{SidebarLinksData.js => SidebarLinksData.tsx} (53%) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b5da21c0f67e..40581f46e76a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3824,7 +3824,7 @@ function shouldReportBeInOptionList({ report: OnyxEntry; currentReportId: string; isInGSDMode: boolean; - betas: Beta[]; + betas: OnyxEntry; policies: OnyxCollection; excludeEmptyChats: boolean; doesReportHaveViolations: boolean; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 5fe646c5ad13..7b757d7fc5b8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -79,10 +79,10 @@ let hasInitialReportActions = false; */ function getOrderedReportIDs( currentReportId: string | null, - allReports: Record, - betas: Beta[], - policies: Record, - priorityMode: ValueOf, + allReports: OnyxEntry>, + betas: OnyxEntry, + policies: OnyxEntry>, + priorityMode: OnyxEntry>, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', @@ -110,7 +110,7 @@ function getOrderedReportIDs( const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReports); + const allReportsDictValues = Object.values(allReports ?? {}); // Filter out all the reports that shouldn't be displayed let reportsToDisplay = allReportsDictValues.filter((report) => { @@ -118,7 +118,7 @@ function getOrderedReportIDs( const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReportAction = parentReportActions?.find((action) => action && report && action?.reportActionID === report?.parentReportActionID); const doesReportHaveViolations = - betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); + betas?.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); return ReportUtils.shouldReportBeInOptionList({ report, currentReportId: currentReportId ?? '', @@ -126,7 +126,7 @@ function getOrderedReportIDs( betas, policies, excludeEmptyChats: true, - doesReportHaveViolations, + doesReportHaveViolations: !!doesReportHaveViolations, }); }); diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.tsx similarity index 53% rename from src/pages/home/sidebar/SidebarLinksData.js rename to src/pages/home/sidebar/SidebarLinksData.tsx index 3bd538e8beab..b128fcaf33ec 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -1,154 +1,114 @@ +import {useIsFocused} from '@react-navigation/native'; import {deepEqual} from 'fast-equals'; -import lodashGet from 'lodash/get'; -import lodashMap from 'lodash/map'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; -import withCurrentReportID from '@components/withCurrentReportID'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import withNavigationFocus from '@components/withNavigationFocus'; +import type {EdgeInsets} from 'react-native-safe-area-context'; +import type {ValueOf} from 'type-fest'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; +import useCurrentReportID from '@hooks/useCurrentReportID'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; -import reportPropTypes from '@pages/reportPropTypes'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import SidebarLinks, {basePropTypes} from './SidebarLinks'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Message} from '@src/types/onyx/ReportAction'; +import SidebarLinks from './SidebarLinks'; -const propTypes = { - ...basePropTypes, - - /* Onyx Props */ - /** List of reports */ - chatReports: PropTypes.objectOf(reportPropTypes), - - /** All report actions for all reports */ - - /** Object of report actions for this report */ - allReportActions: PropTypes.objectOf( - PropTypes.arrayOf( - PropTypes.shape({ - error: PropTypes.string, - message: PropTypes.arrayOf( - PropTypes.shape({ - moderationDecision: PropTypes.shape({ - decision: PropTypes.string, - }), - }), - ), - }), - ), - ), - - /** Whether the reports are loading. When false it means they are ready to be used. */ - isLoadingApp: PropTypes.bool, - - /** The chat priority mode */ - priorityMode: PropTypes.string, - - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - - network: networkPropTypes.isRequired, - - /** The policies which the user has access to */ - // eslint-disable-next-line react/forbid-prop-types - policies: PropTypes.object, - - // eslint-disable-next-line react/forbid-prop-types - policyMembers: PropTypes.object, - - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user accountID */ - accountID: PropTypes.number, - }), - /** All of the transaction violations */ - transactionViolations: PropTypes.shape({ - violations: PropTypes.arrayOf( - PropTypes.shape({ - /** The transaction ID */ - transactionID: PropTypes.number, - - /** The transaction violation type */ - type: PropTypes.string, - - /** The transaction violation message */ - message: PropTypes.string, - - /** The transaction violation data */ - data: PropTypes.shape({ - /** The transaction violation data field */ - field: PropTypes.string, - - /** The transaction violation data value */ - value: PropTypes.string, - }), - }), - ), - }), +type SidebarLinksDataOnyxProps = { + chatReports: OnyxEntry< + Pick< + OnyxTypes.Report, + | 'reportID' + | 'participantAccountIDs' + | 'hasDraft' + | 'isPinned' + | 'isHidden' + | 'notificationPreference' + | 'errorFields' + | 'lastMessageText' + | 'lastVisibleActionCreated' + | 'iouReportID' + | 'total' + | 'nonReimbursableTotal' + | 'hasOutstandingChildRequest' + | 'isWaitingOnBankAccount' + | 'statusNum' + | 'stateNum' + | 'chatType' + | 'type' + | 'policyID' + | 'visibility' + | 'lastReadTime' + | 'reportName' + | 'policyName' + | 'oldPolicyName' + | 'ownerAccountID' + | 'currency' + | 'managerID' + | 'parentReportActionID' + | 'parentReportID' + | 'isDeletedParentAction' + > & {isUnreadWithMention: boolean} + >; + isLoadingApp: OnyxEntry; + priorityMode: OnyxEntry>; + betas: OnyxEntry; + allReportActions: OnyxEntry>>; + policies: OnyxEntry>; + policyMembers: OnyxCollection; + transactionViolations: OnyxCollection; }; -const defaultProps = { - chatReports: {}, - allReportActions: {}, - isLoadingApp: true, - priorityMode: CONST.PRIORITY_MODE.DEFAULT, - betas: [], - policies: {}, - policyMembers: {}, - session: { - accountID: '', - }, - transactionViolations: {}, +type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { + onLinkClick: (reportID: number) => void; + insets: EdgeInsets; }; function SidebarLinksData({ - isFocused, allReportActions, betas, chatReports, - currentReportID, insets, - isLoadingApp, + isLoadingApp = true, onLinkClick, policies, - priorityMode, - network, + priorityMode = CONST.PRIORITY_MODE.DEFAULT, policyMembers, - session: {accountID}, + // session: {accountID}, transactionViolations, -}) { +}: SidebarLinksDataProps) { + const {currentReportID} = useCurrentReportID() ?? {}; + const {accountID} = useCurrentUserPersonalDetails(); + const network = useNetwork(); + const isFocused = useIsFocused(); const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); const prevPriorityMode = usePrevious(priorityMode); - const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, accountID); // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]); + useEffect(() => Policy.openWorkspace(activeWorkspaceID ?? '', policyMemberAccountIDs), [activeWorkspaceID]); - const reportIDsRef = useRef(null); + const reportIDsRef = useRef(null); const isLoading = isLoadingApp; const optionListItems = useMemo(() => { const reportIDs = SidebarUtils.getOrderedReportIDs( null, - chatReports, + chatReports as OnyxEntry>, betas, - policies, + policies as OnyxEntry>, priorityMode, - allReportActions, + allReportActions as OnyxEntry>, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -161,7 +121,7 @@ function SidebarLinksData({ // 1. We need to update existing reports only once while loading because they are updated several times during loading and causes this regression: https://github.com/Expensify/App/issues/24596#issuecomment-1681679531 // 2. If the user is offline, we need to update the reports unconditionally, since the loading of report data might be stuck in this case. // 3. Changing priority mode to Most Recent will call OpenApp. If there is an existing reports and the priority mode is updated, we want to immediately update the list instead of waiting the OpenApp request to complete - if (!isLoading || !reportIDsRef.current || network.isOffline || (reportIDsRef.current && prevPriorityMode !== priorityMode)) { + if (!isLoading || !reportIDsRef.current || !!network.isOffline || (reportIDsRef.current && prevPriorityMode !== priorityMode)) { reportIDsRef.current = reportIDs; } return reportIDsRef.current || []; @@ -173,14 +133,14 @@ function SidebarLinksData({ // the current report is missing from the list, which should very rarely happen. In this // case we re-generate the list a 2nd time with the current report included. const optionListItemsWithCurrentReport = useMemo(() => { - if (currentReportID && !_.contains(optionListItems, currentReportID)) { + if (currentReportID && !optionListItems?.includes(currentReportID)) { return SidebarUtils.getOrderedReportIDs( currentReportID, - chatReports, + chatReports as OnyxEntry>, betas, - policies, + policies as OnyxEntry>, priorityMode, - allReportActions, + allReportActions as OnyxEntry>, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -191,7 +151,7 @@ function SidebarLinksData({ const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; - const isActiveReport = useCallback((reportID) => currentReportIDRef.current === reportID, []); + const isActiveReport = useCallback((reportID: string) => currentReportIDRef.current === reportID, []); return ( +const chatReportSelector = (report: OnyxEntry) => report && { reportID: report.reportID, participantAccountIDs: report.participantAccountIDs, @@ -233,7 +190,7 @@ const chatReportSelector = (report) => isHidden: report.isHidden, notificationPreference: report.notificationPreference, errorFields: { - addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, + addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, }, lastMessageText: report.lastMessageText, lastVisibleActionCreated: report.lastVisibleActionCreated, @@ -264,78 +221,63 @@ const chatReportSelector = (report) => isUnreadWithMention: ReportUtils.isUnreadWithMention(report), }; -/** - * @param {Object} [reportActions] - * @returns {Object|undefined} - */ -const reportActionsSelector = (reportActions) => +const reportActionsSelector = (reportActions: OnyxEntry) => reportActions && - lodashMap(reportActions, (reportAction) => { - const {reportActionID, parentReportActionID, actionName, errors = []} = reportAction; - const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision'); + Object.values(reportActions).map((reportAction) => { + const {reportActionID, actionName, errors} = reportAction; + const decision = reportAction.message?.[0].moderationDecision?.decision; return { reportActionID, - parentReportActionID, actionName, errors, message: [ { moderationDecision: {decision}, }, - ], + ] as Message[], }; }); -/** - * @param {Object} [policy] - * @returns {Object|undefined} - */ -const policySelector = (policy) => +const policySelector = (policy: OnyxEntry) => policy && { type: policy.type, name: policy.name, avatar: policy.avatar, }; -export default compose( - withCurrentReportID, - withCurrentUserPersonalDetails, - withNavigationFocus, - withNetwork(), - withOnyx({ - chatReports: { - key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, - initialValue: {}, - }, - isLoadingApp: { - key: ONYXKEYS.IS_LOADING_APP, - }, - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - initialValue: CONST.PRIORITY_MODE.DEFAULT, - }, - betas: { - key: ONYXKEYS.BETAS, - initialValue: [], - }, - allReportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - selector: reportActionsSelector, - initialValue: {}, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, - initialValue: {}, - }, - policyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - initialValue: {}, - }, - }), -)(SidebarLinksData); +export default withOnyx({ + chatReports: { + key: ONYXKEYS.COLLECTION.REPORT, + selector: chatReportSelector, + initialValue: {}, + }, + isLoadingApp: { + key: ONYXKEYS.IS_LOADING_APP, + }, + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + initialValue: CONST.PRIORITY_MODE.DEFAULT, + }, + betas: { + key: ONYXKEYS.BETAS, + initialValue: [], + }, + allReportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + selector: reportActionsSelector, + initialValue: {}, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + selector: policySelector, + initialValue: {}, + }, + policyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + initialValue: {}, + }, +})(SidebarLinksData); From bc5cd4df25c3dbd9e8a07e14a8abc44d61ec32b0 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:46:36 +0100 Subject: [PATCH 025/556] ref: move SidebarLinks to TS --- src/components/LHNOptionsList/types.ts | 2 +- .../{SidebarLinks.js => SidebarLinks.tsx} | 64 +++++++++---------- src/pages/home/sidebar/SidebarLinksData.tsx | 4 +- 3 files changed, 32 insertions(+), 38 deletions(-) rename src/pages/home/sidebar/{SidebarLinks.js => SidebarLinks.tsx} (78%) diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 58bea97f04c9..f3d6bde9d41c 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -45,7 +45,7 @@ type CustomLHNOptionsListProps = { contentContainerStyles?: StyleProp; /** Sections for the section list */ - data: string[]; + data: string[] | null; /** Callback to fire when a row is selected */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.tsx similarity index 78% rename from src/pages/home/sidebar/SidebarLinks.js rename to src/pages/home/sidebar/SidebarLinks.tsx index 9431bae68d8a..52165c148727 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -1,10 +1,9 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {InteractionManager, StyleSheet, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {EdgeInsets} from 'react-native-safe-area-context'; +import type {ValueOf} from 'type-fest'; import Breadcrumbs from '@components/Breadcrumbs'; import LHNOptionsList from '@components/LHNOptionsList/LHNOptionsList'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; @@ -16,37 +15,33 @@ import KeyboardShortcut from '@libs/KeyboardShortcut'; import Navigation from '@libs/Navigation/Navigation'; import onyxSubscribe from '@libs/onyxSubscribe'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import safeAreaInsetPropTypes from '@pages/safeAreaInsetPropTypes'; import * as App from '@userActions/App'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Modal, Policy, Report} from '@src/types/onyx'; -const basePropTypes = { - /** Toggles the navigation menu open and closed */ - onLinkClick: PropTypes.func.isRequired, - - /** Safe area insets required for mobile devices margins */ - insets: safeAreaInsetPropTypes.isRequired, +type SidebarLinksOnyxProps = { + activePolicy: OnyxEntry; }; -const propTypes = { - ...basePropTypes, - - optionListItems: PropTypes.arrayOf(PropTypes.string).isRequired, - - isLoading: PropTypes.bool.isRequired, - - // eslint-disable-next-line react/require-default-props - priorityMode: PropTypes.oneOf(_.values(CONST.PRIORITY_MODE)), - - isActiveReport: PropTypes.func.isRequired, +type SidebarLinksProps = SidebarLinksOnyxProps & { + onLinkClick: () => void; + insets: EdgeInsets; + optionListItems: string[] | null; + isLoading: OnyxEntry; + priorityMode?: OnyxEntry>; + isActiveReport: (reportID: string) => boolean; + isCreateMenuOpen?: boolean; + + // eslint-disable-next-line react/no-unused-prop-types -- its used in withOnyx + activeWorkspaceID: string | undefined; }; -function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}) { +function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}: SidebarLinksProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const modal = useRef({}); + const modal = useRef({}); const {translate, updateLocale} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -67,7 +62,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority const unsubscribeOnyxModal = onyxSubscribe({ key: ONYXKEYS.MODAL, callback: (modalArg) => { - if (_.isNull(modalArg) || typeof modalArg !== 'object') { + if (modalArg === null || typeof modalArg !== 'object') { return; } modal.current = modalArg; @@ -105,18 +100,19 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority /** * Show Report page with selected report id - * - * @param {Object} option - * @param {String} option.reportID */ const showReportPage = useCallback( - (option) => { + (option: Report) => { // Prevent opening Report page when clicking LHN row quickly after clicking FAB icon // or when clicking the active LHN row on large screens // or when continuously clicking different LHNs, only apply to small screen // since getTopmostReportId always returns on other devices const reportActionID = Navigation.getTopmostReportActionId(); - if (isCreateMenuOpen || (option.reportID === Navigation.getTopmostReportId() && !reportActionID) || (isSmallScreenWidth && isActiveReport(option.reportID) && !reportActionID)) { + if ( + !!isCreateMenuOpen || + (option.reportID === Navigation.getTopmostReportId() && !reportActionID) || + (isSmallScreenWidth && isActiveReport(option.reportID) && !reportActionID) + ) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID)); @@ -137,7 +133,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority activePolicy ? { type: CONST.BREADCRUMB_TYPE.STRONG, - text: lodashGet(activePolicy, 'name', ''), + text: activePolicy.name ?? '', } : { type: CONST.BREADCRUMB_TYPE.ROOT, @@ -158,7 +154,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority optionMode={viewMode} onFirstItemRendered={App.setSidebarLoaded} /> - {isLoading && optionListItems.length === 0 && ( + {isLoading && optionListItems?.length === 0 && ( @@ -168,12 +164,10 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority ); } -SidebarLinks.propTypes = propTypes; SidebarLinks.displayName = 'SidebarLinks'; -export default withOnyx({ +export default withOnyx({ activePolicy: { key: ({activeWorkspaceID}) => `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`, }, })(SidebarLinks); -export {basePropTypes}; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index b128fcaf33ec..ddd232e99275 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -69,7 +69,7 @@ type SidebarLinksDataOnyxProps = { }; type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { - onLinkClick: (reportID: number) => void; + onLinkClick: () => void; insets: EdgeInsets; }; @@ -101,7 +101,7 @@ function SidebarLinksData({ const reportIDsRef = useRef(null); const isLoading = isLoadingApp; - const optionListItems = useMemo(() => { + const optionListItems: string[] | null = useMemo(() => { const reportIDs = SidebarUtils.getOrderedReportIDs( null, chatReports as OnyxEntry>, From 3fb3715847c5c82958147940cdcb68073456aedf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:54:54 +0100 Subject: [PATCH 026/556] ref: move PressableAvatarWithIndicator to TS --- ...or.js => PressableAvatarWithIndicator.tsx} | 52 ++++++------------- 1 file changed, 17 insertions(+), 35 deletions(-) rename src/pages/home/sidebar/{PressableAvatarWithIndicator.js => PressableAvatarWithIndicator.tsx} (56%) diff --git a/src/pages/home/sidebar/PressableAvatarWithIndicator.js b/src/pages/home/sidebar/PressableAvatarWithIndicator.tsx similarity index 56% rename from src/pages/home/sidebar/PressableAvatarWithIndicator.js rename to src/pages/home/sidebar/PressableAvatarWithIndicator.tsx index 63c5936e957b..e07b6e856823 100644 --- a/src/pages/home/sidebar/PressableAvatarWithIndicator.js +++ b/src/pages/home/sidebar/PressableAvatarWithIndicator.tsx @@ -1,44 +1,30 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import AvatarWithIndicator from '@components/AvatarWithIndicator'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as UserUtils from '@libs/UserUtils'; -import personalDetailsPropType from '@pages/personalDetailsPropType'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -const propTypes = { - /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, - - /** The personal details of the person who is logged in */ - currentUserPersonalDetails: personalDetailsPropType, - +type PressableAvatarWithIndicatorOnyxProps = { /** Indicates whether the app is loading initial data */ - isLoading: PropTypes.bool, + isLoading: OnyxEntry; }; -const defaultProps = { - isCreateMenuOpen: false, - currentUserPersonalDetails: { - pendingFields: {avatar: ''}, - accountID: '', - avatar: '', - }, - isLoading: true, +type PressableAvatarWithIndicatorProps = PressableAvatarWithIndicatorOnyxProps & { + /** Whether the create menu is open or not */ + isCreateMenuOpen: boolean; }; -function PressableAvatarWithIndicator({isCreateMenuOpen, currentUserPersonalDetails, isLoading}) { +function PressableAvatarWithIndicator({isCreateMenuOpen = false, isLoading = true}: PressableAvatarWithIndicatorProps) { const {translate} = useLocalize(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const showSettingsPage = useCallback(() => { if (isCreateMenuOpen) { @@ -55,26 +41,22 @@ function PressableAvatarWithIndicator({isCreateMenuOpen, currentUserPersonalDeta role={CONST.ROLE.BUTTON} onPress={showSettingsPage} > - + ); } -PressableAvatarWithIndicator.propTypes = propTypes; -PressableAvatarWithIndicator.defaultProps = defaultProps; PressableAvatarWithIndicator.displayName = 'PressableAvatarWithIndicator'; -export default compose( - withCurrentUserPersonalDetails, - withOnyx({ - isLoading: { - key: ONYXKEYS.IS_LOADING_APP, - }, - }), -)(PressableAvatarWithIndicator); + +export default withOnyx({ + isLoading: { + key: ONYXKEYS.IS_LOADING_APP, + }, +})(PressableAvatarWithIndicator); From 5fa814c327d1e9767afa896e3304487885039547 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:57:26 +0100 Subject: [PATCH 027/556] ref: move AvatarWithOptionalStatus to TS --- ...alStatus.js => AvatarWithOptionalStatus.tsx} | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) rename src/pages/home/sidebar/{AvatarWithOptionalStatus.js => AvatarWithOptionalStatus.tsx} (81%) diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.js b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx similarity index 81% rename from src/pages/home/sidebar/AvatarWithOptionalStatus.js rename to src/pages/home/sidebar/AvatarWithOptionalStatus.tsx index e1ff3982a0cc..5597d46c29bc 100644 --- a/src/pages/home/sidebar/AvatarWithOptionalStatus.js +++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx @@ -1,5 +1,3 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; @@ -12,20 +10,15 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; -const propTypes = { +type AvatarWithOptionalStatusProps = { /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, + isCreateMenuOpen: boolean; /** Emoji status */ - emojiStatus: PropTypes.string, + emojiStatus: string; }; -const defaultProps = { - isCreateMenuOpen: false, - emojiStatus: '', -}; - -function AvatarWithOptionalStatus({emojiStatus, isCreateMenuOpen}) { +function AvatarWithOptionalStatus({emojiStatus = '', isCreateMenuOpen = false}: AvatarWithOptionalStatusProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -61,7 +54,5 @@ function AvatarWithOptionalStatus({emojiStatus, isCreateMenuOpen}) { ); } -AvatarWithOptionalStatus.propTypes = propTypes; -AvatarWithOptionalStatus.defaultProps = defaultProps; AvatarWithOptionalStatus.displayName = 'AvatarWithOptionalStatus'; export default AvatarWithOptionalStatus; From 6733581a39b700a147c1708f606cdc6fede9b1ee Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 17:02:16 +0100 Subject: [PATCH 028/556] ref: move BaseSidebarScreen to TS --- src/pages/home/sidebar/SidebarLinks.tsx | 2 +- src/pages/home/sidebar/SidebarLinksData.tsx | 2 +- .../{BaseSidebarScreen.js => BaseSidebarScreen.tsx} | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) rename src/pages/home/sidebar/SidebarScreen/{BaseSidebarScreen.js => BaseSidebarScreen.tsx} (94%) diff --git a/src/pages/home/sidebar/SidebarLinks.tsx b/src/pages/home/sidebar/SidebarLinks.tsx index 52165c148727..f01ed07ea476 100644 --- a/src/pages/home/sidebar/SidebarLinks.tsx +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -27,7 +27,7 @@ type SidebarLinksOnyxProps = { type SidebarLinksProps = SidebarLinksOnyxProps & { onLinkClick: () => void; - insets: EdgeInsets; + insets: EdgeInsets | undefined; optionListItems: string[] | null; isLoading: OnyxEntry; priorityMode?: OnyxEntry>; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index ddd232e99275..ab84f69e824b 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -70,7 +70,7 @@ type SidebarLinksDataOnyxProps = { type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { onLinkClick: () => void; - insets: EdgeInsets; + insets: EdgeInsets | undefined; }; function SidebarLinksData({ diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx similarity index 94% rename from src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js rename to src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 9188a859d175..314b3921cc0b 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -17,7 +17,7 @@ const startTimer = () => { Performance.markStart(CONST.TIMING.SWITCH_REPORT); }; -function BaseSidebarScreen(props) { +function BaseSidebarScreen() { const styles = useThemeStyles(); useEffect(() => { Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); @@ -37,7 +37,6 @@ function BaseSidebarScreen(props) { )} From add3ffa070a77cd3c821ea7a99d628b7d965ad19 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 18 Feb 2024 00:35:04 +0200 Subject: [PATCH 029/556] converting statuspage to ts --- src/components/Form/FormProvider.tsx | 4 ++-- src/components/TextInput/BaseTextInput/types.ts | 5 ++++- src/pages/settings/Profile/CustomStatus/StatusPage.tsx | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 594d07f887d6..1fbc95e74a15 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -1,5 +1,5 @@ import lodashIsEqual from 'lodash/isEqual'; -import type {ForwardedRef, MutableRefObject, ReactNode} from 'react'; +import type {ForwardedRef, MutableRefObject, ReactNode, RefAttributes} from 'react'; import React, {createRef, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import type {NativeSyntheticEvent, StyleProp, TextInputSubmitEditingEventData, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -389,4 +389,4 @@ export default withOnyx({ // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any key: (props) => `${props.formID}Draft` as any, }, -})(forwardRef(FormProvider)) as (props: Omit, keyof FormProviderOnyxProps>) => ReactNode; +})(forwardRef(FormProvider)) as (props: Omit & RefAttributes, keyof FormProviderOnyxProps>) => ReactNode; diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index ce0f0e126252..66d29e2705a5 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -1,4 +1,4 @@ -import type {GestureResponderEvent, StyleProp, TextInputProps, TextStyle, ViewStyle} from 'react-native'; +import type {GestureResponderEvent, Role, 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'; @@ -108,6 +108,9 @@ type CustomBaseTextInputProps = { /** Type of autocomplete */ autoCompleteType?: string; + + /** Keyboard type */ + role?: Role; }; type BaseTextInputRef = HTMLFormElement | AnimatedTextInputRef; diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index 61e93324bb16..dc9709126064 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -6,7 +6,7 @@ import type {ValueOf} from 'type-fest'; import EmojiPickerButtonDropdown from '@components/EmojiPicker/EmojiPickerButtonDropdown'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import type {FormInputErrors, FormOnyxValues, FormRef} from '@components/Form/types'; import HeaderPageLayout from '@components/HeaderPageLayout'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -46,7 +46,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const formRef = useRef(null); + const formRef = useRef(null); const [brickRoadIndicator, setBrickRoadIndicator] = useState | '' | null>(''); const currentUserEmojiCode = currentUserPersonalDetails?.status?.emojiCode ?? ''; const currentUserStatusText = currentUserPersonalDetails?.status?.text ?? ''; @@ -160,6 +160,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) Date: Sun, 18 Feb 2024 01:00:58 +0200 Subject: [PATCH 030/556] migrating pronouns --- .../{PronounsPage.js => PronounsPage.tsx} | 82 +++++++++---------- 1 file changed, 37 insertions(+), 45 deletions(-) rename src/pages/settings/Profile/{PronounsPage.js => PronounsPage.tsx} (57%) diff --git a/src/pages/settings/Profile/PronounsPage.js b/src/pages/settings/Profile/PronounsPage.tsx similarity index 57% rename from src/pages/settings/Profile/PronounsPage.js rename to src/pages/settings/Profile/PronounsPage.tsx index 5bb528373e8f..19578c7dd198 100644 --- a/src/pages/settings/Profile/PronounsPage.js +++ b/src/pages/settings/Profile/PronounsPage.tsx @@ -1,48 +1,46 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useEffect, useMemo, useState} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import Text from '@components/Text'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -const propTypes = { - ...withCurrentUserPersonalDetailsPropTypes, +type PronounsListType = (typeof CONST.PRONOUNS_LIST)[number]; - /** Indicates whether the app is loading initial data */ - isLoadingApp: PropTypes.bool, +type PronounEntry = { + text: string; + value: string; + keyForList: PronounsListType; + isSelected: boolean; }; -const defaultProps = { - ...withCurrentUserPersonalDetailsDefaultProps, - isLoadingApp: true, +type PronounsPageOnyxProps = { + isLoadingApp: OnyxEntry; }; +type PronounsPageProps = PronounsPageOnyxProps & WithCurrentUserPersonalDetailsProps; -function PronounsPage({currentUserPersonalDetails, isLoadingApp}) { +function PronounsPage({currentUserPersonalDetails, isLoadingApp = true}: PronounsPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const currentPronouns = lodashGet(currentUserPersonalDetails, 'pronouns', ''); + const currentPronouns = currentUserPersonalDetails?.pronouns ?? ''; const currentPronounsKey = currentPronouns.substring(CONST.PRONOUNS.PREFIX.length); const [searchValue, setSearchValue] = useState(''); useEffect(() => { - if (isLoadingApp && _.isUndefined(currentUserPersonalDetails.pronouns)) { + if (isLoadingApp && !currentUserPersonalDetails.pronouns) { return; } - const currentPronounsText = _.chain(CONST.PRONOUNS_LIST) - .find((_value) => _value === currentPronounsKey) - .value(); + const currentPronounsText = CONST.PRONOUNS_LIST.find((value) => value === currentPronounsKey); setSearchValue(currentPronounsText ? translate(`pronouns.${currentPronounsText}`) : ''); @@ -50,34 +48,31 @@ function PronounsPage({currentUserPersonalDetails, isLoadingApp}) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoadingApp]); - const filteredPronounsList = useMemo(() => { - const pronouns = _.chain(CONST.PRONOUNS_LIST) - .map((value) => { - const fullPronounKey = `${CONST.PRONOUNS.PREFIX}${value}`; - const isCurrentPronouns = fullPronounKey === currentPronouns; - - return { - text: translate(`pronouns.${value}`), - value: fullPronounKey, - keyForList: value, - isSelected: isCurrentPronouns, - }; - }) - .sortBy((pronoun) => pronoun.text.toLowerCase()) - .value(); + const filteredPronounsList = useMemo((): PronounEntry[] => { + const pronouns = CONST.PRONOUNS_LIST.map((value) => { + const fullPronounKey = `${CONST.PRONOUNS.PREFIX}${value}`; + const isCurrentPronouns = fullPronounKey === currentPronouns; + + return { + text: translate(`pronouns.${value}`), + value: fullPronounKey, + keyForList: value, + isSelected: isCurrentPronouns, + }; + }).sort((a, b) => a.text.toLowerCase().localeCompare(b.text.toLowerCase())); const trimmedSearch = searchValue.trim(); if (trimmedSearch.length === 0) { return []; } - return _.filter(pronouns, (pronoun) => pronoun.text.toLowerCase().indexOf(trimmedSearch.toLowerCase()) >= 0); + return pronouns.filter((pronoun) => pronoun.text.toLowerCase().indexOf(trimmedSearch.toLowerCase()) >= 0); }, [searchValue, currentPronouns, translate]); - const headerMessage = searchValue.trim() && filteredPronounsList.length === 0 ? translate('common.noResultsFound') : ''; + const headerMessage = searchValue.trim() && filteredPronounsList?.length === 0 ? translate('common.noResultsFound') : ''; - const updatePronouns = (selectedPronouns) => { - PersonalDetails.updatePronouns(selectedPronouns.keyForList === currentPronounsKey ? '' : lodashGet(selectedPronouns, 'value', '')); + const updatePronouns = (selectedPronouns: PronounEntry) => { + PersonalDetails.updatePronouns(selectedPronouns.keyForList === currentPronounsKey ? '' : selectedPronouns?.value ?? ''); }; return ( @@ -85,7 +80,7 @@ function PronounsPage({currentUserPersonalDetails, isLoadingApp}) { includeSafeAreaPaddingBottom={false} testID={PronounsPage.displayName} > - {isLoadingApp && _.isUndefined(currentUserPersonalDetails.pronouns) ? ( + {isLoadingApp && currentUserPersonalDetails.pronouns ? ( ) : ( <> @@ -110,15 +105,12 @@ function PronounsPage({currentUserPersonalDetails, isLoadingApp}) { ); } -PronounsPage.propTypes = propTypes; -PronounsPage.defaultProps = defaultProps; PronounsPage.displayName = 'PronounsPage'; -export default compose( - withCurrentUserPersonalDetails, - withOnyx({ +export default withCurrentUserPersonalDetails( + withOnyx({ isLoadingApp: { key: ONYXKEYS.IS_LOADING_APP, }, - }), -)(PronounsPage); + })(PronounsPage), +); From fa93bf376407e010771f2f0fa8036c6f7f6149f8 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 18 Feb 2024 01:04:02 +0200 Subject: [PATCH 031/556] minor edit --- src/pages/settings/Profile/CustomStatus/StatusPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index dc9709126064..ee400fb9a828 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -83,7 +83,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) return; } User.updateCustomStatus({ - text: values.statusText, + text: statusText, emojiCode: !emojiCode && statusText ? initialEmoji : emojiCode, clearAfter: clearAfterTime !== CONST.CUSTOM_STATUS_TYPES.NEVER ? clearAfterTime : '', }); From 038be0d072d480a822911d5577b4c77321f8c053 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 18 Feb 2024 01:11:56 +0200 Subject: [PATCH 032/556] Minor --- src/pages/settings/Profile/LoungeAccessPage.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Profile/LoungeAccessPage.tsx b/src/pages/settings/Profile/LoungeAccessPage.tsx index 81df868c5565..ca3cd32c4398 100644 --- a/src/pages/settings/Profile/LoungeAccessPage.tsx +++ b/src/pages/settings/Profile/LoungeAccessPage.tsx @@ -1,6 +1,6 @@ import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx/lib/types'; import IllustratedHeaderPageLayout from '@components/IllustratedHeaderPageLayout'; import LottieAnimations from '@components/LottieAnimations'; import Text from '@components/Text'; @@ -11,7 +11,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type {User} from '@src/types/onyx'; type LoungeAccessPageOnyxProps = { @@ -31,7 +30,7 @@ function LoungeAccessPage({user}: LoungeAccessPageProps) { return ( Navigation.goBack(ROUTES)} + onBackButtonPress={() => Navigation.goBack()} illustration={LottieAnimations.ExpensifyLounge} testID={LoungeAccessPage.displayName} > From 81c69a09d0eaa07b5d03257651ca84138da44589 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 18 Feb 2024 01:56:28 +0200 Subject: [PATCH 033/556] Minor edit --- src/pages/settings/Profile/CustomStatus/StatusPage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index ee400fb9a828..d7a8abad470b 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -74,8 +74,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) const navigateBackToPreviousScreen = useCallback(() => Navigation.goBack(), []); const updateStatus = useCallback( - (values: FormOnyxValues) => { - const {emojiCode, statusText} = values; + ({emojiCode, statusText}: FormOnyxValues) => { const clearAfterTime = draftClearAfter ?? currentUserClearAfter ?? CONST.CUSTOM_STATUS_TYPES.NEVER; const isValid = DateUtils.isTimeAtLeastOneMinuteInFuture({dateTimeString: clearAfterTime}); if (!isValid && clearAfterTime !== CONST.CUSTOM_STATUS_TYPES.NEVER) { From 5f96f129d21a76c25b482bddfb04e0bcd3649ca3 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 18 Feb 2024 02:04:48 +0200 Subject: [PATCH 034/556] move clearDraftCustomStatus inside runAfterInteractions --- src/pages/settings/Profile/CustomStatus/StatusPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index d7a8abad470b..7bc440c97ae5 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -87,8 +87,8 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) clearAfter: clearAfterTime !== CONST.CUSTOM_STATUS_TYPES.NEVER ? clearAfterTime : '', }); - User.clearDraftCustomStatus(); InteractionManager.runAfterInteractions(() => { + User.clearDraftCustomStatus(); navigateBackToPreviousScreen(); }); }, From bc076b5e6a38acf273ea46721ecad6c18dc39aa9 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Sun, 18 Feb 2024 23:02:11 +0530 Subject: [PATCH 035/556] Increase editing space for recovery code --- src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index 4afba77b77b5..7cbf05dba783 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -324,7 +324,7 @@ function BaseValidateCodeForm(props) { accessibilityLabel={props.translate('recoveryCodeForm.recoveryCode')} value={recoveryCode} onChangeText={(text) => onTextInput(text, 'recoveryCode')} - maxLength={CONST.RECOVERY_CODE_LENGTH} + maxLength={CONST.FORM_CHARACTER_LIMIT} label={props.translate('recoveryCodeForm.recoveryCode')} errorText={formError.recoveryCode || ''} hasError={hasError} From 04fb21000447ec284980392bd55ff7fade561a9a Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Sun, 18 Feb 2024 23:16:23 +0530 Subject: [PATCH 036/556] Add logic to trim text on input --- src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index 7cbf05dba783..2bc90efaceb5 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -179,7 +179,7 @@ function BaseValidateCodeForm(props) { setInput = setRecoveryCode; } - setInput(text); + setInput(text.trim()); setFormError((prevError) => ({...prevError, [key]: ''})); if (props.account.errors) { From 2fad64eddb709a5bdde140f53ce65beb63b64578 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 20 Feb 2024 10:02:39 +0100 Subject: [PATCH 037/556] ref: move FloatingActionButtonAndPopover to TS --- src/libs/actions/Policy.ts | 6 +- src/libs/actions/Task.ts | 2 +- ....js => FloatingActionButtonAndPopover.tsx} | 139 +++++++----------- 3 files changed, 58 insertions(+), 89 deletions(-) rename src/pages/home/sidebar/SidebarScreen/{FloatingActionButtonAndPopover.js => FloatingActionButtonAndPopover.tsx} (70%) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 9e6745bbc291..911837cd3bf5 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -81,7 +81,7 @@ type OptimisticCustomUnits = { outputCurrency: string; }; -type PoliciesRecord = Record>; +type PoliciesRecord = Record; type NewCustomUnit = { customUnitID: string; @@ -206,8 +206,8 @@ function updateLastAccessedWorkspace(policyID: OnyxEntry) { /** * Check if the user has any active free policies (aka workspaces) */ -function hasActiveFreePolicy(policies: Array> | PoliciesRecord): boolean { - const adminFreePolicies = Object.values(policies).filter((policy) => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN); +function hasActiveFreePolicy(policies: OnyxEntry): boolean { + const adminFreePolicies = Object.values(policies ?? {}).filter((policy) => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN); if (adminFreePolicies.length === 0) { return false; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 28cecf460a5f..b05e7cd6b2d9 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -645,7 +645,7 @@ function setParentReportID(parentReportID: string) { /** * Clears out the task info from the store and navigates to the NewTaskDetails page */ -function clearOutTaskInfoAndNavigate(reportID: string) { +function clearOutTaskInfoAndNavigate(reportID?: string) { clearOutTaskInfo(); if (reportID && reportID !== '0') { setParentReportID(reportID); diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx similarity index 70% rename from src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js rename to src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 0df490fa4466..83cc719f2e90 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,17 +1,16 @@ -import PropTypes from 'prop-types'; +import {useIsFocused, useNavigation} from '@react-navigation/native'; +import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FloatingActionButton from '@components/FloatingActionButton'; import * as Expensicons from '@components/Icon/Expensicons'; import PopoverMenu from '@components/PopoverMenu'; -import withNavigation from '@components/withNavigation'; -import withNavigationFocus from '@components/withNavigationFocus'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; @@ -22,74 +21,53 @@ import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; -/** - * @param {Object} [policy] - * @returns {Object|undefined} - */ -const policySelector = (policy) => - policy && { - type: policy.type, - role: policy.role, - isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, - pendingAction: policy.pendingAction, - }; - -const propTypes = { - ...windowDimensionsPropTypes, +type FloatingActionButtonAndPopoverOnyxProps = { + /** The list of policies the user has access to. */ + allPolicies: OnyxEntry>; + isLoading: OnyxEntry; +}; +type FloatingActionButtonAndPopoverProps = FloatingActionButtonAndPopoverOnyxProps & { /* Callback function when the menu is shown */ - onShowCreateMenu: PropTypes.func, + onShowCreateMenu: () => void; /* Callback function before the menu is hidden */ - onHideCreateMenu: PropTypes.func, - - /** The list of policies the user has access to. */ - allPolicies: PropTypes.shape({ - /** The policy name */ - name: PropTypes.string, - }), - - /** Indicated whether the report data is loading */ - isLoading: PropTypes.bool, - - /** Forwarded ref to FloatingActionButtonAndPopover */ - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + onHideCreateMenu: () => void; }; -const defaultProps = { - onHideCreateMenu: () => {}, - onShowCreateMenu: () => {}, - allPolicies: {}, - isLoading: false, - innerRef: null, + +type FloatingActionButtonAndPopoverRef = { + hideCreateMenu: () => void; }; /** * Responsible for rendering the {@link PopoverMenu}, and the accompanying * FAB that can open or close the menu. - * @param {Object} props - * @returns {JSX.Element} */ -function FloatingActionButtonAndPopover(props) { +function FloatingActionButtonAndPopover( + {onHideCreateMenu = () => {}, onShowCreateMenu = () => {}, isLoading, allPolicies}: FloatingActionButtonAndPopoverProps, + ref: ForwardedRef, +) { + console.log('allPolices', allPolicies); const styles = useThemeStyles(); const {translate} = useLocalize(); const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); - const fabRef = useRef(null); + const fabRef = useRef(null); + const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); + const isFocused = useIsFocused(); - const prevIsFocused = usePrevious(props.isFocused); + const prevIsFocused = usePrevious(isFocused); /** * Check if LHN status changed from active to inactive. * Used to close already opened FAB menu when open any other pages (i.e. Press Command + K on web). - * - * @param {Object} prevProps - * @return {Boolean} */ const didScreenBecomeInactive = useCallback( () => // When any other page is opened over LHN - !props.isFocused && prevIsFocused, - [props.isFocused, prevIsFocused], + !isFocused && prevIsFocused, + [isFocused, prevIsFocused], ); /** @@ -97,14 +75,14 @@ function FloatingActionButtonAndPopover(props) { */ const showCreateMenu = useCallback( () => { - if (!props.isFocused && props.isSmallScreenWidth) { + if (!isFocused && isSmallScreenWidth) { return; } setIsCreateMenuActive(true); - props.onShowCreateMenu(); + onShowCreateMenu(); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [props.isFocused, props.isSmallScreenWidth], + [isFocused, isSmallScreenWidth], ); /** @@ -118,7 +96,7 @@ function FloatingActionButtonAndPopover(props) { return; } setIsCreateMenuActive(false); - props.onHideCreateMenu(); + onHideCreateMenu(); }, // eslint-disable-next-line react-hooks/exhaustive-deps [isCreateMenuActive], @@ -133,7 +111,7 @@ function FloatingActionButtonAndPopover(props) { hideCreateMenu(); }, [didScreenBecomeInactive, hideCreateMenu]); - useImperativeHandle(props.innerRef, () => ({ + useImperativeHandle(ref, () => ({ hideCreateMenu() { hideCreateMenu(); }, @@ -151,10 +129,10 @@ function FloatingActionButtonAndPopover(props) { interceptAnonymousUser(() => Navigation.navigate(ROUTES.TEACHERS_UNITE)), }, - ...(!props.isLoading && !Policy.hasActiveFreePolicy(props.allPolicies) + ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies) ? [ { displayInDefaultIconColor: true, - contentFit: 'contain', + contentFit: 'contain' as const, icon: Expensicons.NewWorkspace, iconWidth: 46, iconHeight: 40, @@ -219,31 +197,22 @@ function FloatingActionButtonAndPopover(props) { ); } -FloatingActionButtonAndPopover.propTypes = propTypes; -FloatingActionButtonAndPopover.defaultProps = defaultProps; FloatingActionButtonAndPopover.displayName = 'FloatingActionButtonAndPopover'; -const FloatingActionButtonAndPopoverWithRef = forwardRef((props, ref) => ( - -)); - -FloatingActionButtonAndPopoverWithRef.displayName = 'FloatingActionButtonAndPopoverWithRef'; - -export default compose( - withNavigation, - withNavigationFocus, - withWindowDimensions, - withOnyx({ - allPolicies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, - }, - isLoading: { - key: ONYXKEYS.IS_LOADING_APP, - }, - }), -)(FloatingActionButtonAndPopoverWithRef); +const policySelector = (policy: OnyxEntry) => + policy && { + type: policy.type, + role: policy.role, + isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, + pendingAction: policy.pendingAction, + }; + +export default withOnyx({ + allPolicies: { + key: ONYXKEYS.COLLECTION.POLICY, + selector: policySelector, + }, + isLoading: { + key: ONYXKEYS.IS_LOADING_APP, + }, +})(forwardRef(FloatingActionButtonAndPopover)); From 6465479de01d8d88e890ce6d24828380ab5be40d Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Tue, 20 Feb 2024 16:12:33 +0530 Subject: [PATCH 038/556] Update recovery code trim logic --- src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index 2bc90efaceb5..db14010f5f37 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -179,7 +179,7 @@ function BaseValidateCodeForm(props) { setInput = setRecoveryCode; } - setInput(text.trim()); + setInput(text); setFormError((prevError) => ({...prevError, [key]: ''})); if (props.account.errors) { @@ -283,7 +283,7 @@ function BaseValidateCodeForm(props) { setFormError({recoveryCode: 'recoveryCodeForm.error.pleaseFillRecoveryCode'}); return; } - if (!ValidationUtils.isValidRecoveryCode(recoveryCode)) { + if (!ValidationUtils.isValidRecoveryCode(recoveryCode.trim())) { setFormError({recoveryCode: 'recoveryCodeForm.error.incorrectRecoveryCode'}); return; } @@ -303,7 +303,7 @@ function BaseValidateCodeForm(props) { } setFormError({}); - const recoveryCodeOr2faCode = props.isUsingRecoveryCode ? recoveryCode : twoFactorAuthCode; + const recoveryCodeOr2faCode = props.isUsingRecoveryCode ? recoveryCode.trim() : twoFactorAuthCode; const accountID = lodashGet(props.credentials, 'accountID'); if (accountID) { From c9158cbd92eaed9d32315fb38fda611e4c182079 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 20 Feb 2024 14:48:56 +0100 Subject: [PATCH 039/556] fix: wip --- src/libs/actions/Policy.ts | 2 +- .../sidebar/BottomTabBarFloatingActionButton/index.tsx | 1 - .../home/sidebar/SidebarScreen/BaseSidebarScreen.tsx | 2 -- .../SidebarScreen/FloatingActionButtonAndPopover.tsx | 9 ++++----- .../home/sidebar/SidebarScreen/{index.js => index.tsx} | 8 +++++--- src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js | 7 ------- 6 files changed, 10 insertions(+), 19 deletions(-) rename src/pages/home/sidebar/SidebarScreen/{index.js => index.tsx} (75%) delete mode 100644 src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index e780001a7ecc..261e15af8d48 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -81,7 +81,7 @@ type OptimisticCustomUnits = { outputCurrency: string; }; -type PoliciesRecord = Record; +type PoliciesRecord = Record; type NewCustomUnit = { customUnitID: string; diff --git a/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx b/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx index 788dd4ae5bc8..33b89be8fd17 100644 --- a/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx +++ b/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx @@ -32,7 +32,6 @@ function BottomTabBarFloatingActionButton() { return ( diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 314b3921cc0b..b3901e1ae06f 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -7,7 +7,6 @@ import Performance from '@libs/Performance'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; -import sidebarPropTypes from './sidebarPropTypes'; /** * Function called when a pinned chat is selected. @@ -44,7 +43,6 @@ function BaseSidebarScreen() { ); } -BaseSidebarScreen.propTypes = sidebarPropTypes; BaseSidebarScreen.displayName = 'BaseSidebarScreen'; export default BaseSidebarScreen; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 83cc719f2e90..01d5ddaff475 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,4 +1,4 @@ -import {useIsFocused, useNavigation} from '@react-navigation/native'; +import {useIsFocused} from '@react-navigation/native'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; @@ -25,13 +25,13 @@ import type * as OnyxTypes from '@src/types/onyx'; type FloatingActionButtonAndPopoverOnyxProps = { /** The list of policies the user has access to. */ - allPolicies: OnyxEntry>; + allPolicies: OnyxEntry>>; isLoading: OnyxEntry; }; type FloatingActionButtonAndPopoverProps = FloatingActionButtonAndPopoverOnyxProps & { /* Callback function when the menu is shown */ - onShowCreateMenu: () => void; + onShowCreateMenu?: () => void; /* Callback function before the menu is hidden */ onHideCreateMenu: () => void; @@ -49,7 +49,6 @@ function FloatingActionButtonAndPopover( {onHideCreateMenu = () => {}, onShowCreateMenu = () => {}, isLoading, allPolicies}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef, ) { - console.log('allPolices', allPolicies); const styles = useThemeStyles(); const {translate} = useLocalize(); const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); @@ -200,7 +199,7 @@ function FloatingActionButtonAndPopover( FloatingActionButtonAndPopover.displayName = 'FloatingActionButtonAndPopover'; const policySelector = (policy: OnyxEntry) => - policy && { + !!policy && { type: policy.type, role: policy.role, isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.tsx similarity index 75% rename from src/pages/home/sidebar/SidebarScreen/index.js rename to src/pages/home/sidebar/SidebarScreen/index.tsx index 7086e8a8561a..f017750c912c 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.tsx @@ -1,10 +1,13 @@ import React from 'react'; +import type {LayoutChangeEvent} from 'react-native'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; import BaseSidebarScreen from './BaseSidebarScreen'; -import sidebarPropTypes from './sidebarPropTypes'; -function SidebarScreen(props) { +type SidebarScreenProps = { + onLayout: (event: LayoutChangeEvent) => void; +}; +function SidebarScreen(props: SidebarScreenProps) { const {isSmallScreenWidth} = useWindowDimensions(); return ( @@ -17,7 +20,6 @@ function SidebarScreen(props) { ); } -SidebarScreen.propTypes = sidebarPropTypes; SidebarScreen.displayName = 'SidebarScreen'; export default SidebarScreen; diff --git a/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js b/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js deleted file mode 100644 index 61a9194bb1e5..000000000000 --- a/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js +++ /dev/null @@ -1,7 +0,0 @@ -import PropTypes from 'prop-types'; - -const sidebarPropTypes = { - /** Callback when onLayout of sidebar is called */ - onLayout: PropTypes.func, -}; -export default sidebarPropTypes; From dc58fff4022813cc70e994bc2f1f97f4e427cebf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 20 Feb 2024 15:42:06 +0100 Subject: [PATCH 040/556] fix: wip --- .../FloatingActionButtonAndPopover.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 01d5ddaff475..9291401d4569 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -26,6 +26,8 @@ import type * as OnyxTypes from '@src/types/onyx'; type FloatingActionButtonAndPopoverOnyxProps = { /** The list of policies the user has access to. */ allPolicies: OnyxEntry>>; + + /** Wheater app is in loading state */ isLoading: OnyxEntry; }; @@ -199,12 +201,14 @@ function FloatingActionButtonAndPopover( FloatingActionButtonAndPopover.displayName = 'FloatingActionButtonAndPopover'; const policySelector = (policy: OnyxEntry) => - !!policy && { - type: policy.type, - role: policy.role, - isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, - pendingAction: policy.pendingAction, - }; + policy + ? { + type: policy.type, + role: policy.role, + isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, + pendingAction: policy.pendingAction, + } + : null; export default withOnyx({ allPolicies: { From 4add919636d3e398a22bafa6f55901603183f2f9 Mon Sep 17 00:00:00 2001 From: RohanSasne Date: Wed, 21 Feb 2024 03:21:30 +0530 Subject: [PATCH 041/556] Add whitespace check --- .../substeps/PhoneNumberBusiness.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx index e0d369e099d1..3a851b001ff7 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, {useState} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -26,10 +26,11 @@ 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 validate = (values: FormOnyxValues, inputValue: string): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true)) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if ((values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true)) || inputValue.trim() !== inputValue) { errors.companyPhone = 'bankAccount.error.phoneNumber'; } @@ -41,6 +42,12 @@ function PhoneNumberBusiness({reimbursementAccount, onNext, isEditing}: PhoneNum const styles = useThemeStyles(); const defaultCompanyPhoneNumber = reimbursementAccount?.achData?.companyPhone ?? ''; + const [inputValue, setInputValue] = useState(defaultCompanyPhoneNumber); + + const handleInputChange = (newValue: string) => { + setInputValue(newValue); + }; + const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, isEditing, @@ -51,7 +58,7 @@ function PhoneNumberBusiness({reimbursementAccount, onNext, isEditing}: PhoneNum validate(values, inputValue)} onSubmit={handleSubmit} style={[styles.mh5, styles.flexGrow1]} submitButtonStyles={[styles.pb5, styles.mb0]} @@ -68,6 +75,7 @@ function PhoneNumberBusiness({reimbursementAccount, onNext, isEditing}: PhoneNum defaultValue={defaultCompanyPhoneNumber} shouldSaveDraft={!isEditing} containerStyles={[styles.mt6]} + onChangeText={handleInputChange} /> ); From cb7bfac8c0e73a6ef1814436e7fe35237dc3f5de Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 21 Feb 2024 12:12:14 +0100 Subject: [PATCH 042/556] TS updates after merging main --- src/components/Onfido/BaseOnfidoWeb.tsx | 54 +++++++++++-------- src/components/Onfido/index.native.tsx | 13 ++--- src/components/Onfido/types.ts | 10 ++-- .../VerifyIdentity/VerifyIdentity.tsx | 6 +-- 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/components/Onfido/BaseOnfidoWeb.tsx b/src/components/Onfido/BaseOnfidoWeb.tsx index ee206b15fc24..2722b9e9be79 100644 --- a/src/components/Onfido/BaseOnfidoWeb.tsx +++ b/src/components/Onfido/BaseOnfidoWeb.tsx @@ -1,17 +1,28 @@ -import lodashGet from 'lodash/get'; import * as OnfidoSDK from 'onfido-sdk-ui'; import React, {forwardRef, useEffect} from 'react'; -import _ from 'underscore'; +import type {ForwardedRef} from 'react'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import Log from '@libs/Log'; +import type {ThemeColors} from '@styles/theme/types'; import FontUtils from '@styles/utils/FontUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import './index.css'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoElement, OnfidoProps} from './types'; -function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate, theme}) { +type InitializeOnfidoProps = OnfidoProps & + Pick & { + theme: ThemeColors; + }; + +type OnfidoEvent = Event & { + detail?: Record; +}; + +function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate, theme}: InitializeOnfidoProps) { OnfidoSDK.init({ token: sdkToken, containerId: CONST.ONFIDO.CONTAINER_ID, @@ -21,7 +32,7 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo fontFamilySubtitle: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`, fontFamilyBody: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`, fontSizeTitle: `${variables.fontSizeLarge}px`, - fontWeightTitle: FontUtils.fontWeight.bold, + fontWeightTitle: Number(FontUtils.fontWeight.bold), fontWeightSubtitle: 400, fontSizeSubtitle: `${variables.fontSizeNormal}px`, colorContentTitle: theme.text, @@ -46,7 +57,6 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo colorBorderLinkUnderline: theme.link, colorBackgroundLinkHover: theme.link, colorBackgroundLinkActive: theme.link, - authAccentColor: theme.link, colorBackgroundInfoPill: theme.link, colorBackgroundSelector: theme.appBG, colorBackgroundDocTypeButton: theme.success, @@ -58,11 +68,10 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo { type: CONST.ONFIDO.TYPE.DOCUMENT, options: { - useLiveDocumentCapture: true, forceCrossDevice: true, hideCountrySelection: true, - country: 'USA', documentTypes: { + // eslint-disable-next-line @typescript-eslint/naming-convention driving_licence: { country: 'USA', }, @@ -77,17 +86,15 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, }, ], - smsNumberCountryCode: CONST.ONFIDO.SMS_NUMBER_COUNTRY_CODE.US, - showCountrySelection: false, onComplete: (data) => { - if (_.isEmpty(data)) { + if (isEmptyObject(data)) { Log.warn('Onfido completed with no data'); } onSuccess(data); }, onError: (error) => { - const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR); - const errorType = lodashGet(error, 'type'); + const errorMessage = error.message ?? CONST.ERROR.UNKNOWN_ERROR; + const errorType = error.type; Log.hmmm('Onfido error', {errorType, errorMessage}); onError(errorMessage); }, @@ -100,33 +107,34 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, language: { // We need to use ES_ES as locale key because the key `ES` is not a valid config key for Onfido - locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : preferredLocale, + locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : (preferredLocale as OnfidoSDK.SupportedLanguages), // Provide a custom phrase for the back button so that the first letter is capitalized, // and translate the phrase while we're at it. See the issue and documentation for more context. // https://github.com/Expensify/App/issues/17244 // https://documentation.onfido.com/sdk/web/#custom-languages phrases: { + // eslint-disable-next-line @typescript-eslint/naming-convention 'generic.back': translate('common.back'), }, }, }); } -function logOnFidoEvent(event) { +function logOnFidoEvent(event: OnfidoEvent) { Log.hmmm('Receiving Onfido analytic event', event.detail); } -const Onfido = forwardRef((props, ref) => { +function Onfido({sdkToken, onSuccess, onError, onUserExit}: OnfidoProps, ref: ForwardedRef) { const {preferredLocale, translate} = useLocalize(); const theme = useTheme(); useEffect(() => { initializeOnfido({ - sdkToken: props.sdkToken, - onSuccess: props.onSuccess, - onError: props.onError, - onUserExit: props.onUserExit, + sdkToken, + onSuccess, + onError, + onUserExit, preferredLocale, translate, theme, @@ -144,8 +152,8 @@ const Onfido = forwardRef((props, ref) => { ref={ref} /> ); -}); +} Onfido.displayName = 'Onfido'; -Onfido.propTypes = onfidoPropTypes; -export default Onfido; + +export default forwardRef(Onfido); diff --git a/src/components/Onfido/index.native.tsx b/src/components/Onfido/index.native.tsx index eae01d14869e..a7e7a277fff9 100644 --- a/src/components/Onfido/index.native.tsx +++ b/src/components/Onfido/index.native.tsx @@ -1,13 +1,13 @@ -import {OnfidoCaptureType, OnfidoCountryCode, OnfidoDocumentType, Onfido as OnfidoSDK} from '@onfido/react-native-sdk'; +import {OnfidoCaptureType, OnfidoCountryCode, OnfidoDocumentType, Onfido as OnfidoSDK, OnfidoTheme} from '@onfido/react-native-sdk'; import React, {useEffect} from 'react'; import {Alert, Linking} from 'react-native'; import {checkMultiple, PERMISSIONS, RESULTS} from 'react-native-permissions'; -import _ from 'underscore'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useLocalize from '@hooks/useLocalize'; import getPlatform from '@libs/getPlatform'; import Log from '@libs/Log'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import type {OnfidoProps} from './types'; function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { @@ -16,6 +16,7 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { useEffect(() => { OnfidoSDK.start({ sdkToken, + theme: OnfidoTheme.AUTOMATIC, flowSteps: { welcome: true, captureFace: { @@ -41,13 +42,13 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { return; } - if (!_.isEmpty(errorMessage) && getPlatform() === CONST.PLATFORM.IOS) { + if (!!errorMessage && getPlatform() === CONST.PLATFORM.IOS) { checkMultiple([PERMISSIONS.IOS.MICROPHONE, PERMISSIONS.IOS.CAMERA]) .then((statuses) => { const isMicAllowed = statuses[PERMISSIONS.IOS.MICROPHONE] === RESULTS.GRANTED; const isCameraAllowed = statuses[PERMISSIONS.IOS.CAMERA] === RESULTS.GRANTED; - let alertTitle = ''; - let alertMessage = ''; + let alertTitle: TranslationPaths | '' = ''; + let alertMessage: TranslationPaths | '' = ''; if (!isCameraAllowed) { alertTitle = 'onfidoStep.cameraPermissionsNotGranted'; alertMessage = 'onfidoStep.cameraRequestMessage'; @@ -56,7 +57,7 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { alertMessage = 'onfidoStep.microphoneRequestMessage'; } - if (!_.isEmpty(alertTitle) && !_.isEmpty(alertMessage)) { + if (!!alertTitle && !!alertMessage) { Alert.alert( translate(alertTitle), translate(alertMessage), diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts index a4fe3d93f05e..e341dfd64960 100644 --- a/src/components/Onfido/types.ts +++ b/src/components/Onfido/types.ts @@ -1,5 +1,7 @@ -import {OnfidoError, OnfidoResult} from '@onfido/react-native-sdk'; -import * as OnfidoSDK from 'onfido-sdk-ui'; +import type {OnfidoResult} from '@onfido/react-native-sdk'; +import type * as OnfidoSDK from 'onfido-sdk-ui'; + +type OnfidoData = OnfidoSDK.SdkResponse | OnfidoResult; type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; @@ -11,10 +13,10 @@ type OnfidoProps = { onUserExit: (userExitCode?: OnfidoSDK.UserExitCode) => void; /** Called when the user is totally done with Onfido */ - onSuccess: (data: OnfidoSDK.SdkResponse | OnfidoResult | OnfidoError) => void; + onSuccess: (data: OnfidoData) => void; /** Called when Onfido throws an error */ onError: (error?: string) => void; }; -export type {OnfidoProps, OnfidoElement}; +export type {OnfidoProps, OnfidoElement, OnfidoData}; diff --git a/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx b/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx index d17166365a39..f7c4df6fd915 100644 --- a/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx +++ b/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx @@ -5,8 +5,8 @@ import {withOnyx} from 'react-native-onyx'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -// @ts-expect-error TODO: Remove this once Onfido (https://github.com/Expensify/App/issues/25136) is migrated to TypeScript. import Onfido from '@components/Onfido'; +import type {OnfidoData} from '@components/Onfido/types'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -40,7 +40,7 @@ function VerifyIdentity({reimbursementAccount, onBackButtonPress, onfidoApplican const policyID = reimbursementAccount?.achData?.policyID ?? ''; const handleOnfidoSuccess = useCallback( - (onfidoData: Record) => { + (onfidoData: OnfidoData) => { BankAccounts.verifyIdentityForBankAccount(Number(reimbursementAccount?.achData?.bankAccountID ?? '0'), {...onfidoData, applicantID: onfidoApplicantID}, policyID); BankAccounts.updateReimbursementAccountDraft({isOnfidoSetupComplete: true}); }, @@ -74,7 +74,7 @@ function VerifyIdentity({reimbursementAccount, onBackButtonPress, onfidoApplican Date: Wed, 21 Feb 2024 12:29:27 +0100 Subject: [PATCH 043/556] Add OnfidoDataWithApplicantID type --- src/components/Onfido/types.ts | 7 ++++++- src/libs/actions/BankAccounts.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts index e341dfd64960..90403f1ab179 100644 --- a/src/components/Onfido/types.ts +++ b/src/components/Onfido/types.ts @@ -1,8 +1,13 @@ import type {OnfidoResult} from '@onfido/react-native-sdk'; import type * as OnfidoSDK from 'onfido-sdk-ui'; +import type {OnyxEntry} from 'react-native-onyx'; type OnfidoData = OnfidoSDK.SdkResponse | OnfidoResult; +type OnfidoDataWithApplicantID = OnfidoData & { + applicantID: OnyxEntry; +}; + type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; type OnfidoProps = { @@ -19,4 +24,4 @@ type OnfidoProps = { onError: (error?: string) => void; }; -export type {OnfidoProps, OnfidoElement, OnfidoData}; +export type {OnfidoProps, OnfidoElement, OnfidoData, OnfidoDataWithApplicantID}; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 30dd03b6e780..90e4a6c4aaed 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import type {OnfidoDataWithApplicantID} from '@components/Onfido/types'; import * as API from '@libs/API'; import type { AddPersonalBankAccountParams, @@ -436,7 +437,7 @@ function connectBankAccountManually(bankAccountID: number, bankAccount: PlaidBan /** * Verify the user's identity via Onfido */ -function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: Record, policyID: string) { +function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: OnfidoDataWithApplicantID, policyID: string) { const parameters: VerifyIdentityForBankAccountParams = { bankAccountID, onfidoData: JSON.stringify(onfidoData), From 4bb8acb56b6f3995a58eebf0f96cb975d6a94baa Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 21 Feb 2024 12:42:45 +0100 Subject: [PATCH 044/556] fix: removed unused props spreading --- src/pages/home/sidebar/SidebarScreen/index.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/index.tsx b/src/pages/home/sidebar/SidebarScreen/index.tsx index f017750c912c..e448a9cad332 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.tsx +++ b/src/pages/home/sidebar/SidebarScreen/index.tsx @@ -1,21 +1,14 @@ import React from 'react'; -import type {LayoutChangeEvent} from 'react-native'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; import BaseSidebarScreen from './BaseSidebarScreen'; -type SidebarScreenProps = { - onLayout: (event: LayoutChangeEvent) => void; -}; -function SidebarScreen(props: SidebarScreenProps) { +function SidebarScreen() { const {isSmallScreenWidth} = useWindowDimensions(); return ( - + ); } From a8097cd6a6be5b86a7d045304d1d8cf6e668c00c Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Wed, 21 Feb 2024 16:33:00 +0200 Subject: [PATCH 045/556] Initial historical action to show --- src/CONST.ts | 1 + src/pages/home/report/ReportActionItem.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 6a57738d06ec..dade8443bfce 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -653,6 +653,7 @@ const CONST = { LEAVE_ROOM: 'LEAVEROOM', UPDATE_ROOM_DESCRIPTION: 'UPDATEROOMDESCRIPTION', }, + UNAPPROVED: 'UNAPPROVED', UNHOLD: 'UNHOLD', }, THREAD_DISABLED: ['CREATED'], diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 66394190fde6..dac0353623a5 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -464,7 +464,11 @@ function ReportActionItem(props) { children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { children = ; - } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED) { + } else if ([ + CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + CONST.REPORT.ACTIONS.TYPE.UNAPPROVED + ].includes(props.action.actionName)) { + // This handles all historical actions from OldDot that we just want to display the message text children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD) { children = ; From 7064cfe6e4487972ff995c5886994cd45f80e5ea Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Wed, 21 Feb 2024 15:52:22 +0000 Subject: [PATCH 046/556] [TS migration] Migrate postTestBuildComment to typescript --- ...uildComment.js => postTestBuildComment.ts} | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) rename tests/unit/{postTestBuildComment.js => postTestBuildComment.ts} (83%) diff --git a/tests/unit/postTestBuildComment.js b/tests/unit/postTestBuildComment.ts similarity index 83% rename from tests/unit/postTestBuildComment.js rename to tests/unit/postTestBuildComment.ts index ff77ca190c08..df0a623aaced 100644 --- a/tests/unit/postTestBuildComment.js +++ b/tests/unit/postTestBuildComment.ts @@ -1,10 +1,11 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; +import type {Writable} from 'type-fest'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; const mockGetInput = jest.fn(); -const mockCreateComment = jest.fn(); +const createCommentMock = jest.spyOn(GithubUtils, 'createComment'); const mockListComments = jest.fn(); const mockGraphql = jest.fn(); jest.spyOn(GithubUtils, 'octokit', 'get').mockReturnValue({ @@ -12,7 +13,11 @@ jest.spyOn(GithubUtils, 'octokit', 'get').mockReturnValue({ listComments: mockListComments, }, }); -jest.spyOn(GithubUtils, 'paginate', 'get').mockReturnValue((endpoint, params) => endpoint(params).then(({data}) => data)); + +jest.spyOn(GithubUtils, 'paginate', 'get').mockReturnValue((endpoint: (params: Record) => Promise<{data: TData}>, params: Record) => + endpoint(params).then((response) => response.data), +); + jest.spyOn(GithubUtils, 'graphql', 'get').mockReturnValue(mockGraphql); jest.mock('@actions/github', () => ({ @@ -49,11 +54,12 @@ const message = `:test_tube::test_tube: Use the links below to test this adhoc b :eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/1234) :eyes: `; +const asMutable = (value: T): Writable => value as Writable; + describe('Post test build comments action tests', () => { beforeAll(() => { // Mock core module - core.getInput = mockGetInput; - GithubUtils.createComment = mockCreateComment; + asMutable(core).getInput = mockGetInput; }); test('Test GH action', async () => { @@ -66,11 +72,12 @@ describe('Post test build comments action tests', () => { when(core.getInput).calledWith('IOS_LINK').mockReturnValue('https://expensify.app/IOS_LINK'); when(core.getInput).calledWith('WEB_LINK').mockReturnValue('https://expensify.app/WEB_LINK'); when(core.getInput).calledWith('DESKTOP_LINK').mockReturnValue('https://expensify.app/DESKTOP_LINK'); - GithubUtils.createComment.mockResolvedValue(true); + createCommentMock.mockResolvedValue(true); mockListComments.mockResolvedValue({ data: [ { body: ':test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing!', + // eslint-disable-next-line @typescript-eslint/naming-convention node_id: 'IC_abcd', }, ], @@ -86,7 +93,7 @@ describe('Post test build comments action tests', () => { } } `); - expect(GithubUtils.createComment).toBeCalledTimes(1); - expect(GithubUtils.createComment).toBeCalledWith('App', 12, message); + expect(createCommentMock).toBeCalledTimes(1); + expect(createCommentMock).toBeCalledWith('App', 12, message); }); }); From 0ead9f440a8ff6d5bd8167a27dc0bf2e3def6ca8 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Wed, 21 Feb 2024 15:58:26 +0000 Subject: [PATCH 047/556] [TS migration][postTestBuildComment] Added last ts issue fix --- tests/unit/postTestBuildComment.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index df0a623aaced..808e85d5c8a8 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -63,7 +63,9 @@ describe('Post test build comments action tests', () => { }); test('Test GH action', async () => { - when(core.getInput).calledWith('PR_NUMBER', {required: true}).mockReturnValue(12); + when(core.getInput) + .calledWith('PR_NUMBER', {required: true}) + .mockReturnValue(12 as unknown as string); when(core.getInput).calledWith('ANDROID', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('IOS', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('WEB', {required: true}).mockReturnValue('success'); From 9e641bd545df9472e55f8b612561c3bc7e23c598 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 21 Feb 2024 16:30:05 +0000 Subject: [PATCH 048/556] refactor(typescript): migrate addplaidbankaccount --- ...BankAccount.js => AddPlaidBankAccount.tsx} | 155 ++++++++---------- src/components/PlaidLink/types.ts | 2 +- src/libs/KeyboardShortcut/index.ts | 4 +- 3 files changed, 68 insertions(+), 93 deletions(-) rename src/components/{AddPlaidBankAccount.js => AddPlaidBankAccount.tsx} (71%) diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.tsx similarity index 71% rename from src/components/AddPlaidBankAccount.js rename to src/components/AddPlaidBankAccount.tsx index b6fc639546a8..c1c4fc122f9f 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.tsx @@ -1,20 +1,19 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import Log from '@libs/Log'; -import {plaidDataPropTypes} from '@pages/ReimbursementAccount/plaidDataPropTypes'; import * as App from '@userActions/App'; import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {PlaidData} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlockingView'; import FormHelpMessage from './FormHelpMessage'; import Icon from './Icon'; @@ -24,103 +23,82 @@ import PlaidLink from './PlaidLink'; import RadioButtons from './RadioButtons'; import Text from './Text'; -const propTypes = { +type AddPlaidBankAccountOnyxProps = { /** If the user has been throttled from Plaid */ - isPlaidDisabled: PropTypes.bool, + isPlaidDisabled: OnyxEntry; + /** Plaid SDK token to use to initialize the widget */ + plaidLinkToken: OnyxEntry; +}; + +type AddPlaidBankAccountProps = AddPlaidBankAccountOnyxProps & { /** Contains plaid data */ - plaidData: plaidDataPropTypes.isRequired, + plaidData: OnyxEntry; /** Selected account ID from the Picker associated with the end of the Plaid flow */ - selectedPlaidAccountID: PropTypes.string, - - /** Plaid SDK token to use to initialize the widget */ - plaidLinkToken: PropTypes.string, + selectedPlaidAccountID?: string; /** Fired when the user exits the Plaid flow */ - onExitPlaid: PropTypes.func, + onExitPlaid?: () => void; /** Fired when the user selects an account */ - onSelect: PropTypes.func, + onSelect?: (plaidAccountID: string) => void; /** Additional text to display */ - text: PropTypes.string, + text?: string; /** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */ - receivedRedirectURI: PropTypes.string, + receivedRedirectURI: string | null; /** During the OAuth flow we need to use the plaidLink token that we initially connected with */ - plaidLinkOAuthToken: PropTypes.string, + plaidLinkOAuthToken?: string; /** If we're updating an existing bank account, what's its bank account ID? */ - bankAccountID: PropTypes.number, + bankAccountID?: number; /** Are we adding a withdrawal account? */ - allowDebit: PropTypes.bool, + allowDebit?: boolean; /** Is displayed in new VBBA */ - isDisplayedInNewVBBA: PropTypes.bool, + isDisplayedInNewVBBA?: boolean; /** Text to display on error message */ - errorText: PropTypes.string, + errorText?: string; /** Function called whenever radio button value changes */ - onInputChange: PropTypes.func, -}; - -const defaultProps = { - selectedPlaidAccountID: '', - plaidLinkToken: '', - onExitPlaid: () => {}, - onSelect: () => {}, - text: '', - receivedRedirectURI: null, - plaidLinkOAuthToken: '', - allowDebit: false, - bankAccountID: 0, - isPlaidDisabled: false, - isDisplayedInNewVBBA: false, - errorText: '', - onInputChange: () => {}, + onInputChange?: (plaidAccountID: string) => void; }; function AddPlaidBankAccount({ plaidData, - selectedPlaidAccountID, + selectedPlaidAccountID = '', plaidLinkToken, - onExitPlaid, - onSelect, - text, - receivedRedirectURI, - plaidLinkOAuthToken, - bankAccountID, - allowDebit, + onExitPlaid = () => {}, + onSelect = () => {}, + text = '', + receivedRedirectURI = null, + plaidLinkOAuthToken = '', + bankAccountID = 0, + allowDebit = false, isPlaidDisabled, - isDisplayedInNewVBBA, - errorText, - onInputChange, -}) { + isDisplayedInNewVBBA = false, + errorText = '', + onInputChange = () => {}, +}: AddPlaidBankAccountProps) { const theme = useTheme(); const styles = useThemeStyles(); - const plaidBankAccounts = lodashGet(plaidData, 'bankAccounts', []); - const defaultSelectedPlaidAccount = _.find(plaidBankAccounts, (account) => account.plaidAccountID === selectedPlaidAccountID); - const defaultSelectedPlaidAccountID = lodashGet(defaultSelectedPlaidAccount, 'plaidAccountID', ''); - const defaultSelectedPlaidAccountMask = lodashGet( - _.find(plaidBankAccounts, (account) => account.plaidAccountID === selectedPlaidAccountID), - 'mask', - '', - ); - const subscribedKeyboardShortcuts = useRef([]); - const previousNetworkState = useRef(); - const [selectedPlaidAccountMask, setSelectedPlaidAccountMask] = useState(defaultSelectedPlaidAccountMask); + const plaidBankAccounts = plaidData?.bankAccounts ?? []; + const defaultSelectedPlaidAccount = plaidBankAccounts.find((account) => account.plaidAccountID === selectedPlaidAccountID); + const defaultSelectedPlaidAccountID = defaultSelectedPlaidAccount?.plaidAccountID ?? ''; + const defaultSelectedPlaidAccountMask = plaidBankAccounts.find((account) => account.plaidAccountID === selectedPlaidAccountID)?.mask ?? ''; + const subscribedKeyboardShortcuts = useRef void>>([]); + const previousNetworkState = useRef(); + const [selectedPlaidAccountMask, setSelectedPlaidAccountMask] = useState(defaultSelectedPlaidAccountMask); const {translate} = useLocalize(); const {isOffline} = useNetwork(); - /** - * @returns {String} - */ - const getPlaidLinkToken = () => { + const getPlaidLinkToken = (): string | undefined => { if (plaidLinkToken) { return plaidLinkToken; } @@ -135,7 +113,7 @@ function AddPlaidBankAccount({ * I'm using useCallback so the useEffect which uses this function doesn't run on every render. */ const isAuthenticatedWithPlaid = useCallback( - () => (receivedRedirectURI && plaidLinkOAuthToken) || !_.isEmpty(lodashGet(plaidData, 'bankAccounts')) || !_.isEmpty(lodashGet(plaidData, 'errors')), + () => (!!receivedRedirectURI && !!plaidLinkOAuthToken) || !plaidData?.bankAccounts?.length || !isEmptyObject(plaidData?.errors), [plaidData, plaidLinkOAuthToken, receivedRedirectURI], ); @@ -144,15 +122,15 @@ function AddPlaidBankAccount({ */ const subscribeToNavigationShortcuts = () => { // find and block the shortcuts - const shortcutsToBlock = _.filter(CONST.KEYBOARD_SHORTCUTS, (x) => x.type === CONST.KEYBOARD_SHORTCUTS_TYPES.NAVIGATION_SHORTCUT); - subscribedKeyboardShortcuts.current = _.map(shortcutsToBlock, (shortcut) => + const shortcutsToBlock = Object.values(CONST.KEYBOARD_SHORTCUTS).filter((x) => 'type' in x && x.type === CONST.KEYBOARD_SHORTCUTS_TYPES.NAVIGATION_SHORTCUT); + subscribedKeyboardShortcuts.current = shortcutsToBlock.map((shortcut) => KeyboardShortcut.subscribe( shortcut.shortcutKey, () => {}, // do nothing shortcut.descriptionKey, shortcut.modifiers, false, - () => lodashGet(plaidData, 'bankAccounts', []).length > 0, // start bubbling when there are bank accounts + () => (plaidData?.bankAccounts ?? []).length > 0, // start bubbling when there are bank accounts ), ); }; @@ -161,7 +139,7 @@ function AddPlaidBankAccount({ * Unblocks the keyboard shortcuts that can navigate */ const unsubscribeToNavigationShortcuts = () => { - _.each(subscribedKeyboardShortcuts.current, (unsubscribe) => unsubscribe()); + subscribedKeyboardShortcuts.current.forEach((unsubscribe) => unsubscribe()); subscribedKeyboardShortcuts.current = []; }; @@ -188,23 +166,22 @@ function AddPlaidBankAccount({ previousNetworkState.current = isOffline; }, [allowDebit, bankAccountID, isAuthenticatedWithPlaid, isOffline]); - const token = getPlaidLinkToken(); - const options = _.map(plaidBankAccounts, (account) => ({ + const token = getPlaidLinkToken() ?? ''; + const options = plaidBankAccounts.map((account) => ({ value: account.plaidAccountID, - label: account.addressName, + label: account.addressName ?? '', })); const {icon, iconSize, iconStyles} = getBankIcon({styles}); - const plaidErrors = lodashGet(plaidData, 'errors'); - const plaidDataErrorMessage = !_.isEmpty(plaidErrors) ? _.chain(plaidErrors).values().first().value() : ''; - const bankName = lodashGet(plaidData, 'bankName'); + const plaidErrors = plaidData?.errors; + const plaidDataErrorMessage = !isEmptyObject(plaidErrors) ? (Object.values(plaidErrors)[0] as string) : ''; + const bankName = plaidData?.bankName; /** - * @param {String} plaidAccountID * * When user selects one of plaid accounts we need to set the mask in order to display it on UI */ - const handleSelectingPlaidAccount = (plaidAccountID) => { - const mask = _.find(plaidBankAccounts, (account) => account.plaidAccountID === plaidAccountID).mask; + const handleSelectingPlaidAccount = (plaidAccountID: string) => { + const mask = plaidBankAccounts.find((account) => account.plaidAccountID === plaidAccountID)?.mask ?? ''; setSelectedPlaidAccountMask(mask); onSelect(plaidAccountID); onInputChange(plaidAccountID); @@ -219,24 +196,24 @@ function AddPlaidBankAccount({ } const renderPlaidLink = () => { - if (Boolean(token) && !bankName) { + if (!!token && !bankName) { return ( { Log.info('[PlaidLink] Success!'); - BankAccounts.openPlaidBankAccountSelector(publicToken, metadata.institution.name, allowDebit, bankAccountID); + BankAccounts.openPlaidBankAccountSelector(publicToken, metadata?.institution?.name ?? '', allowDebit, bankAccountID); }} onError={(error) => { - Log.hmmm('[PlaidLink] Error: ', error.message); + Log.hmmm('[PlaidLink] Error: ', error?.message); }} onEvent={(event, metadata) => { BankAccounts.setPlaidEvent(event); // Handle Plaid login errors (will potentially reset plaid token and item depending on the error) if (event === 'ERROR') { - Log.hmmm('[PlaidLink] Error: ', metadata); - if (bankAccountID && metadata && metadata.error_code) { - BankAccounts.handlePlaidError(bankAccountID, metadata.error_code, metadata.error_message, metadata.request_id); + Log.hmmm('[PlaidLink] Error: ', metadata as Record | undefined); + if (bankAccountID && metadata && 'error_code' in metadata) { + BankAccounts.handlePlaidError(bankAccountID, metadata.error_code ?? '', metadata.error_message ?? '', metadata.request_id); } } @@ -257,7 +234,7 @@ function AddPlaidBankAccount({ return {plaidDataErrorMessage}; } - if (lodashGet(plaidData, 'isLoading')) { + if (plaidData?.isLoading) { return ( {translate('bankAccount.chooseAnAccount')} - {!_.isEmpty(text) && {text}} + {!!text && {text}} - {!_.isEmpty(text) && {text}} + {!!text && {text}} ({ plaidLinkToken: { key: ONYXKEYS.PLAID_LINK_TOKEN, initWithStoredValues: false, diff --git a/src/components/PlaidLink/types.ts b/src/components/PlaidLink/types.ts index 48526520c736..06dc38bbf5fb 100644 --- a/src/components/PlaidLink/types.ts +++ b/src/components/PlaidLink/types.ts @@ -19,7 +19,7 @@ type PlaidLinkProps = { // The redirect URI with an OAuth state ID. Needed to re-initialize the PlaidLink after directing the // user to their respective bank platform - receivedRedirectURI?: string; + receivedRedirectURI?: string | null; }; export default PlaidLinkProps; diff --git a/src/libs/KeyboardShortcut/index.ts b/src/libs/KeyboardShortcut/index.ts index 44ba54953c40..999725598e86 100644 --- a/src/libs/KeyboardShortcut/index.ts +++ b/src/libs/KeyboardShortcut/index.ts @@ -131,10 +131,10 @@ function getPlatformEquivalentForKeys(keys: ShortcutModifiers): string[] { function subscribe( key: string, callback: (event?: KeyboardEvent) => void, - descriptionKey: string, + descriptionKey: string | null, modifiers: ShortcutModifiers = ['CTRL'], captureOnInputs = false, - shouldBubble = false, + shouldBubble: boolean | (() => boolean) = false, priority = 0, shouldPreventDefault = true, excludedNodes: string[] = [], From 09e448f7ddbe7992a943a63abc17f960e71cd647 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 21 Feb 2024 17:37:35 +0100 Subject: [PATCH 049/556] fix: added ugly fix for onyx selectors --- src/pages/home/sidebar/SidebarLinksData.tsx | 74 +++++++++---------- .../FloatingActionButtonAndPopover.tsx | 10 ++- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 29f58d679760..182f413272f0 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -23,42 +23,42 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportAction'; import SidebarLinks from './SidebarLinks'; +type PickedReport = Pick< + OnyxTypes.Report, + | 'reportID' + | 'participantAccountIDs' + | 'hasDraft' + | 'isPinned' + | 'isHidden' + | 'notificationPreference' + | 'errorFields' + | 'lastMessageText' + | 'lastVisibleActionCreated' + | 'iouReportID' + | 'total' + | 'nonReimbursableTotal' + | 'hasOutstandingChildRequest' + | 'isWaitingOnBankAccount' + | 'statusNum' + | 'stateNum' + | 'chatType' + | 'type' + | 'policyID' + | 'visibility' + | 'lastReadTime' + | 'reportName' + | 'policyName' + | 'oldPolicyName' + | 'ownerAccountID' + | 'currency' + | 'managerID' + | 'parentReportActionID' + | 'parentReportID' + | 'isDeletedParentAction' +>; + type SidebarLinksDataOnyxProps = { - chatReports: OnyxCollection< - Pick< - OnyxTypes.Report, - | 'reportID' - | 'participantAccountIDs' - | 'hasDraft' - | 'isPinned' - | 'isHidden' - | 'notificationPreference' - | 'errorFields' - | 'lastMessageText' - | 'lastVisibleActionCreated' - | 'iouReportID' - | 'total' - | 'nonReimbursableTotal' - | 'hasOutstandingChildRequest' - | 'isWaitingOnBankAccount' - | 'statusNum' - | 'stateNum' - | 'chatType' - | 'type' - | 'policyID' - | 'visibility' - | 'lastReadTime' - | 'reportName' - | 'policyName' - | 'oldPolicyName' - | 'ownerAccountID' - | 'currency' - | 'managerID' - | 'parentReportActionID' - | 'parentReportID' - | 'isDeletedParentAction' - > & {isUnreadWithMention: boolean} - >; + chatReports: OnyxCollection; isLoadingApp: OnyxEntry; priorityMode: OnyxEntry>; betas: OnyxEntry; @@ -248,7 +248,7 @@ const policySelector = (policy: OnyxEntry) => export default withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, + selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, initialValue: {}, }, isLoadingApp: { @@ -269,7 +269,7 @@ export default withOnyx({ }, policies: { key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, + selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection>, initialValue: {}, }, policyMembers: { diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 2a0682b776cb..208a9c0a9deb 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,5 +1,5 @@ import {useIsFocused} from '@react-navigation/native'; -import type {ForwardedRef} from 'react'; +import type {ForwardedRef, RefAttributes} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -170,7 +170,7 @@ function FloatingActionButtonAndPopover( text: translate('sidebarScreen.saveTheWorld'), onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TEACHERS_UNITE)), }, - ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies) + ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies as Record) ? [ { displayInDefaultIconColor: true, @@ -211,10 +211,12 @@ const policySelector = (policy: OnyxEntry) => } : null; -export default withOnyx({ +export default withOnyx, FloatingActionButtonAndPopoverOnyxProps>({ allPolicies: { key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, + selector: policySelector as unknown as ( + policy: OnyxEntry, + ) => OnyxEntry>>, }, isLoading: { key: ONYXKEYS.IS_LOADING_APP, From a5ea70b6898e063ef27b2e11884cfaf716d633e7 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 22 Feb 2024 09:05:21 +0000 Subject: [PATCH 050/556] [TS migration][postTestBuildComment] Updated asMutable to the util --- src/utils/asMutable.ts | 5 +++++ tests/unit/postTestBuildComment.ts | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 src/utils/asMutable.ts diff --git a/src/utils/asMutable.ts b/src/utils/asMutable.ts new file mode 100644 index 000000000000..57c49058cd14 --- /dev/null +++ b/src/utils/asMutable.ts @@ -0,0 +1,5 @@ +import type {Writable} from 'type-fest'; + +const asMutable = (value: T): Writable => value as Writable; + +export default asMutable; diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 808e85d5c8a8..1c1e9ad35564 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; -import type {Writable} from 'type-fest'; +import asMutable from '@src/utils/asMutable'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; @@ -54,8 +54,6 @@ const message = `:test_tube::test_tube: Use the links below to test this adhoc b :eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/1234) :eyes: `; -const asMutable = (value: T): Writable => value as Writable; - describe('Post test build comments action tests', () => { beforeAll(() => { // Mock core module From dfad42627dc4711bd9311b30b397f4468c414ee3 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 22 Feb 2024 09:17:26 +0000 Subject: [PATCH 051/556] [TS migration][postTestBuildComment] Changed file location and name --- src/{utils/asMutable.ts => types/utils/AsMutable.ts} | 0 tests/unit/postTestBuildComment.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{utils/asMutable.ts => types/utils/AsMutable.ts} (100%) diff --git a/src/utils/asMutable.ts b/src/types/utils/AsMutable.ts similarity index 100% rename from src/utils/asMutable.ts rename to src/types/utils/AsMutable.ts diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 1c1e9ad35564..26c0711a34cd 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; -import asMutable from '@src/utils/asMutable'; +import asMutable from '@src/types/utils/AsMutable'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; From 8b2a982a0d31ca5348a4b29a38accd1b2fc90519 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 22 Feb 2024 19:16:54 +0530 Subject: [PATCH 052/556] minor fic. Signed-off-by: Krishna Gupta --- .../ReportActionCompose/AttachmentPickerWithMenuItems.js | 3 +++ .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.js | 1 + 2 files changed, 4 insertions(+) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 3828a43cd54d..af8f31e8c6b4 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -18,11 +18,14 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; +import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; const propTypes = { /** The report currently being looked at */ diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index b514c374e34c..5b9dce43ddcc 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -16,6 +16,7 @@ import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; +import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; From 2fa5415ddd6951a4ffe88c5b2c0ae278cdf60e75 Mon Sep 17 00:00:00 2001 From: RohanSasne Date: Fri, 23 Feb 2024 00:00:54 +0530 Subject: [PATCH 053/556] Set isEditing to true to trim the input --- .../substeps/PhoneNumberBusiness.tsx | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx index 3a851b001ff7..650dd32e069d 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -26,11 +26,11 @@ 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, inputValue: string): FormInputErrors => { +const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if ((values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true)) || inputValue.trim() !== inputValue) { + if ((values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true))) { errors.companyPhone = 'bankAccount.error.phoneNumber'; } @@ -42,15 +42,9 @@ function PhoneNumberBusiness({reimbursementAccount, onNext, isEditing}: PhoneNum const styles = useThemeStyles(); const defaultCompanyPhoneNumber = reimbursementAccount?.achData?.companyPhone ?? ''; - const [inputValue, setInputValue] = useState(defaultCompanyPhoneNumber); - - const handleInputChange = (newValue: string) => { - setInputValue(newValue); - }; - const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, - isEditing, + isEditing: true, onNext, }); @@ -58,7 +52,7 @@ function PhoneNumberBusiness({reimbursementAccount, onNext, isEditing}: PhoneNum validate(values, inputValue)} + validate={validate} onSubmit={handleSubmit} style={[styles.mh5, styles.flexGrow1]} submitButtonStyles={[styles.pb5, styles.mb0]} @@ -75,7 +69,6 @@ function PhoneNumberBusiness({reimbursementAccount, onNext, isEditing}: PhoneNum defaultValue={defaultCompanyPhoneNumber} shouldSaveDraft={!isEditing} containerStyles={[styles.mt6]} - onChangeText={handleInputChange} /> ); From 59830b8f8138d918be3f4d6b1a121d694762857b Mon Sep 17 00:00:00 2001 From: RohanSasne Date: Fri, 23 Feb 2024 00:02:32 +0530 Subject: [PATCH 054/556] Set isEditing to true to trim the input --- .../BusinessInfo/substeps/PhoneNumberBusiness.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx index 650dd32e069d..e2746dbab59f 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/PhoneNumberBusiness.tsx @@ -29,8 +29,7 @@ const STEP_FIELDS = [COMPANY_PHONE_NUMBER_KEY]; const validate = (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if ((values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true))) { + if (values.companyPhone && !ValidationUtils.isValidUSPhone(values.companyPhone, true)) { errors.companyPhone = 'bankAccount.error.phoneNumber'; } From 00c5efc5d76f7888b57c4323740d793d2cddcf8f Mon Sep 17 00:00:00 2001 From: smelaa Date: Fri, 23 Feb 2024 09:54:12 +0100 Subject: [PATCH 055/556] PlaybackContext migrated to ts --- ...PlaybackContext.js => PlaybackContext.tsx} | 56 ++++++++++--------- src/components/VideoPlayerContexts/types.ts | 17 ++++++ 2 files changed, 47 insertions(+), 26 deletions(-) rename src/components/VideoPlayerContexts/{PlaybackContext.js => PlaybackContext.tsx} (63%) create mode 100644 src/components/VideoPlayerContexts/types.ts diff --git a/src/components/VideoPlayerContexts/PlaybackContext.js b/src/components/VideoPlayerContexts/PlaybackContext.tsx similarity index 63% rename from src/components/VideoPlayerContexts/PlaybackContext.js rename to src/components/VideoPlayerContexts/PlaybackContext.tsx index b77068f3aea2..d931296831c7 100644 --- a/src/components/VideoPlayerContexts/PlaybackContext.js +++ b/src/components/VideoPlayerContexts/PlaybackContext.tsx @@ -1,52 +1,53 @@ +import type {Video} from 'expo-av'; import PropTypes from 'prop-types'; +import type {ReactNode} from 'react'; import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import type {View} from 'react-native'; import useCurrentReportID from '@hooks/useCurrentReportID'; +import type PlaybackContext from './types'; -const PlaybackContext = React.createContext(null); +const Context = React.createContext(null); -function PlaybackContextProvider({children}) { - const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState(null); - const [sharedElement, setSharedElement] = useState(null); - const [originalParent, setOriginalParent] = useState(null); - const currentVideoPlayerRef = useRef(null); - const {currentReportID} = useCurrentReportID(); +function PlaybackContextProvider({children}: {children: ReactNode}) { + const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState(null); + const [sharedElement, setSharedElement] = useState(null); + const [originalParent, setOriginalParent] = useState(null); + const currentVideoPlayerRef = useRef