Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#35715 workspace member details page #37715

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
35f5618
basic member details page added
burczu Mar 1, 2024
be52759
passing personal details, policy members and routes to details page
burczu Mar 5, 2024
69ee8a1
passing backTo parameter to the details route
burczu Mar 5, 2024
210d294
showing member details page title from its personal details
burczu Mar 5, 2024
8e0f762
showing avatar added
burczu Mar 5, 2024
6dc39ce
lint and prettier fixes
burczu Mar 5, 2024
9409582
remove from workspace button added
burczu Mar 5, 2024
ec5458b
removing member implementation added
burczu Mar 5, 2024
d50d4a4
Merge branch 'main' into feature/35715-workspace-member-details-page
burczu Mar 6, 2024
d916cae
after main merge fix
burczu Mar 6, 2024
8000440
role and profile buttons added
burczu Mar 6, 2024
e29e0b2
lint and prettier fixes
burczu Mar 6, 2024
b37abfe
role selection modal added
burczu Mar 6, 2024
f583fd4
role selection list added
burczu Mar 6, 2024
f24deca
changing role on the role selection modal added
burczu Mar 6, 2024
27f7917
policy name added as subtitle of the member detail page
burczu Mar 6, 2024
7a91ec8
transform scale helper introduced
burczu Mar 6, 2024
6775986
unnecessary padding removed
burczu Mar 6, 2024
07280c7
back to handled properly
burczu Mar 6, 2024
aa11bf3
using value to update workspace member role
burczu Mar 6, 2024
de3c302
switched to use correct menu item component
burczu Mar 6, 2024
a85b5e7
Merge branch 'main' into feature/35715-workspace-member-details-page
burczu Mar 7, 2024
dc7320e
backTo functionality fixed
burczu Mar 7, 2024
86bf243
navigation type issue fixed
burczu Mar 7, 2024
33509bb
state name adjusted
burczu Mar 7, 2024
1159171
event handler name adjusted
burczu Mar 7, 2024
f2b95c7
admin and paid policy access constraints added
burczu Mar 7, 2024
f78a65e
Merge branch 'main' into feature/35715-workspace-member-details-page
burczu Mar 7, 2024
dcd13bb
disabled showing RHN for non paid workspaces
burczu Mar 7, 2024
0140c46
selecting user on item press removed
burczu Mar 7, 2024
18d88ef
lint/prettier
burczu Mar 7, 2024
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
9 changes: 9 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,15 @@ const ROUTES = {
route: 'workspace/:policyID/tags',
getRoute: (policyID: string) => `workspace/${policyID}/tags` as const,
},
WORKSPACE_MEMBER_DETAILS: {
route: 'workspace/:policyID/members/:accountID',
getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/members/${accountID}`, backTo),
},
WORKSPACE_MEMBER_ROLE_SELECTION: {
route: 'workspace/:policyID/members/:accountID/role-selection',
getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/members/${accountID}/role-selection`, backTo),
},

