Skip to content

Commit

Permalink
refactor: Remove duplication in members pages
Browse files Browse the repository at this point in the history
  • Loading branch information
josebui committed Nov 22, 2023
1 parent 0352a62 commit c966dc1
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 488 deletions.
3 changes: 3 additions & 0 deletions src/collaboration/collaborationContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,15 @@ export const CollaborationContextProvider = props => {
'onMemberJoin',
'onMemberRemove',
'onMemberRoleChange',
'onMemberApprove',
'MemberLeaveButton',
'MemberRemoveButton',
'MemberJoinButton',
'MemberRequestJoinButton',
'MemberRequestCancelButton',
'updateOwner',
'acceptedRoles',
'allowedToManageMembers',
],
props
),
Expand Down
233 changes: 233 additions & 0 deletions src/collaboration/components/MembersPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
* Copyright © 2021-2023 Technology Matters
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import React, { useMemo } from 'react';
import _ from 'lodash/fp';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { LoadingButton } from '@mui/lab';
import { ListItem, Stack, Typography } from '@mui/material';

import { withProps } from 'react-hoc';

import {
MEMBERSHIP_STATUS_APPROVED,
MEMBERSHIP_STATUS_PENDING,
} from 'collaboration/collaborationConstants';
import { useCollaborationContext } from 'collaboration/collaborationContext';
import MemberName from 'collaboration/components/MemberName';
import MembershipsList from 'collaboration/components/MembershipsList';
import RoleSelect from 'collaboration/components/RoleSelect';
import ConfirmButton from 'common/components/ConfirmButton';
import List from 'common/components/List';
import PageContainer from 'layout/PageContainer';
import PageHeader from 'layout/PageHeader';

import MembershipPendingWarning from './MembershipPendingWarning';

const Header = () => {
const { t } = useTranslation();
const { owner, allowedToManageMembers } = useCollaborationContext();

return (
<>
<PageHeader
header={t(
allowedToManageMembers
? 'group.members_title_manager'
: 'group.members_title_member',
{ name: _.get('name', owner) }
)}
/>
<Typography
variant="body2"
display="block"
sx={{
marginBottom: 3,
marginTop: 2,
}}
>
{t(
allowedToManageMembers
? 'group.members_description_manager'
: 'group.members_description_member',
{ name: _.get('name', owner) }
)}
</Typography>
</>
);
};

const RemoveButton = props => {
const { membership, tabIndex } = props;
const { data: currentUser } = useSelector(state => state.account.currentUser);
const {
owner,
onMemberRemove,
MemberLeaveButton,
MemberRemoveButton,
allowedToManageMembers,
} = useCollaborationContext();

if (membership.user.email === currentUser.email) {
return (
<MemberLeaveButton
onConfirm={() => onMemberRemove(membership)}
owner={owner}
loading={membership.fetching}
buttonProps={{ tabIndex }}
/>
);
}

if (!allowedToManageMembers) {
return null;
}

return (
<MemberRemoveButton
onConfirm={() => onMemberRemove(membership)}
owner={owner}
membership={membership}
loading={membership.fetching}
buttonProps={{ tabIndex }}
/>
);
};

const PendingApprovals = props => {
const { t } = useTranslation();
const { memberships } = props;
const { owner, allowedToManageMembers, onMemberApprove, onMemberRemove } =
useCollaborationContext();

if (!allowedToManageMembers || _.isEmpty(memberships)) {
return null;
}

return (
<section aria-labelledby="members-pending-title-id">
<Typography
id="members-pending-title-id"
variant="h2"
sx={{ marginBottom: 2 }}
>
{t('group.members_list_pending_title')}
</Typography>
<MembershipPendingWarning count={memberships.length} sx={{ mb: 2 }} />
<List aria-labelledby="members-pending-title-id">
{memberships.map(membership => (
<ListItem
key={membership.membershipId}
aria-label={t('user.full_name', { user: membership.user })}
secondaryAction={
<Stack spacing={2} direction="row">
<LoadingButton
variant="contained"
loading={membership.fetching}
onClick={() => onMemberApprove(membership)}
>
{t('group.members_list_pending_approve')}
</LoadingButton>
<ConfirmButton
onConfirm={() => onMemberRemove(membership)}
confirmTitle={t(
'group.members_list_pending_confirmation_title'
)}
confirmMessage={t(
'group.members_list_pending_confirmation_message',
{
userName: t('user.full_name', { user: membership.user }),
name: owner?.name,
}
)}
confirmButton={t(
'group.members_list_pending_confirmation_button'
)}
buttonLabel={t('group.members_list_pending_reject')}
loading={membership.fetching}
/>
</Stack>
}
>
<MemberName membership={membership} />
</ListItem>
))}
</List>
</section>
);
};

