feat: New users page pending tab (#31987)
Co-authored-by: Tasso <tasso.evangelista@rocket.chat>pull/32567/head
parent
363a011487
commit
5f95c4ec6b
@ -0,0 +1,13 @@ |
||||
--- |
||||
"@rocket.chat/meteor": minor |
||||
"@rocket.chat/core-typings": patch |
||||
"@rocket.chat/i18n": patch |
||||
--- |
||||
|
||||
Implemented a new "Pending Users" tab on the users page to list users who have not yet been activated and/or have not logged in for the first time. |
||||
Additionally, added a "Pending Action" column to aid administrators in identifying necessary actions for each user. Incorporated a "Reason for Joining" field |
||||
into the user info contextual bar, along with a callout for exceeding the seats cap in the users page header. Finally, introduced a new logic to disable user creation buttons upon surpassing the seats cap. |
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,63 @@ |
||||
import type { LicenseBehavior, LicenseLimitKind } from '@rocket.chat/core-typings'; |
||||
import { validateWarnLimit } from '@rocket.chat/license/src/validation/validateLimit'; |
||||
|
||||
import { useLicense } from './useLicense'; |
||||
|
||||
type LicenseLimitsByBehavior = Record<LicenseBehavior, LicenseLimitKind[]>; |
||||
|
||||
export const useLicenseLimitsByBehavior = () => { |
||||
const result = useLicense({ loadValues: true }); |
||||
|
||||
if (result.isLoading || result.isError) { |
||||
return null; |
||||
} |
||||
|
||||
const { license, limits } = result.data; |
||||
|
||||
if (!license || !limits) { |
||||
return null; |
||||
} |
||||
|
||||
const keyLimits = Object.keys(limits) as Array<keyof typeof limits>; |
||||
|
||||
// Get the rule with the highest limit that applies to this key
|
||||
const rules = keyLimits |
||||
.map((key) => { |
||||
const rule = license.limits[key] |
||||
?.filter((limit) => validateWarnLimit(limit.max, limits[key].value ?? 0, limit.behavior)) |
||||
.reduce<{ max: number; behavior: LicenseBehavior } | null>( |
||||
(maxLimit, currentLimit) => (!maxLimit || currentLimit.max > maxLimit.max ? currentLimit : maxLimit), |
||||
null, |
||||
); |
||||
|
||||
if (!rule) { |
||||
return undefined; |
||||
} |
||||
|
||||
if (rule.max === 0) { |
||||
return undefined; |
||||
} |
||||
|
||||
if (rule.max === -1) { |
||||
return undefined; |
||||
} |
||||
|
||||
return [key, rule.behavior]; |
||||
}) |
||||
.filter(Boolean) as Array<[keyof typeof limits, LicenseBehavior]>; |
||||
|
||||
if (!rules.length) { |
||||
return null; |
||||
} |
||||
|
||||
// Group by behavior
|
||||
return rules.reduce((acc, [key, behavior]) => { |
||||
if (!acc[behavior]) { |
||||
acc[behavior] = []; |
||||
} |
||||
|
||||
acc[behavior].push(key); |
||||
|
||||
return acc; |
||||
}, {} as LicenseLimitsByBehavior); |
||||
}; |
||||
@ -0,0 +1,25 @@ |
||||
import type { Serialized } from '@rocket.chat/core-typings'; |
||||
import type { DefaultUserInfo, UsersListStatusParamsGET } from '@rocket.chat/rest-typings'; |
||||
import { useEndpoint } from '@rocket.chat/ui-contexts'; |
||||
import { useQuery } from '@tanstack/react-query'; |
||||
|
||||
const usePendingUsersCount = (users: Serialized<DefaultUserInfo[]> | undefined) => { |
||||
const getUsers = useEndpoint('GET', '/v1/users.listByStatus'); |
||||
|
||||
return useQuery( |
||||
['pendingUsersCount', users], |
||||
async () => { |
||||
const payload: UsersListStatusParamsGET = { |
||||
hasLoggedIn: false, |
||||
status: 'deactivated', |
||||
type: 'user', |
||||
count: 1, |
||||
}; |
||||
|
||||
return getUsers(payload); |
||||
}, |
||||
{ enabled: !!users, select: (data) => data?.total }, |
||||
); |
||||
}; |
||||
|
||||
export default usePendingUsersCount; |
||||
@ -0,0 +1,29 @@ |
||||
import { useEndpoint, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; |
||||
import type { UseMutationResult } from '@tanstack/react-query'; |
||||
import { useMutation } from '@tanstack/react-query'; |
||||
|
||||
type useSendWelcomeEmailMutationProps = { |
||||
email: string | undefined; |
||||
}; |
||||
|
||||
export const useSendWelcomeEmailMutation = (): UseMutationResult<null, Error, useSendWelcomeEmailMutationProps> => { |
||||
const t = useTranslation(); |
||||
const dispatchToastMessage = useToastMessageDispatch(); |
||||
|
||||
const sendWelcomeEmail = useEndpoint('POST', '/v1/users.sendWelcomeEmail'); |
||||
|
||||
return useMutation( |
||||
async ({ email }) => { |
||||
if (!email) { |
||||
dispatchToastMessage({ type: 'error', message: t('Welcome_email_failed') }); |
||||
return null; |
||||
} |
||||
|
||||
return sendWelcomeEmail({ email }); |
||||
}, |
||||
{ |
||||
onSuccess: () => dispatchToastMessage({ type: 'success', message: t('Welcome_email_resent') }), |
||||
onError: (error) => dispatchToastMessage({ type: 'error', message: error }), |
||||
}, |
||||
); |
||||
}; |
||||
Loading…
Reference in new issue