// Referral program promotion
REFERRAL_DETAILS_MODAL: {
route: 'referral/:contentType',
Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ const SCREENS = {
CATEGORY_CREATE: 'Category_Create',
CATEGORY_SETTINGS: 'Category_Settings',
CATEGORIES_SETTINGS: 'Categories_Settings',
MEMBER_DETAILS: 'Workspace_Member_Details',
MEMBER_DETAILS_ROLE_SELECTION: 'Workspace_Member_Details_Role_Selection',
},

EDIT_REQUEST: {
Expand Down
3 changes: 3 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,9 @@ export default {
genericFailureMessage: 'An error occurred removing a user from the workspace, please try again.',
removeMembersPrompt: 'Are you sure you want to remove these members?',
removeMembersTitle: 'Remove members',
removeMemberButtonTitle: 'Remove from workspace',
removeMemberPrompt: ({memberName}) => `Are you sure you want to remove ${memberName}`,
removeMemberTitle: 'Remove member',
makeMember: 'Make member',
makeAdmin: 'Make admin',
selectAll: 'Select all',
Expand Down
3 changes: 3 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1833,6 +1833,9 @@ export default {
genericFailureMessage: 'Se ha producido un error al intentar eliminar a un usuario del espacio de trabajo. Por favor, inténtalo más tarde.',
removeMembersPrompt: '¿Estás seguro de que deseas eliminar a estos miembros?',
removeMembersTitle: 'Eliminar miembros',
removeMemberButtonTitle: 'Quitar del espacio de trabajo',
removeMemberPrompt: ({memberName}) => `¿Estás seguro de que deseas eliminar a ${memberName}`,
removeMemberTitle: 'Eliminar miembro',
makeMember: 'Hacer miembro',
makeAdmin: 'Hacer administrador',
selectAll: 'Seleccionar todo',
Expand Down
2 changes: 2 additions & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.CURRENCY]: () => require('../../../pages/workspace/WorkspaceProfileCurrencyPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORY_SETTINGS]: () => require('../../../pages/workspace/categories/CategorySettingsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require('../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.MEMBER_DETAILS]: () => require('../../../pages/workspace/members/WorkspaceMemberDetailsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: () => require('../../../pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORY_CREATE]: () => require('../../../pages/workspace/categories/CreateCategoryPage').default as React.ComponentType,
[SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType,
[SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SCREENS from '@src/SCREENS';
const CENTRAL_PANE_TO_RHP_MAPPING: Partial<Record<CentralPaneName, string[]>> = {
[SCREENS.WORKSPACE.PROFILE]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.CURRENCY, SCREENS.WORKSPACE.DESCRIPTION, SCREENS.WORKSPACE.SHARE],
[SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT, SCREENS.WORKSPACE.RATE_AND_UNIT_RATE, SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT],
[SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE],
[SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE, SCREENS.WORKSPACE.MEMBER_DETAILS, SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION],
[SCREENS.WORKSPACE.WORKFLOWS]: [SCREENS.WORKSPACE.WORKFLOWS_APPROVER, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET],
[SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS],
};
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,12 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: {
path: ROUTES.WORKSPACE_CATEGORIES_SETTINGS.route,
},
[SCREENS.WORKSPACE.MEMBER_DETAILS]: {
path: ROUTES.WORKSPACE_MEMBER_DETAILS.route,
},
[SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: {
path: ROUTES.WORKSPACE_MEMBER_ROLE_SELECTION.route,
},
[SCREENS.WORKSPACE.CATEGORY_CREATE]: {
path: ROUTES.WORKSPACE_CATEGORY_CREATE.route,
},
Expand Down
10 changes: 10 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,16 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: {
policyID: string;
};
[SCREENS.WORKSPACE.MEMBER_DETAILS]: {
policyID: string;
accountID: string;
backTo: Routes;
};
[SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: {
policyID: string;
accountID: string;
backTo: Routes;
};
[SCREENS.GET_ASSISTANCE]: {
backTo: Routes;
};
Expand Down
28 changes: 20 additions & 8 deletions src/pages/workspace/WorkspaceMembersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,23 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se
[selectedEmployees, addUser, removeUser],
);

/** Opens the member details page */
const openMemberDetails = useCallback(
(item: MemberOption) => {
if (!isPolicyAdmin) {
Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID));
return;
}

if (!PolicyUtils.isPaidGroupPolicy(policy)) {
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we just wanna navigate to the regular profile here too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't block on this, but we should follow up with a fix.

}

Navigation.navigate(ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(route.params.policyID, item.accountID, Navigation.getActiveRoute()));
},
[isPolicyAdmin, policy, route.params.policyID],
);

/**
* Dismisses the errors on one item
*/
Expand Down Expand Up @@ -465,7 +482,7 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se
onPress={inviteUser}
text={translate('workspace.invite.member')}
icon={Expensicons.Plus}
iconStyles={{transform: [{scale: 0.6}]}}
iconStyles={StyleUtils.getTransformScaleStyle(0.6)}
innerStyles={[isSmallScreenWidth && styles.alignItemsCenter]}
style={[isSmallScreenWidth && styles.flexGrow1]}
/>
Expand Down Expand Up @@ -525,13 +542,8 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se
disableKeyboardShortcuts={removeMembersConfirmModalVisible}
headerMessage={getHeaderMessage()}
headerContent={getHeaderContent()}
onSelectRow={(item) => {
if (!isPolicyAdmin) {
Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID));
return;
}
toggleUser(item.accountID);
}}
onSelectRow={openMemberDetails}
onCheckboxPress={(item) => toggleUser(item.accountID)}
onSelectAll={() => toggleAllUsers(data)}
onDismissError={dismissError}
showLoadingPlaceholder={!isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policyMembers))}
Expand Down
152 changes: 152 additions & 0 deletions src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import Avatar from '@components/Avatar';
import Button from '@components/Button';
import ConfirmModal from '@components/ConfirmModal';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as UserUtils from '@libs/UserUtils';
import Navigation from '@navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading';
import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {PersonalDetails, PersonalDetailsList} from '@src/types/onyx';

type WorkspacePolicyOnyxProps = {
/** Personal details of all users */
personalDetails: OnyxEntry<PersonalDetailsList>;
};

type WorkspaceMemberDetailsPageProps = WithPolicyAndFullscreenLoadingProps & WorkspacePolicyOnyxProps & StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.MEMBER_DETAILS>;

function WorkspaceMemberDetailsPage({personalDetails, policyMembers, policy, route}: WorkspaceMemberDetailsPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const StyleUtils = useStyleUtils();

const [isRemoveMemberConfirmModalVisible, setIsRemoveMemberConfirmModalVisible] = React.useState(false);

const accountID = Number(route.params.accountID);
const policyID = route.params.policyID;
const backTo = route.params.backTo ?? ('' as Route);

const member = policyMembers?.[accountID];
const details = personalDetails?.[accountID] ?? ({} as PersonalDetails);
const avatar = details.avatar ?? UserUtils.getDefaultAvatar();
const fallbackIcon = details.fallbackIcon ?? '';
const displayName = details.displayName ?? '';

const askForConfirmationToRemove = () => {
setIsRemoveMemberConfirmModalVisible(true);
};

const removeUser = useCallback(() => {
Policy.removeMembers([accountID], route.params.policyID);
setIsRemoveMemberConfirmModalVisible(false);
Navigation.goBack(backTo);
}, [accountID, backTo, route.params.policyID]);

const navigateToProfile = useCallback(() => {
Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getActiveRoute()));
}, [accountID]);

const openRoleSelectionModal = useCallback(() => {
Navigation.navigate(ROUTES.WORKSPACE_MEMBER_ROLE_SELECTION.getRoute(route.params.policyID, accountID, Navigation.getActiveRoute()));
}, [accountID, route.params.policyID]);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not checking if user is workspace member or not, ref: #41492

return (
<AdminPolicyAccessOrNotFoundWrapper policyID={policyID}>
<PaidPolicyAccessOrNotFoundWrapper policyID={policyID}>
<ScreenWrapper testID={WorkspaceMemberDetailsPage.displayName}>
<HeaderWithBackButton
title={displayName}
subtitle={policy?.name}
onBackButtonPress={() => Navigation.goBack(backTo)}
/>
<View style={[styles.containerWithSpaceBetween, styles.pointerEventsBoxNone, styles.justifyContentStart]}>
<View style={[styles.avatarSectionWrapper, styles.pb0]}>
<OfflineWithFeedback pendingAction={details.pendingFields?.avatar}>
<Avatar
containerStyles={[styles.avatarXLarge, styles.mv5, styles.noOutline]}
imageStyles={[styles.avatarXLarge]}
source={UserUtils.getAvatar(avatar, accountID)}
size={CONST.AVATAR_SIZE.XLARGE}
fallbackIcon={fallbackIcon}
/>
</OfflineWithFeedback>
{Boolean(details.displayName ?? '') && (
<Text
style={[styles.textHeadline, styles.pre, styles.mb6, styles.w100, styles.textAlignCenter]}
numberOfLines={1}
>
{displayName}
</Text>
)}
<Button
text={translate('workspace.people.removeMemberButtonTitle')}
onPress={askForConfirmationToRemove}
medium
icon={Expensicons.RemoveMembers}
iconStyles={StyleUtils.getTransformScaleStyle(0.8)}
style={styles.mv5}
/>
<ConfirmModal
danger
title={translate('workspace.people.removeMemberTitle')}
isVisible={isRemoveMemberConfirmModalVisible}
onConfirm={removeUser}
onCancel={() => setIsRemoveMemberConfirmModalVisible(false)}
prompt={translate('workspace.people.removeMemberPrompt', {memberName: displayName})}
confirmText={translate('common.remove')}
cancelText={translate('common.cancel')}
/>
</View>
<View style={styles.w100}>
<MenuItemWithTopDescription
title={member?.role === CONST.POLICY.ROLE.ADMIN ? translate('common.admin') : translate('common.member')}
description={translate('common.role')}
shouldShowRightIcon
onPress={openRoleSelectionModal}
/>
<MenuItem
title={translate('common.profile')}
icon={Expensicons.Info}
onPress={navigateToProfile}
shouldShowRightIcon
/>
</View>
</View>
</ScreenWrapper>
</PaidPolicyAccessOrNotFoundWrapper>
</AdminPolicyAccessOrNotFoundWrapper>
);
}

WorkspaceMemberDetailsPage.displayName = 'WorkspaceMemberDetailsPage';

export default withPolicyAndFullscreenLoading(
withOnyx<WorkspaceMemberDetailsPageProps, WorkspacePolicyOnyxProps>({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
})(WorkspaceMemberDetailsPage),
);
Loading
Loading