Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,9 @@ const ONYXKEYS = {
/** Stores domain admin account ID */
EXPENSIFY_ADMIN_ACCESS_PREFIX: 'expensify_adminPermissions_',

/** Stores domain security group */
DOMAIN_SECURITY_GROUP_PREFIX: 'expensify_securityGroup_',

/** Pending actions for a domain */
DOMAIN_PENDING_ACTIONS: 'domainPendingActions_',

Expand Down Expand Up @@ -1160,6 +1163,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.EXPENSIFY_ADMIN_ACCESS_PREFIX]: number;
[ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS]: OnyxTypes.DomainPendingActions;
[ONYXKEYS.COLLECTION.DOMAIN_ERRORS]: OnyxTypes.DomainErrors;
[ONYXKEYS.COLLECTION.DOMAIN_SECURITY_GROUP_PREFIX]: OnyxTypes.DomainSecurityGroup;
};

type OnyxValuesMapping = {
Expand Down
12 changes: 12 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3575,6 +3575,18 @@ const ROUTES = {
route: 'domain/:domainAccountID/admins/invite',
getRoute: (domainAccountID: number) => `domain/${domainAccountID}/admins/invite` as const,
},
DOMAIN_MEMBERS: {
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,
},
DOMAIN_LOCK_ACCOUNT: {
route: 'domain/:domainAccountID/members/:accountID/lock-account',
getRoute: (domainAccountID: number, accountID: number) => `domain/${domainAccountID}/members/${accountID}/lock-account` as const,
},
} as const;

