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

feat: Implement users page deactivated tab #30532

Merged
merged 16 commits into from
Nov 29, 2023
Merged
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
2 changes: 1 addition & 1 deletion apps/meteor/client/components/UserCard/UserCardRoles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import UserCardInfo from './UserCardInfo';

const UserCardRoles = ({ children }: { children: ReactNode }): ReactElement => (
<Box m='neg-x2'>
<UserCardInfo flexWrap='wrap' display='flex' flexShrink={0}>
<UserCardInfo mb={8} flexWrap='wrap' display='flex' flexShrink={0}>
{children}
</UserCardInfo>
</Box>
Expand Down
130 changes: 64 additions & 66 deletions apps/meteor/client/components/UserInfo/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import { useUserCustomFields } from '../../hooks/useUserCustomFields';
import { useUserDisplayName } from '../../hooks/useUserDisplayName';
import { ContextualbarScrollableContent } from '../Contextualbar';
import InfoPanel from '../InfoPanel';
import InfoPanelAvatar from '../InfoPanel/InfoPanelAvatar';
import InfoPanelField from '../InfoPanel/InfoPanelField';
import InfoPanelLabel from '../InfoPanel/InfoPanelLabel';
import InfoPanelSection from '../InfoPanel/InfoPanelSection';
import InfoPanelText from '../InfoPanel/InfoPanelText';
import InfoPanelTitle from '../InfoPanel/InfoPanelTitle';
import MarkdownText from '../MarkdownText';
import UTCClock from '../UTCClock';
import UserCard from '../UserCard';
Expand Down Expand Up @@ -58,7 +64,6 @@ const UserInfo = ({
status,
statusText,
customFields,
canViewAllInfo,
actions,
reason,
...props
Expand All @@ -72,124 +77,117 @@ const UserInfo = ({
<ContextualbarScrollableContent p={24} {...props}>
<InfoPanel>
{username && (
<InfoPanel.Avatar>
<InfoPanelAvatar>
<UserInfoAvatar username={username} etag={avatarETag} />
</InfoPanel.Avatar>
</InfoPanelAvatar>
)}

{actions && <InfoPanel.Section>{actions}</InfoPanel.Section>}
{actions && <InfoPanelSection>{actions}</InfoPanelSection>}

<InfoPanel.Section>
{userDisplayName && <InfoPanel.Title icon={status} title={userDisplayName} />}
<InfoPanelSection>
{userDisplayName && <InfoPanelTitle icon={status} title={userDisplayName} />}
{statusText && (
<InfoPanel.Text>
<InfoPanelText>
<MarkdownText content={statusText} parseEmoji={true} variant='inline' />
</InfoPanel.Text>
</InfoPanelText>
)}
</InfoPanel.Section>
</InfoPanelSection>

<InfoPanel.Section>
<InfoPanelSection>
{reason && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Reason_for_joining')}</InfoPanel.Label>
<InfoPanel.Text>{reason}</InfoPanel.Text>
</InfoPanel.Field>
<InfoPanelField>
<InfoPanelLabel>{t('Reason_for_joining')}</InfoPanelLabel>
<InfoPanelText>{reason}</InfoPanelText>
</InfoPanelField>
)}
{nickname && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Nickname')}</InfoPanel.Label>
<InfoPanel.Text>{nickname}</InfoPanel.Text>
</InfoPanel.Field>
<InfoPanelField>
<InfoPanelLabel>{t('Nickname')}</InfoPanelLabel>
<InfoPanelText>{nickname}</InfoPanelText>
</InfoPanelField>
)}

{roles.length !== 0 && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Roles')}</InfoPanel.Label>
<InfoPanelField>
<InfoPanelLabel>{t('Roles')}</InfoPanelLabel>
<UserCard.Roles>{roles}</UserCard.Roles>
</InfoPanel.Field>
</InfoPanelField>
)}

{username && username !== name && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Username')}</InfoPanel.Label>
<InfoPanel.Text data-qa='UserInfoUserName'>{username}</InfoPanel.Text>
</InfoPanel.Field>
<InfoPanelField>
<InfoPanelLabel>{t('Username')}</InfoPanelLabel>
<InfoPanelText data-qa='UserInfoUserName'>{username}</InfoPanelText>
</InfoPanelField>
)}

{Number.isInteger(utcOffset) && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Local_Time')}</InfoPanel.Label>
<InfoPanel.Text>{utcOffset && <UTCClock utcOffset={utcOffset} />}</InfoPanel.Text>
</InfoPanel.Field>
<InfoPanelField>
<InfoPanelLabel>{t('Local_Time')}</InfoPanelLabel>
<InfoPanelText>{utcOffset && <UTCClock utcOffset={utcOffset} />}</InfoPanelText>
</InfoPanelField>
)}

