From e643deb8b949db852e8aadc34c1fdd918cb9b095 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 31 Jan 2024 03:29:04 +0200 Subject: [PATCH 01/16] 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 42aa582deb02445448f4a01f48c3e08232f167dd Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 14 Feb 2024 03:12:23 +0200 Subject: [PATCH 02/16] 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 03/16] 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 add3ffa070a77cd3c821ea7a99d628b7d965ad19 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 18 Feb 2024 00:35:04 +0200 Subject: [PATCH 04/16] 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 05/16] 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 06/16] 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 07/16] 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 08/16] 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 09/16] 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 b48b29356ac5ba80f06fc1b63553ddfe4e5eed14 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 26 Feb 2024 18:59:07 +0200 Subject: [PATCH 10/16] ts fix --- src/types/form/SettingsStatusSetForm.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/types/form/SettingsStatusSetForm.ts b/src/types/form/SettingsStatusSetForm.ts index dcb0211ece6f..53e42ba01cfa 100644 --- a/src/types/form/SettingsStatusSetForm.ts +++ b/src/types/form/SettingsStatusSetForm.ts @@ -1,3 +1,4 @@ +import type {ValueOf} from 'type-fest'; import type Form from './Form'; const INPUT_IDS = { @@ -6,11 +7,16 @@ const INPUT_IDS = { clearAfter: 'clearAfter', } as const; -type SettingsStatusSetForm = Form<{ - [INPUT_IDS.EMOJI_CODE]: string; - [INPUT_IDS.STATUS_TEXT]: string; - [INPUT_IDS.clearAfter]?: string; -}>; +type InputID = ValueOf; + +type SettingsStatusSetForm = Form< + InputID, + { + [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}; From 72c16bff3841e17518b90b3a2da82ab4469bae83 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 26 Feb 2024 19:53:49 +0200 Subject: [PATCH 11/16] ts fix --- src/pages/settings/Profile/ProfilePage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index 7a0c63fb102b..eb27674b1f92 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -47,6 +47,8 @@ function ProfilePage({ state: '', zip: '', country: '', + zipPostCode: '', + addressLine1: '', }, }, currentUserPersonalDetails, From 0360331c9baa821fa443936b930c3486187be40b Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 11 Mar 2024 03:12:36 +0200 Subject: [PATCH 12/16] fixes --- src/ROUTES.ts | 2 +- src/components/MenuItem.tsx | 2 +- .../TextInput/BaseTextInput/types.ts | 5 +-- .../Profile/CustomStatus/StatusPage.tsx | 42 +++++++++++-------- src/pages/settings/Profile/ProfilePage.tsx | 2 +- src/pages/settings/Profile/PronounsPage.tsx | 10 ++--- .../settings/Profile/TimezoneSelectPage.tsx | 5 ++- src/types/onyx/PersonalDetails.ts | 2 +- 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 635bab8f7a47..db0068365760 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -37,7 +37,7 @@ const ROUTES = { }, PROFILE_AVATAR: { route: 'a/:accountID/avatar', - getRoute: (accountID: string | number) => `a/${accountID}/avatar` as const, + getRoute: (accountID: string) => `a/${accountID}/avatar` as const, }, TRANSITION_BETWEEN_APPS: 'transition', diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index dc0d289561d9..f9ce70e851bc 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -194,7 +194,7 @@ type MenuItemBaseProps = { isSmallAvatarSubscriptMenu?: boolean; /** The type of brick road indicator to show. */ - brickRoadIndicator?: ValueOf | '' | null; + brickRoadIndicator?: ValueOf; /** Should render the content in HTML format */ shouldRenderAsHTML?: boolean; diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index 529ea0ff4c25..a0f3d62c3547 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -1,4 +1,4 @@ -import type {GestureResponderEvent, Role, StyleProp, TextInputProps, TextStyle, ViewStyle} from 'react-native'; +import type {GestureResponderEvent, StyleProp, TextInputProps, TextStyle, ViewStyle} from 'react-native'; import type {AnimatedTextInputRef} from '@components/RNTextInput'; import type {MaybePhraseKey} from '@libs/Localize'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -104,9 +104,6 @@ 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 7bc440c97ae5..06f9e4abdd93 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -26,15 +26,14 @@ 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'; +import type {CustomStatusDraft} from '@src/types/onyx'; type StatusPageOnyxProps = { - draftStatus: OnyxEntry; + draftStatus: OnyxEntry; }; type StatusPageProps = StatusPageOnyxProps & WithCurrentUserPersonalDetailsProps; @@ -47,7 +46,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const formRef = useRef(null); - const [brickRoadIndicator, setBrickRoadIndicator] = useState | '' | null>(''); + const [brickRoadIndicator, setBrickRoadIndicator] = useState>(); const currentUserEmojiCode = currentUserPersonalDetails?.status?.emojiCode ?? ''; const currentUserStatusText = currentUserPersonalDetails?.status?.text ?? ''; const currentUserClearAfter = currentUserPersonalDetails?.status?.clearAfter ?? ''; @@ -55,17 +54,22 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) const draftText = draftStatus?.text; const draftClearAfter = draftStatus?.clearAfter; - const defaultEmoji = draftEmojiCode ?? currentUserEmojiCode; - const defaultText = draftText ?? currentUserStatusText; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const defaultEmoji = draftEmojiCode || currentUserEmojiCode; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const defaultText = draftText || currentUserStatusText; const customClearAfter = useMemo(() => { - const dataToShow = draftClearAfter ?? currentUserClearAfter; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 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 === '') { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const clearAfterTime = draftClearAfter || currentUserClearAfter; + if (clearAfterTime === CONST.CUSTOM_STATUS_TYPES.NEVER || clearAfterTime === '') { return true; } @@ -75,10 +79,11 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) const navigateBackToPreviousScreen = useCallback(() => Navigation.goBack(), []); const updateStatus = useCallback( ({emojiCode, statusText}: FormOnyxValues) => { - const clearAfterTime = draftClearAfter ?? currentUserClearAfter ?? CONST.CUSTOM_STATUS_TYPES.NEVER; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 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); + setBrickRoadIndicator(isValidClearAfterDate() ? undefined : CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR); return; } User.updateCustomStatus({ @@ -102,19 +107,20 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) emojiCode: '', clearAfter: DateUtils.getEndOfToday(), }); - formRef.current?.resetForm?.({[INPUT_IDS.EMOJI_CODE]: ''}); + formRef.current?.resetForm({[INPUT_IDS.EMOJI_CODE]: ''}); + InteractionManager.runAfterInteractions(() => { navigateBackToPreviousScreen(); }); }; - useEffect(() => setBrickRoadIndicator(isValidClearAfterDate() ? null : CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR), [isValidClearAfterDate]); + useEffect(() => setBrickRoadIndicator(isValidClearAfterDate() ? undefined : CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR), [isValidClearAfterDate]); useEffect(() => { if (!currentUserEmojiCode && !currentUserClearAfter && !draftClearAfter) { - User.updateDraftCustomStatus({clearAfter: DateUtils.getEndOfToday()} as Status); + User.updateDraftCustomStatus({clearAfter: DateUtils.getEndOfToday()}); } else { - User.updateDraftCustomStatus({clearAfter: currentUserClearAfter} as Status); + User.updateDraftCustomStatus({clearAfter: currentUserClearAfter}); } return () => User.clearDraftCustomStatus(); @@ -123,7 +129,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) const validateForm = useCallback((): FormInputErrors => { if (brickRoadIndicator) { - return {clearAfter: '' as TranslationPaths}; + return {clearAfter: ''}; } return {}; }, [brickRoadIndicator]); @@ -161,7 +167,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) inputID={INPUT_IDS.EMOJI_CODE} // @ts-expect-error TODO: Remove ts-expect-error when EmojiPickerButtonDropdown migration is done accessibilityLabel={INPUT_IDS.EMOJI_CODE} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} defaultValue={defaultEmoji} style={styles.mb3} /> @@ -184,7 +190,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) containerStyle={styles.pr2} brickRoadIndicator={brickRoadIndicator} /> - {(!!currentUserEmojiCode ?? !!currentUserStatusText) && ( + {(!!currentUserEmojiCode || !!currentUserStatusText) && ( - {isLoadingApp && currentUserPersonalDetails.pronouns ? ( + {isLoadingApp && !currentUserPersonalDetails.pronouns ? ( ) : ( <> diff --git a/src/pages/settings/Profile/TimezoneSelectPage.tsx b/src/pages/settings/Profile/TimezoneSelectPage.tsx index a9e223ce6d40..3aff5f820cf8 100644 --- a/src/pages/settings/Profile/TimezoneSelectPage.tsx +++ b/src/pages/settings/Profile/TimezoneSelectPage.tsx @@ -1,4 +1,5 @@ import React, {useState} from 'react'; +import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; @@ -21,12 +22,12 @@ type TimezoneSelectPageProps = Pick `${text}-${new Date().getTime()}`; -const getUserTimezone = ({currentUserPersonalDetails}: Pick) => +const getUserTimezone = (currentUserPersonalDetails: ValueOf) => currentUserPersonalDetails?.timezone ?? CONST.DEFAULT_TIME_ZONE; function TimezoneSelectPage({currentUserPersonalDetails}: TimezoneSelectPageProps) { const {translate} = useLocalize(); - const timezone = getUserTimezone({currentUserPersonalDetails}); + const timezone = getUserTimezone(currentUserPersonalDetails); const allTimezones = useInitialValue(() => TIMEZONES.filter((tz: string) => !tz.startsWith('Etc/GMT')).map((text: string) => ({ text, diff --git a/src/types/onyx/PersonalDetails.ts b/src/types/onyx/PersonalDetails.ts index c9bbe0eac66f..42482f9104dc 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 fe9dbc0bb977dce0a908b016d45b8d1454657466 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 11 Mar 2024 03:20:00 +0200 Subject: [PATCH 13/16] ts fixes --- src/libs/actions/User.ts | 4 ++-- src/pages/settings/Profile/CustomStatus/StatusPage.tsx | 2 +- src/types/onyx/CustomStatusDraft.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index fdd657f801f2..6655d78cb0a8 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -33,7 +33,7 @@ import playSoundExcludingMobile from '@libs/Sound/playSoundExcludingMobile'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {BlockedFromConcierge, FrequentlyUsedEmoji, Policy} from '@src/types/onyx'; +import type {BlockedFromConcierge, CustomStatusDraft, FrequentlyUsedEmoji, Policy} from '@src/types/onyx'; import type Login from '@src/types/onyx/Login'; import type {OnyxServerUpdate} from '@src/types/onyx/OnyxUpdatesFromServer'; import type OnyxPersonalDetails from '@src/types/onyx/PersonalDetails'; @@ -945,7 +945,7 @@ function clearCustomStatus() { * @param status.emojiCode * @param status.clearAfter - ISO 8601 format string, which represents the time when the status should be cleared */ -function updateDraftCustomStatus(status: Status) { +function updateDraftCustomStatus(status: CustomStatusDraft) { Onyx.merge(ONYXKEYS.CUSTOM_STATUS_DRAFT, status); } diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index 06f9e4abdd93..c189ac21a925 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -175,7 +175,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) InputComponent={TextInput} ref={inputCallbackRef} inputID={INPUT_IDS.STATUS_TEXT} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} label={translate('statusPage.message')} accessibilityLabel={INPUT_IDS.STATUS_TEXT} defaultValue={defaultText} diff --git a/src/types/onyx/CustomStatusDraft.ts b/src/types/onyx/CustomStatusDraft.ts index b2801a1d89e0..73c8fa4baa1a 100644 --- a/src/types/onyx/CustomStatusDraft.ts +++ b/src/types/onyx/CustomStatusDraft.ts @@ -1,9 +1,9 @@ type CustomStatusDraft = { /** The emoji code of the draft status */ - emojiCode: string; + emojiCode?: string; /** The text of the draft status */ - text: string; + text?: string; /** ISO 8601 format string, which represents the time when the status should be cleared */ clearAfter: string; From 18bb08b8f987bb572b6ca65bc15aaeb356c2a163 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 11 Mar 2024 03:23:34 +0200 Subject: [PATCH 14/16] moving clearDraftCustomStatus out of the runAfterInteractions --- 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 c189ac21a925..24839ff17a41 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -91,9 +91,8 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) emojiCode: !emojiCode && statusText ? initialEmoji : emojiCode, clearAfter: clearAfterTime !== CONST.CUSTOM_STATUS_TYPES.NEVER ? clearAfterTime : '', }); - + User.clearDraftCustomStatus(); InteractionManager.runAfterInteractions(() => { - User.clearDraftCustomStatus(); navigateBackToPreviousScreen(); }); }, From b46f64c5900564141f47e2914310f08a93fca585 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 12 Mar 2024 14:31:23 +0200 Subject: [PATCH 15/16] minor --- src/pages/settings/Profile/CustomStatus/StatusPage.tsx | 2 -- src/pages/settings/Profile/ProfilePage.tsx | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index 24839ff17a41..bb7be4a6866c 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -53,8 +53,6 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) const draftEmojiCode = draftStatus?.emojiCode; const draftText = draftStatus?.text; const draftClearAfter = draftStatus?.clearAfter; - - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const defaultEmoji = draftEmojiCode || currentUserEmojiCode; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index 255fd3b72ddd..58a323bf0a10 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -45,12 +45,11 @@ function ProfilePage({ dob: '', address: { street: '', + street2: '', city: '', state: '', zip: '', country: '', - zipPostCode: '', - addressLine1: '', }, }, currentUserPersonalDetails, From f128c278ccb63d4e74c4619b6610ce581a3e231e Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 13 Mar 2024 17:06:00 +0200 Subject: [PATCH 16/16] FormProvider ref type --- src/components/Form/FormProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 3b1f56554f04..e9c5b80b37ac 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -389,6 +389,6 @@ 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; export type {FormProviderProps};