import type { AvatarObject, IRole, IUser, Serialized } from '@rocket.chat/core-typings'; import { Field, FieldLabel, FieldRow, FieldError, FieldHint, TextInput, TextAreaInput, MultiSelectFiltered, Box, ToggleSwitch, Icon, FieldGroup, Button, Callout, Skeleton, } from '@rocket.chat/fuselage'; import type { SelectOption } from '@rocket.chat/fuselage'; import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; import type { UserCreateParamsPOST } from '@rocket.chat/rest-typings'; import { validateEmail } from '@rocket.chat/tools'; import { CustomFieldsForm, ContextualbarScrollableContent, ContextualbarFooter } from '@rocket.chat/ui-client'; import { useAccountsCustomFields, useSetting, useEndpoint, useRouter, useToastMessageDispatch, useTranslation, } from '@rocket.chat/ui-contexts'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import DOMPurify from 'dompurify'; import { useId, useMemo, useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; import AdminUserSetRandomPasswordContent from './AdminUserSetRandomPasswordContent'; import AdminUserSetRandomPasswordRadios from './AdminUserSetRandomPasswordRadios'; import PasswordFieldSkeleton from './PasswordFieldSkeleton'; import { useSmtpQuery } from './hooks/useSmtpQuery'; import { useShowVoipExtension } from './useShowVoipExtension'; import { parseCSV } from '../../../../lib/utils/parseCSV'; import UserAvatarEditor from '../../../components/avatar/UserAvatarEditor'; import { useEndpointMutation } from '../../../hooks/useEndpointMutation'; import { useUpdateAvatar } from '../../../hooks/useUpdateAvatar'; import { USER_STATUS_TEXT_MAX_LENGTH, BIO_TEXT_MAX_LENGTH } from '../../../lib/constants'; type AdminUserFormProps = { userData?: Serialized; onReload: () => void; context: string; refetchUserFormData?: () => void; roleData: { roles: IRole[] } | undefined; roleError: Error | null; }; export type UserFormProps = Omit< UserCreateParamsPOST & { avatar: AvatarObject; passwordConfirmation: string; freeSwitchExtension?: string }, 'fields' >; const getInitialValue = ({ data, defaultUserRoles, isSmtpEnabled, isVerificationNeeded, isNewUserPage, }: { data?: Serialized; defaultUserRoles?: IUser['roles']; isSmtpEnabled?: boolean; isVerificationNeeded?: boolean; isNewUserPage?: boolean; }): UserFormProps => ({ roles: data?.roles ?? defaultUserRoles, name: data?.name ?? '', password: '', username: data?.username ?? '', bio: data?.bio ?? '', nickname: data?.nickname ?? '', email: (data?.emails?.length && data.emails[0].address) || '', verified: isSmtpEnabled && isVerificationNeeded && ((data?.emails?.length && data.emails[0].verified) || false), setRandomPassword: isNewUserPage && isSmtpEnabled, requirePasswordChange: isNewUserPage && isSmtpEnabled && (data?.requirePasswordChange ?? true), customFields: data?.customFields ?? {}, statusText: data?.statusText ?? '', freeSwitchExtension: data?.freeSwitchExtension ?? '', ...(isNewUserPage && { joinDefaultChannels: true }), sendWelcomeEmail: isSmtpEnabled, avatar: '' as AvatarObject, passwordConfirmation: '', }); const AdminUserForm = ({ userData, onReload, context, refetchUserFormData, roleData, roleError, ...props }: AdminUserFormProps) => { const t = useTranslation(); const router = useRouter(); const dispatchToastMessage = useToastMessageDispatch(); const queryClient = useQueryClient(); const customFieldsMetadata = useAccountsCustomFields(); const defaultRoles = useSetting('Accounts_Registration_Users_Default_Roles', ''); const isVerificationNeeded = useSetting('Accounts_EmailVerification'); const defaultUserRoles = parseCSV(defaultRoles); const { data, isLoading: isLoadingSmtpStatus } = useSmtpQuery(); const isSmtpEnabled = !!data?.isSMTPConfigured; const isNewUserPage = context === 'new'; const { control, watch, handleSubmit, formState: { errors, isDirty }, setValue, } = useForm({ values: getInitialValue({ data: userData, defaultUserRoles, isSmtpEnabled, isNewUserPage, isVerificationNeeded: !!isVerificationNeeded, }), mode: 'onBlur', }); const showVoipExtension = useShowVoipExtension(); const { avatar, username, setRandomPassword, password, name: userFullName } = watch(); const { mutateAsync: eventStats } = useEndpointMutation('POST', '/v1/statistics.telemetry'); const updateUserAction = useEndpoint('POST', '/v1/users.update'); const createUserAction = useEndpoint('POST', '/v1/users.create'); const availableRoles: SelectOption[] = useMemo( () => roleData?.roles.map(({ _id, name, description }) => [_id, description || name]) || [], [roleData], ); const updateAvatar = useUpdateAvatar(avatar, userData?._id || ''); const handleUpdateUser = useMutation({ mutationFn: updateUserAction, onSuccess: async ({ user: { _id } }) => { dispatchToastMessage({ type: 'success', message: t('User_updated_successfully') }); await updateAvatar(); router.navigate(`/admin/users/info/${_id}`); onReload(); refetchUserFormData?.(); }, onError: (error) => { dispatchToastMessage({ type: 'error', message: error }); }, }); const handleCreateUser = useMutation({ mutationFn: createUserAction, onSuccess: async ({ user: { _id } }) => { dispatchToastMessage({ type: 'success', message: t('New_user_manually_created') }); await eventStats({ params: [{ eventName: 'updateCounter', settingsId: 'Manual_Entry_User_Count' }], }); queryClient.invalidateQueries({ queryKey: ['pendingUsersCount'], refetchType: 'all', }); router.navigate(`/admin/users/created/${_id}`); onReload(); }, onError: (error) => { dispatchToastMessage({ type: 'error', message: error }); }, }); const handleSaveUser = useEffectEvent(async (userFormPayload: UserFormProps) => { const { avatar, passwordConfirmation, ...userFormData } = userFormPayload; if (!isNewUserPage && userData?._id) { return handleUpdateUser.mutateAsync({ userId: userData?._id, data: userFormData }); } return handleCreateUser.mutateAsync({ ...userFormData, fields: '' }); }); const nameId = useId(); const usernameId = useId(); const voiceExtensionId = useId(); const emailId = useId(); const verifiedId = useId(); const statusTextId = useId(); const bioId = useId(); const nicknameId = useId(); const passwordId = useId(); const rolesId = useId(); const joinDefaultChannelsId = useId(); const sendWelcomeEmailId = useId(); const setRandomPasswordId = useId(); const [showCustomFields, setShowCustomFields] = useState(true); if (!context) { return null; } return ( <> {!isNewUserPage && ( ( )} /> )} {isNewUserPage && {t('Manually_created_users_briefing')}} {t('Email')} (validateEmail(email) ? undefined : t('error-invalid-email-address')), }} render={({ field }) => ( )} /> {errors?.email && ( {errors.email.message} )} {isLoadingSmtpStatus ? ( ) : ( <> {t('Mark_email_as_verified')} ( )} /> {isVerificationNeeded && !isSmtpEnabled && ( )} {!isVerificationNeeded && ( )} )} {t('Name')} ( )} /> {errors?.name && ( {errors.name.message} )} {t('Username')} ( )} /> {errors?.username && ( {errors.username.message} )} {showVoipExtension && ( {t('Voice_call_extension')} } /> )} {isLoadingSmtpStatus ? ( ) : ( <> {t('Password')} {!setRandomPassword && ( )} )} {t('Roles')} {roleError && {roleError.message}} {!roleError && ( ( )} /> )} {errors?.roles && {errors.roles.message}} {isNewUserPage && ( {t('Join_default_channels')} ( )} /> )} {isLoadingSmtpStatus ? ( ) : ( <> {t('Send_welcome_email')} ( )} /> {!isSmtpEnabled && ( )} )} {t('StatusMessage')} ( )} /> {errors?.statusText && ( {errors.statusText.message} )} {t('Bio')} ( } /> )} /> {errors?.bio && ( {errors.bio.message} )} {t('Nickname')} } /> {!!customFieldsMetadata.length && ( <> {showCustomFields && } )} ); }; export default AdminUserForm;