/**
Expand Down
3 changes: 3 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,9 @@ const SCREENS = {
ADMINS_SETTINGS: 'Admins_Settings',
ADD_PRIMARY_CONTACT: 'Add_Primary_Contact',
ADD_ADMIN: 'Domain_Add_Admin',
MEMBERS: 'Domain_Members',
MEMBER_DETAILS: 'Member_Details',
MEMBER_LOCK_ACCOUNT: 'Member_Lock_Account',
},
} as const;

Expand Down
9 changes: 9 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7888,6 +7888,15 @@ const translations = {
invite: 'Invite',
addAdminError: 'Unable to add this member as an admin. Please try again.',
},
members: {
title: 'Members',
findMember: 'Find member',
closeAccount: 'Close account',
reportSuspiciousActivityPrompt: (email: string) =>
`Are you sure? This will lock <strong>${email}'s</strong> account. <br /><br /> Our team will then review the account and remove any unauthorized access. To regain access, they'll need to work with Concierge.`,
reportSuspiciousActivityConfirmationTitle: 'We’ve received your request',
reportSuspiciousActivityConfirmationPrompt: 'We’ll review the account to verify it’s safe to unlock and reach out via Concierge with any questions.',
},
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.DOMAIN.ADMINS_SETTINGS]: () => require<ReactComponentModule>('../../../../pages/domain/Admins/DomainAdminsSettingsPage').default,
[SCREENS.DOMAIN.ADD_PRIMARY_CONTACT]: () => require<ReactComponentModule>('../../../../pages/domain/Admins/DomainAddPrimaryContactPage').default,
[SCREENS.DOMAIN.ADD_ADMIN]: () => require<ReactComponentModule>('../../../../pages/domain/Admins/DomainAddAdminPage').default,
[SCREENS.DOMAIN.MEMBER_DETAILS]: () => require<ReactComponentModule>('../../../../pages/domain/Members/MembersDetailsPage').default,
[SCREENS.DOMAIN.MEMBER_LOCK_ACCOUNT]: () => require<ReactComponentModule>('../../../../pages/domain/Members/DomainReportSuspiciousActivityPage').default,
});

const TwoFactorAuthenticatorStackNavigator = createModalStackNavigator<EnablePaymentsNavigatorParamList>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type ReactComponentModule from '@src/types/utils/ReactComponentModule';
const loadDomainInitialPage = () => require<ReactComponentModule>('../../../../pages/domain/DomainInitialPage').default;
const loadDomainSamlPage = () => require<ReactComponentModule>('../../../../pages/domain/DomainSamlPage').default;
const loadDomainAdminsPage = () => require<ReactComponentModule>('../../../../pages/domain/Admins/DomainAdminsPage').default;
const loadDomainMembersPage = () => require<ReactComponentModule>('../../../../pages/domain/Members/DomainMembersPage').default;

const Split = createSplitNavigator<DomainSplitNavigatorParamList>();

Expand Down Expand Up @@ -50,6 +51,12 @@ function DomainSplitNavigator({route, navigation}: PlatformStackScreenProps<Auth
name={SCREENS.DOMAIN.ADMINS}
getComponent={loadDomainAdminsPage}
/>

<Split.Screen
key={SCREENS.DOMAIN.MEMBERS}
name={SCREENS.DOMAIN.MEMBERS}
getComponent={loadDomainMembersPage}
/>
</Split.Navigator>
</View>
</FocusTrapForScreens>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const DOMAIN_TO_RHP: Partial<Record<keyof DomainSplitNavigatorParamList, string[
[SCREENS.DOMAIN.INITIAL]: [],
[SCREENS.DOMAIN.SAML]: [SCREENS.DOMAIN.VERIFY, SCREENS.DOMAIN.VERIFIED],
[SCREENS.DOMAIN.ADMINS]: [SCREENS.DOMAIN.ADMIN_DETAILS, SCREENS.DOMAIN.ADMINS_SETTINGS, SCREENS.DOMAIN.ADD_PRIMARY_CONTACT, SCREENS.DOMAIN.ADD_ADMIN],
[SCREENS.DOMAIN.MEMBERS]: [SCREENS.DOMAIN.MEMBER_DETAILS, SCREENS.DOMAIN.MEMBER_LOCK_ACCOUNT],
};

export default DOMAIN_TO_RHP;
9 changes: 9 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,12 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
[SCREENS.DOMAIN.ADD_PRIMARY_CONTACT]: {
path: ROUTES.DOMAIN_ADD_PRIMARY_CONTACT.route,
},
[SCREENS.DOMAIN.MEMBER_DETAILS]: {
path: ROUTES.DOMAIN_MEMBER_DETAILS.route,
},
[SCREENS.DOMAIN.MEMBER_LOCK_ACCOUNT]: {
path: ROUTES.DOMAIN_LOCK_ACCOUNT.route,
},
},
},
[SCREENS.RIGHT_MODAL.TWO_FACTOR_AUTH]: {
Expand Down Expand Up @@ -1999,6 +2005,9 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
[SCREENS.DOMAIN.ADMINS]: {
path: ROUTES.DOMAIN_ADMINS.route,
},
[SCREENS.DOMAIN.MEMBERS]: {
path: ROUTES.DOMAIN_MEMBERS.route,
},
},
},

Expand Down
15 changes: 15 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,14 @@ type SettingsNavigatorParamList = {
[SCREENS.DOMAIN.ADD_ADMIN]: {
domainAccountID: number;
};
[SCREENS.DOMAIN.MEMBER_DETAILS]: {
domainAccountID: number;
accountID: number;
};
[SCREENS.DOMAIN.MEMBER_LOCK_ACCOUNT]: {
domainAccountID: number;
accountID: number;
};
} & ReimbursementAccountNavigatorParamList;

type DomainCardNavigatorParamList = {
Expand Down Expand Up @@ -2487,6 +2495,13 @@ type DomainSplitNavigatorParamList = {
[SCREENS.DOMAIN.ADMINS]: {
domainAccountID: number;
};
[SCREENS.DOMAIN.MEMBERS]: {
domainAccountID: number;
};
[SCREENS.DOMAIN.MEMBER_DETAILS]: {
domainAccountID: number;
accountID: number;
};
};

type OnboardingModalNavigatorParamList = {
Expand Down
4 changes: 2 additions & 2 deletions src/libs/actions/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@

let currentUserAccountID = -1;
let currentEmail = '';
Onyx.connect({

Check warning on line 66 in src/libs/actions/User.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

Check warning on line 66 in src/libs/actions/User.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
currentUserAccountID = value?.accountID ?? CONST.DEFAULT_NUMBER_ID;
Expand All @@ -72,7 +72,7 @@
});

let allPolicies: OnyxCollection<Policy>;
Onyx.connect({

Check warning on line 75 in src/libs/actions/User.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

Check warning on line 75 in src/libs/actions/User.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (value) => (allPolicies = value),
Expand Down Expand Up @@ -1406,7 +1406,7 @@
Onyx.set(ONYXKEYS.IS_DEBUG_MODE_ENABLED, isDebugModeEnabled);
}

function lockAccount() {
function lockAccount(accountID?: number) {
const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.ACCOUNT>> = [
{
onyxMethod: Onyx.METHOD.MERGE,
Expand Down Expand Up @@ -1445,7 +1445,7 @@
];

const params: LockAccountParams = {
accountID: currentUserAccountID,
accountID: accountID ?? currentUserAccountID,
};

// We need to know if this command fails so that we can navigate the user to a failure page.
Expand Down
102 changes: 21 additions & 81 deletions src/pages/domain/Admins/DomainAdminDetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,100 +1,40 @@
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 React, {useMemo} from 'react';
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 type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@navigation/types';
import DomainNotFoundPageWrapper from '@pages/domain/DomainNotFoundPageWrapper';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import BaseDomainMemberDetailsComponent from '@pages/domain/BaseDomainMemberDetailsComponent';
import type {MemberDetailsMenuItem} from '@pages/domain/BaseDomainMemberDetailsComponent';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {PersonalDetailsList} from '@src/types/onyx';

type DomainAdminDetailsPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.DOMAIN.ADMIN_DETAILS>;

function DomainAdminDetailsPage({route}: DomainAdminDetailsPageProps) {
const {domainAccountID, accountID} = route.params;
const styles = useThemeStyles();
const {translate, formatPhoneNumber} = useLocalize();
const {translate} = useLocalize();
const icons = useMemoizedLazyExpensifyIcons(['Info'] as const);

// eslint-disable-next-line rulesdir/no-inline-useOnyx-selector
const [adminPersonalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {
canBeMissing: true,
selector: (personalDetailsList: OnyxEntry<PersonalDetailsList>) => personalDetailsList?.[accountID],
});

const displayName = formatPhoneNumber(getDisplayNameOrDefault(adminPersonalDetails));
const memberLogin = adminPersonalDetails?.login ?? '';
const isSMSLogin = Str.isSMSLogin(memberLogin);
const phoneNumber = getPhoneNumber(adminPersonalDetails);
const fallbackIcon = adminPersonalDetails?.fallbackIcon ?? '';
const menuItems = useMemo(
(): MemberDetailsMenuItem[] => [
{
key: 'profile',
title: translate('common.profile'),
icon: icons.Info,
onPress: () => Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getActiveRoute())),
shouldShowRightIcon: true,
},
],
[accountID, icons.Info, translate],
);

return (
<DomainNotFoundPageWrapper domainAccountID={domainAccountID}>
<ScreenWrapper
enableEdgeToEdgeBottomSafeAreaPadding
testID={DomainAdminDetailsPage.displayName}
>
<HeaderWithBackButton title={displayName} />
<ScrollView addBottomSafeAreaPadding>
<View style={[styles.containerWithSpaceBetween, styles.pointerEventsBoxNone, styles.justifyContentStart]}>
<View style={[styles.avatarSectionWrapper, styles.pb0]}>
<OfflineWithFeedback pendingAction={adminPersonalDetails?.pendingFields?.avatar}>
<Avatar
containerStyles={[styles.avatarXLarge, styles.mb4, styles.noOutline]}
imageStyles={[styles.avatarXLarge]}
source={adminPersonalDetails?.avatar}
avatarID={accountID}
type={CONST.ICON_TYPE_AVATAR}
size={CONST.AVATAR_SIZE.X_LARGE}
fallbackIcon={fallbackIcon}
/>
</OfflineWithFeedback>
{!!displayName && (
<Text
style={[styles.textHeadline, styles.pre, styles.mb8, styles.w100, styles.textAlignCenter]}
numberOfLines={1}
>
{displayName}
</Text>
)}
</View>
<View style={styles.w100}>
<MenuItemWithTopDescription
title={isSMSLogin ? formatPhoneNumber(phoneNumber ?? '') : memberLogin}
copyValue={isSMSLogin ? formatPhoneNumber(phoneNumber ?? '') : memberLogin}
description={translate(isSMSLogin ? 'common.phoneNumber' : 'common.email')}
interactive={false}
copyable
/>
<MenuItem
style={styles.mb5}
title={translate('common.profile')}
icon={icons.Info}
onPress={() => Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getActiveRoute()))}
shouldShowRightIcon
/>
</View>
</View>
</ScrollView>
</ScreenWrapper>
</DomainNotFoundPageWrapper>
<BaseDomainMemberDetailsComponent
domainAccountID={domainAccountID}
accountID={accountID}
menuItems={menuItems}
/>
);
}

Expand Down
Loading
Loading