{bio && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Bio')}</InfoPanel.Label>
<InfoPanel.Text withTruncatedText={false}>
<InfoPanelField>
<InfoPanelLabel>{t('Bio')}</InfoPanelLabel>
<InfoPanelText withTruncatedText={false}>
<MarkdownText variant='inline' content={bio} />
</InfoPanel.Text>
</InfoPanel.Field>
</InfoPanelText>
</InfoPanelField>
)}

{canViewAllInfo && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Last_login')}</InfoPanel.Label>
<InfoPanel.Text>{lastLogin ? timeAgo(lastLogin) : t('Never')}</InfoPanel.Text>
</InfoPanel.Field>
)}

{name && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Full_Name')}</InfoPanel.Label>
<InfoPanel.Text>{name}</InfoPanel.Text>
</InfoPanel.Field>
{Number.isInteger(utcOffset) && (
<InfoPanelField>
<InfoPanelLabel>{t('Last_login')}</InfoPanelLabel>
<InfoPanelText>{lastLogin ? timeAgo(lastLogin) : t('Never')}</InfoPanelText>
</InfoPanelField>
)}

{phone && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Phone')}</InfoPanel.Label>
<InfoPanel.Text display='flex' flexDirection='row' alignItems='center'>
<InfoPanelField>
<InfoPanelLabel>{t('Phone')}</InfoPanelLabel>
<InfoPanelText display='flex' flexDirection='row' alignItems='center'>
<Box is='a' withTruncatedText href={`tel:${phone}`}>
{phone}
</Box>
</InfoPanel.Text>
</InfoPanel.Field>
</InfoPanelText>
</InfoPanelField>
)}

{email && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Email')}</InfoPanel.Label>
<InfoPanel.Text display='flex' flexDirection='row' alignItems='center'>
<InfoPanelField>
<InfoPanelLabel>{t('Email')}</InfoPanelLabel>
<InfoPanelText display='flex' flexDirection='row' alignItems='center'>
<Box is='a' withTruncatedText href={`mailto:${email}`}>
{email}
</Box>
<Margins inline={4}>
<Tag>{verified ? t('Verified') : t('Not_verified')}</Tag>
</Margins>
</InfoPanel.Text>
</InfoPanel.Field>
</InfoPanelText>
</InfoPanelField>
)}

{userCustomFields?.map(
(customField) =>
customField?.value && (
<InfoPanel.Field key={customField.value}>
<InfoPanel.Label>{t(customField.label as TranslationKey)}</InfoPanel.Label>
<InfoPanel.Text>
<InfoPanelField key={customField.value}>
<InfoPanelLabel>{t(customField.label as TranslationKey)}</InfoPanelLabel>
<InfoPanelText>
<MarkdownText content={customField.value} variant='inline' />
</InfoPanel.Text>
</InfoPanel.Field>
</InfoPanelText>
</InfoPanelField>
),
)}