const MembersPage = () => {
const { owner, acceptedRoles, allowedToManageMembers, onMemberRoleChange } =
useCollaborationContext();
const { t } = useTranslation();

const memberships = useMemo(
() => owner?.membershipsInfo?.membershipsSample || [],
[owner]
);

const { pendingMemberships, activeMemberships } = useMemo(() => {
const groupedByStatus = _.flow(
_.values,
_.groupBy('membershipStatus')
)(memberships);

return {
pendingMemberships: groupedByStatus[MEMBERSHIP_STATUS_PENDING],
activeMemberships: groupedByStatus[MEMBERSHIP_STATUS_APPROVED],
};
}, [memberships]);

return (
<PageContainer>
<Header />
<PendingApprovals memberships={pendingMemberships} />
<section aria-labelledby="members-list-title-id">
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
sx={{
marginTop: 2,
marginBottom: 2,
}}
>
{allowedToManageMembers && (
<Typography variant="h2" id="members-list-title-id">
{t('group.members_list_title', {
name: owner?.name,
})}
</Typography>
)}
</Stack>
<MembershipsList
memberships={activeMemberships}
RoleComponent={withProps(RoleSelect, {
roles: acceptedRoles,
allowedToManageMembers,
label: t('memberships.members_list_role_select_label'),
onMemberRoleChange,
})}
RemoveComponent={RemoveButton}
/>
</section>
</PageContainer>
);
};

export default MembersPage;
55 changes: 25 additions & 30 deletions src/collaboration/components/RoleSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,14 @@
import React, { useCallback } from 'react';
import { MenuItem, Select, Typography } from '@mui/material';

import Restricted from 'permissions/components/Restricted';

const RoleSelect = props => {
const {
roles,
membership,
tabIndex,
onMemberRoleChange,
permission,
resource,
label,
allowedToManageMembers,
} = props;

const onChange = useCallback(
Expand All @@ -38,34 +35,32 @@ const RoleSelect = props => {
[onMemberRoleChange, membership]
);

if (!allowedToManageMembers) {
return (
<Typography>
{roles.find(role => role.value === membership.userRole).label}
</Typography>
);
}

return (
<Restricted
permission={permission}
resource={resource}
FallbackComponent={() => (
<Typography>
{roles.find(role => role.value === membership.userRole).label}
</Typography>
)}
<Select
variant="standard"
value={membership.userRole}
onChange={onChange}
disabled={membership.fetching}
inputProps={{
tabIndex,
'aria-label': label,
}}
disableUnderline
>
<Select
variant="standard"
value={membership.userRole}
onChange={onChange}
disabled={membership.fetching}
inputProps={{
tabIndex,
'aria-label': label,
}}
disableUnderline
>
{roles.map(role => (
<MenuItem key={role.key} value={role.value}>
{role.label}
</MenuItem>
))}
</Select>
</Restricted>
{roles.map(role => (
<MenuItem key={role.key} value={role.value}>
{role.label}
</MenuItem>
))}
</Select>
);
};

Expand Down
4 changes: 2 additions & 2 deletions src/group/components/GroupsHomeCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ import {

import { withProps } from 'react-hoc';

import MembershipPendingWarning from 'collaboration/components/MembershipPendingWarning';
import Restricted from 'permissions/components/Restricted';
import {
MEMBERSHIP_STATUS_APPROVED,
MEMBERSHIP_STATUS_PENDING,
} from 'group/membership/components/groupMembershipConstants';
import GroupMembershipPendingWarning from 'group/membership/components/GroupMembershipPendingWarning';
import HomeCard from 'home/components/HomeCard';

import theme from 'theme';
Expand Down Expand Up @@ -81,7 +81,7 @@ const GroupItem = ({ group, index }) => {
)}
<Restricted permission="group.change" resource={group}>
{pendingCount > 0 && (
<GroupMembershipPendingWarning
<MembershipPendingWarning
link
count={pendingCount}
onPendingClick={() => navigate(`/groups/${group.slug}/members`)}
Expand Down
Loading

0 comments on commit c966dc1

Please sign in to comment.