diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 286f9e58dd22a..4a56d7fd4e3cf 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -3639,6 +3639,10 @@ const ROUTES = { route: 'domain/:domainAccountID/members', getRoute: (domainAccountID: number) => `domain/${domainAccountID}/members` as const, }, + DOMAIN_MEMBER_DETAILS: { + route: 'domain/:domainAccountID/members/:accountID', + getRoute: (domainAccountID: number, accountID: number) => `domain/${domainAccountID}/members/${accountID}` as const, + }, } as const; /** diff --git a/src/SCREENS.ts b/src/SCREENS.ts index e90eaec5d0100..f0d7002614884 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -876,6 +876,7 @@ const SCREENS = { ADD_PRIMARY_CONTACT: 'Add_Primary_Contact', ADD_ADMIN: 'Domain_Add_Admin', MEMBERS: 'Domain_Members', + MEMBER_DETAILS: 'Member_Details', }, } as const; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index d36a60d7aa789..863f7b3df7461 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -807,6 +807,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/domain/Admins/DomainAdminsSettingsPage').default, [SCREENS.DOMAIN.ADD_PRIMARY_CONTACT]: () => require('../../../../pages/domain/Admins/DomainAddPrimaryContactPage').default, [SCREENS.DOMAIN.ADD_ADMIN]: () => require('../../../../pages/domain/Admins/DomainAddAdminPage').default, + [SCREENS.DOMAIN.MEMBER_DETAILS]: () => require('../../../../pages/domain/Members/DomainMemberDetailsPage').default, }); const TwoFactorAuthenticatorStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts index 3a1e3bad60168..5a00d2e990fd1 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts @@ -6,6 +6,7 @@ const DOMAIN_TO_RHP: Partial['config'] = { [SCREENS.DOMAIN.ADD_PRIMARY_CONTACT]: { path: ROUTES.DOMAIN_ADD_PRIMARY_CONTACT.route, }, + [SCREENS.DOMAIN.MEMBER_DETAILS]: { + path: ROUTES.DOMAIN_MEMBER_DETAILS.route, + }, }, }, [SCREENS.RIGHT_MODAL.TWO_FACTOR_AUTH]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index d6fb92e14e3e8..63b37765cc824 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1387,6 +1387,10 @@ type SettingsNavigatorParamList = { [SCREENS.DOMAIN.ADD_ADMIN]: { domainAccountID: number; }; + [SCREENS.DOMAIN.MEMBER_DETAILS]: { + domainAccountID: number; + accountID: number; + }; } & ReimbursementAccountNavigatorParamList; type DomainCardNavigatorParamList = { diff --git a/src/pages/domain/Admins/DomainAdminDetailsPage.tsx b/src/pages/domain/Admins/DomainAdminDetailsPage.tsx index 0d9eb0b582a40..a36c705c946f2 100644 --- a/src/pages/domain/Admins/DomainAdminDetailsPage.tsx +++ b/src/pages/domain/Admins/DomainAdminDetailsPage.tsx @@ -1,38 +1,28 @@ -import {Str} from 'expensify-common'; +import {adminAccountIDsSelector, domainSettingsPrimaryContactSelector} from '@selectors/Domain'; import React from 'react'; -import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import Avatar from '@components/Avatar'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItem from '@components/MenuItem'; -import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import {ModalActions} from '@components/Modal/Global/ModalContext'; -import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import ScreenWrapper from '@components/ScreenWrapper'; -import ScrollView from '@components/ScrollView'; -import Text from '@components/Text'; import useConfirmModal from '@hooks/useConfirmModal'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; -import {revokeDomainAdminAccess} from '@libs/actions/Domain'; -import {getDisplayNameOrDefault, getPhoneNumber} from '@libs/PersonalDetailsUtils'; +import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; import Navigation from '@navigation/Navigation'; import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@navigation/types'; -import DomainNotFoundPageWrapper from '@pages/domain/DomainNotFoundPageWrapper'; -import CONST from '@src/CONST'; +import BaseDomainMemberDetailsComponent from '@pages/domain/BaseDomainMemberDetailsComponent'; +import {revokeDomainAdminAccess} from '@userActions/Domain'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import {adminAccountIDsSelector, domainSettingsPrimaryContactSelector} from '@src/selectors/Domain'; import type {PersonalDetailsList} from '@src/types/onyx'; type DomainAdminDetailsPageProps = PlatformStackScreenProps; function DomainAdminDetailsPage({route}: DomainAdminDetailsPageProps) { const {domainAccountID, accountID} = route.params; + const styles = useThemeStyles(); const {translate, formatPhoneNumber} = useLocalize(); const icons = useMemoizedLazyExpensifyIcons(['Info', 'ClosedSign'] as const); @@ -53,16 +43,12 @@ function DomainAdminDetailsPage({route}: DomainAdminDetailsPageProps) { selector: (personalDetailsList: OnyxEntry) => personalDetailsList?.[accountID], }); + const domainHasOnlyOneAdmin = adminAccountIDs?.length === 1; const displayName = formatPhoneNumber(getDisplayNameOrDefault(adminPersonalDetails)); const memberLogin = adminPersonalDetails?.login ?? ''; const isCurrentUserPrimaryContact = primaryContact === memberLogin; - const isSMSLogin = Str.isSMSLogin(memberLogin); - const phoneNumber = getPhoneNumber(adminPersonalDetails); - const fallbackIcon = adminPersonalDetails?.fallbackIcon ?? ''; - const domainHasOnlyOneAdmin = adminAccountIDs?.length === 1; const {showConfirmModal} = useConfirmModal(); - const handleRevokeAdminAccess = async () => { const confirmResult = await showConfirmModal({ title: translate('domain.admins.revokeAdminAccess'), @@ -82,65 +68,21 @@ function DomainAdminDetailsPage({route}: DomainAdminDetailsPageProps) { }; return ( - - - - - - - - - - {!!displayName && ( - - {displayName} - - )} - - - - {!domainHasOnlyOneAdmin && ( - - )} - Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getActiveRoute()))} - shouldShowRightIcon - /> - - - - - + + {!domainHasOnlyOneAdmin && ( + + )} + ); } diff --git a/src/pages/domain/BaseDomainMemberDetailsComponent.tsx b/src/pages/domain/BaseDomainMemberDetailsComponent.tsx new file mode 100644 index 0000000000000..2ebd592482636 --- /dev/null +++ b/src/pages/domain/BaseDomainMemberDetailsComponent.tsx @@ -0,0 +1,113 @@ +import {Str} from 'expensify-common'; +import React from 'react'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import Avatar from '@components/Avatar'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import MenuItem from '@components/MenuItem'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {getDisplayNameOrDefault, getPhoneNumber} from '@libs/PersonalDetailsUtils'; +import Navigation from '@navigation/Navigation'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {PersonalDetailsList} from '@src/types/onyx'; +import DomainNotFoundPageWrapper from './DomainNotFoundPageWrapper'; + +type BaseDomainMemberDetailsComponentProps = { + /** Domain ID */ + domainAccountID: number; + + /** User account ID */ + accountID: number; + + /** List of additional fields (e.g., force 2FA) */ + children?: React.ReactNode; +}; + +function BaseDomainMemberDetailsComponent({domainAccountID, accountID, children}: BaseDomainMemberDetailsComponentProps) { + const styles = useThemeStyles(); + const {translate, formatPhoneNumber} = useLocalize(); + const icons = useMemoizedLazyExpensifyIcons(['Info']); + + // The selector depends on the dynamic `accountID`, so it cannot be extracted + // to a static function outside the component. + // eslint-disable-next-line rulesdir/no-inline-useOnyx-selector + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, { + canBeMissing: true, + selector: (personalDetailsList: OnyxEntry) => personalDetailsList?.[accountID], + }); + + const displayName = formatPhoneNumber(getDisplayNameOrDefault(personalDetails)); + const phoneNumber = getPhoneNumber(personalDetails); + const memberLogin = personalDetails?.login ?? ''; + const isSMSLogin = Str.isSMSLogin(memberLogin); + const copyableName = isSMSLogin ? formatPhoneNumber(phoneNumber ?? '') : memberLogin; + + return ( + + + + + + + + + + + + {!!displayName && ( + + {displayName} + + )} + + + + {children} + Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getActiveRoute()))} + shouldShowRightIcon + /> + + + + + + ); +} + +BaseDomainMemberDetailsComponent.displayName = 'BaseDomainMemberDetailsComponent'; + +export default BaseDomainMemberDetailsComponent; diff --git a/src/pages/domain/Members/DomainMemberDetailsPage.tsx b/src/pages/domain/Members/DomainMemberDetailsPage.tsx new file mode 100644 index 0000000000000..d57f13edbc699 --- /dev/null +++ b/src/pages/domain/Members/DomainMemberDetailsPage.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import BaseDomainMemberDetailsComponent from '@pages/domain/BaseDomainMemberDetailsComponent'; +import type SCREENS from '@src/SCREENS'; + +type DomainMemberDetailsPageProps = PlatformStackScreenProps; + +function DomainMemberDetailsPage({route}: DomainMemberDetailsPageProps) { + const {domainAccountID, accountID} = route.params; + + return ( + + ); +} + +DomainMemberDetailsPage.displayName = 'DomainMemberDetailsPage'; + +export default DomainMemberDetailsPage; diff --git a/src/pages/domain/Members/DomainMembersPage.tsx b/src/pages/domain/Members/DomainMembersPage.tsx index d791b2c0a93a1..1d75e1cbff477 100644 --- a/src/pages/domain/Members/DomainMembersPage.tsx +++ b/src/pages/domain/Members/DomainMembersPage.tsx @@ -3,10 +3,12 @@ import React from 'react'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import Navigation from '@navigation/Navigation'; import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types'; import type {DomainSplitNavigatorParamList} from '@navigation/types'; import BaseDomainMembersPage from '@pages/domain/BaseDomainMembersPage'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; type DomainMembersPageProps = PlatformStackScreenProps; @@ -27,7 +29,7 @@ function DomainMembersPage({route}: DomainMembersPageProps) { accountIDs={memberIDs ?? []} headerTitle={translate('domain.members.title')} searchPlaceholder={translate('domain.members.findMember')} - onSelectRow={() => {}} + onSelectRow={(item) => Navigation.navigate(ROUTES.DOMAIN_MEMBER_DETAILS.getRoute(domainAccountID, item.accountID))} headerIcon={illustrations.Profile} /> );