{createdAt && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Created_at')}</InfoPanel.Label>
<InfoPanel.Text>{timeAgo(createdAt)}</InfoPanel.Text>
</InfoPanel.Field>
<InfoPanelField>
<InfoPanelLabel>{t('Created_at')}</InfoPanelLabel>
<InfoPanelText>{timeAgo(createdAt)}</InfoPanelText>
</InfoPanelField>
)}
</InfoPanel.Section>
</InfoPanelSection>
</InfoPanel>
</ContextualbarScrollableContent>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/client/views/admin/users/AdminUserForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ const AdminUserForm = ({ userData, onReload, setCreatedUsersCount, context, refe
</ContextualbarScrollableContent>
<ContextualbarFooter>
<Button primary disabled={!isDirty} onClick={handleSubmit(handleSaveUser)} w='100%'>
{t('Add_user')}
{isNewUserPage ? t('Add_user') : t('Save_user')}
</Button>
</ContextualbarFooter>
</>
Expand Down
17 changes: 10 additions & 7 deletions apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type AdminUserInfoActionsProps = {
isAdmin: boolean;
onChange: () => void;
onReload: () => void;
tab: 'all' | 'invited' | 'active' | 'deactivated' | 'pending';
};

const AdminUserInfoActions = ({
Expand All @@ -30,6 +31,7 @@ const AdminUserInfoActions = ({
isAdmin,
onChange,
onReload,
tab,
}: AdminUserInfoActionsProps): ReactElement => {
const t = useTranslation();
const directRoute = useRoute('direct');
Expand Down Expand Up @@ -80,24 +82,25 @@ const AdminUserInfoActions = ({
disabled: isFederatedUser,
},
}),
...(changeAdminStatusAction && !isFederatedUser && { makeAdmin: changeAdminStatusAction }),
...(resetE2EKeyAction && !isFederatedUser && { resetE2EKey: resetE2EKeyAction }),
...(resetTOTPAction && !isFederatedUser && { resetTOTP: resetTOTPAction }),
...(changeAdminStatusAction && !isFederatedUser && tab !== 'deactivated' && { makeAdmin: changeAdminStatusAction }),
...(resetE2EKeyAction && !isFederatedUser && tab !== 'deactivated' && { resetE2EKey: resetE2EKeyAction }),
...(resetTOTPAction && !isFederatedUser && tab !== 'deactivated' && { resetTOTP: resetTOTPAction }),
...(changeUserStatusAction && !isFederatedUser && { changeActiveStatus: changeUserStatusAction }),
...(deleteUserAction && { delete: deleteUserAction }),
}),
[
t,
canDirectMessage,
t,
directMessageClick,
canEditOtherUserInfo,
isFederatedUser,
editUserClick,
changeAdminStatusAction,
changeUserStatusAction,
deleteUserAction,
tab,
resetE2EKeyAction,
resetTOTPAction,
isFederatedUser,
changeUserStatusAction,
deleteUserAction,
],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import AdminUserInfoActions from './AdminUserInfoActions';
type AdminUserInfoWithDataProps = {
uid: IUser['_id'];
onReload: () => void;
tab: 'all' | 'invited' | 'active' | 'deactivated' | 'pending';
};

const AdminUserInfoWithData = ({ uid, onReload }: AdminUserInfoWithDataProps): ReactElement => {
const AdminUserInfoWithData = ({ uid, onReload, tab }: AdminUserInfoWithDataProps): ReactElement => {
const t = useTranslation();
const getRoles = useRolesDescription();
const approveManuallyUsers = useSetting('Accounts_ManuallyApproveNewUsers');
Expand Down Expand Up @@ -124,6 +125,7 @@ const AdminUserInfoWithData = ({ uid, onReload }: AdminUserInfoWithDataProps): R
isFederatedUser={isUserFederated(data?.user as unknown as IUser)}
onChange={onChange}
onReload={onReload}
tab={tab}
/>
}
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/client/views/admin/users/AdminUsersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const UsersPage = (): ReactElement => {
</ContextualbarTitle>
<ContextualbarClose onClick={() => router.navigate('/admin/users')} />
</ContextualbarHeader>
{context === 'info' && id && <AdminUserInfoWithData uid={id} onReload={handleReload} />}
{context === 'info' && id && <AdminUserInfoWithData uid={id} onReload={handleReload} tab={tab} />}
{context === 'edit' && id && <AdminUserFormWithData uid={id} onReload={handleReload} context={context} />}
{context === 'new' && <AdminUserForm onReload={handleReload} setCreatedUsersCount={setCreatedUsersCount} context={context} />}
{context === 'created' && id && <AdminUserCreated uid={id} createdUsersCount={createdUsersCount} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const UsersTableRow = ({ user, onClick, mediaQuery, refetchUsers, onReload, tab

const menuOptions = {
...(tab !== 'pending' &&
tab !== 'deactivated' &&
changeAdminStatusAction &&
!isFederatedUser && {
makeAdmin: {
Expand All @@ -63,11 +64,13 @@ const UsersTableRow = ({ user, onClick, mediaQuery, refetchUsers, onReload, tab
},
}),
...(tab !== 'pending' &&
tab !== 'deactivated' &&
resetE2EKeyAction &&
!isFederatedUser && {
resetE2EKey: { label: { label: resetE2EKeyAction.label, icon: resetE2EKeyAction.icon }, action: resetE2EKeyAction.action },
}),
...(tab !== 'pending' &&
tab !== 'deactivated' &&
resetTOTPAction &&
!isFederatedUser && {
resetTOTP: { label: { label: resetTOTPAction.label, icon: resetTOTPAction.icon }, action: resetTOTPAction.action },
Expand All @@ -91,7 +94,7 @@ const UsersTableRow = ({ user, onClick, mediaQuery, refetchUsers, onReload, tab
dispatchToastMessage({ type: 'success', message: t('Welcome_email_resent') });
};

const checkPendingButton = (): ReactElement => {
const renderPendingButton = (): ReactElement => {
if (active) {
return (
<Button small secondary mie={8} onClick={handleResendWelcomeEmail}>
Expand Down Expand Up @@ -166,7 +169,7 @@ const UsersTableRow = ({ user, onClick, mediaQuery, refetchUsers, onReload, tab
e.stopPropagation();
}}
>
{tab === 'pending' && checkPendingButton()}
{tab === 'pending' && renderPendingButton()}

<Menu
mi={4}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { IUser } from '@rocket.chat/core-typings';

export const useFilterActiveOrDeactivatedUsers = (
users: Pick<IUser, '_id' | 'username' | 'name' | 'status' | 'roles' | 'emails' | 'active' | 'avatarETag'>[] | undefined,
tab: string,
) => {
if (!users || (tab !== 'active' && tab !== 'deactivated')) return [];

return tab === 'active' ? users.filter((currentUser) => currentUser.active) : users.filter((currentUser) => !currentUser.active);
};
1 change: 1 addition & 0 deletions apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -4564,6 +4564,7 @@
"Save_Mobile_Bandwidth": "Save Mobile Bandwidth",
"Save_to_enable_this_action": "Save to enable this action",
"Save_To_Webdav": "Save to WebDAV",
"Save_user": "Save user",
"Save_your_encryption_password": "Save your encryption password",
"save-all-canned-responses": "Save All Canned Responses",
"save-all-canned-responses_description": "Permission to save all canned responses",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export type OptionProp = TitleOptionProp | CheckboxOptionProp;
@param selectedOptionsTitle dropdown text after clicking one or more options. For example: 'Rooms (3)'
* @param selectedOptions array with clicked options. This is used in the useFilteredTypeRooms hook, to filter the Rooms' table, for example. This array joins all of the individual clicked options from all available MultiSelectCustom components in the page. It helps to create a union filter for all the selections.
* @param setSelectedOptions part of an useState hook to set the previous selectedOptions
* @param customSetSelected part of an useState hook to set the individual selected checkboxes from this instance.
* @param searchBarText optional text prop that creates a search bar inside the dropdown, when added.
* @returns a React Component that should be used with a custom hook for filters, such as useFilteredTypeRooms.tsx.
* Check out the following files, for examples:
Expand Down
Loading