import { VisuallyHidden } from '@react-aria/visually-hidden'; import { UserStatus } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; import type { SelectOption } from '@rocket.chat/fuselage'; import { Box, Button, Divider, Icon, InputBox, Margins } from '@rocket.chat/fuselage'; import { Field, FieldGroup, FieldLabel, FieldRow, FieldError, FieldHint, TextInput, TextAreaInput, Select, } from '@rocket.chat/fuselage-forms'; import { validateEmail } from '@rocket.chat/tools'; import { CustomFieldsForm } from '@rocket.chat/ui-client'; import { useAccountsCustomFields, useToastMessageDispatch, useTranslation, useEndpoint, useUser, useLayout, useSetting, } from '@rocket.chat/ui-contexts'; import { useMutation } from '@tanstack/react-query'; import type { AllHTMLAttributes, ChangeEvent } from 'react'; import { useCallback, useEffect, useMemo } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import type { AccountProfileFormValues } from './getProfileInitialValues'; import { useAccountProfileSettings } from './useAccountProfileSettings'; import { getUserEmailAddress } from '../../../../lib/getUserEmailAddress'; import UserAutoCompleteMultiple from '../../../components/UserAutoCompleteMultiple'; import UserStatusMenu from '../../../components/UserStatusMenu'; import UserAvatarEditor from '../../../components/avatar/UserAvatarEditor'; import { useUpdateAvatar } from '../../../hooks/useUpdateAvatar'; import { USER_STATUS_TEXT_MAX_LENGTH, BIO_TEXT_MAX_LENGTH } from '../../../lib/constants'; import { STATUS_DURATION_OPTIONS, validateStatusExpiration } from '../../../lib/statusDurations'; const AccountProfileForm = (props: AllHTMLAttributes) => { const t = useTranslation(); const user = useUser(); const dispatchToastMessage = useToastMessageDispatch(); const { isMobile } = useLayout(); const setPreferences = useEndpoint('POST', '/v1/users.setPreferences'); const statusVisibilityEnabled = useSetting('Accounts_StatusVisibility_Enabled', false); const checkUsernameAvailability = useEndpoint('GET', '/v1/users.checkUsernameAvailability'); const sendConfirmationEmail = useEndpoint('POST', '/v1/users.sendConfirmationEmail'); const customFieldsMetadata = useAccountsCustomFields(); const { allowRealNameChange, allowUserStatusMessageChange, allowEmailChange, allowUserAvatarChange, canChangeUsername, requireName, namesRegex, } = useAccountProfileSettings(); const { control, watch, handleSubmit, reset, setValue, formState: { errors, dirtyFields }, } = useFormContext(); const { email, avatar, username, name: userFullName, statusDuration, statusType, statusText } = watch(); const isExpirationDisabled = statusType === UserStatus.ONLINE && !statusText?.trim(); useEffect(() => { if (isExpirationDisabled) { setValue('statusDuration', '', { shouldValidate: true }); } }, [isExpirationDisabled, setValue]); const statusDurationOptions: SelectOption[] = useMemo( () => STATUS_DURATION_OPTIONS.map(({ value, labelKey }) => [value, t(labelKey)]), [t], ); const previousEmail = user ? getUserEmailAddress(user) : ''; const previousUsername = user?.username || ''; const isUserVerified = user?.emails?.[0]?.verified ?? false; const mutateConfirmationEmail = useMutation({ mutationFn: sendConfirmationEmail, onSuccess: () => dispatchToastMessage({ type: 'success', message: t('Verification_email_sent') }), onError: (error) => dispatchToastMessage({ type: 'error', message: error }), }); const handleSendConfirmationEmail = useCallback(async () => { if (email !== previousEmail) { return; } mutateConfirmationEmail.mutateAsync({ email }); }, [email, previousEmail, mutateConfirmationEmail]); const validateUsername = async (username: string): Promise => { if (!username) { return; } if (username === previousUsername) { return; } if (!namesRegex.test(username)) { return t('error-invalid-username'); } const { result: isAvailable } = await checkUsernameAvailability({ username }); if (!isAvailable) { return t('Username_already_exist'); } }; const updateOwnBasicInfo = useEndpoint('POST', '/v1/users.updateOwnBasicInfo'); const setUserStatus = useEndpoint('POST', '/v1/users.setStatus'); const updateAvatar = useUpdateAvatar(avatar, user?._id || ''); const handleSave = async (values: AccountProfileFormValues) => { const { email, name, username, statusType, statusText, statusDuration, statusCustomDate, statusCustomTime, nickname, bio, customFields, statusVisibilityDenied, } = values; const expiresAt = STATUS_DURATION_OPTIONS.find((o) => o.value === statusDuration)?.getExpiresAt?.({ now: new Date(), customDate: statusCustomDate, customTime: statusCustomTime, }); const statusDirty = dirtyFields.statusText || dirtyFields.statusType || dirtyFields.statusDuration || dirtyFields.statusCustomDate || dirtyFields.statusCustomTime; try { await updateOwnBasicInfo({ data: { name, ...(user ? getUserEmailAddress(user) !== email && { email } : {}), username, nickname, bio, }, customFields, }); if (dirtyFields.statusVisibilityDenied) { await setPreferences({ data: { statusVisibilityDenied } }); } if (statusDirty) { await setUserStatus({ status: statusType, ...(allowUserStatusMessageChange && { message: statusText }), ...(allowUserStatusMessageChange && expiresAt && { expiresAt: expiresAt.toISOString() }), }); } await updateAvatar(); dispatchToastMessage({ type: 'success', message: t('Profile_saved_successfully') }); reset(values); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } }; return ( {t('Profile_details')} ( )} /> {t('Name')} ( )} /> {errors.name && {errors.name.message}} {!allowRealNameChange && {t('RealName_Change_Disabled')}} {t('Username')} ( } error={errors.username?.message} /> )} /> {errors.username && {errors.username.message}} {!canChangeUsername && {t('Username_Change_Disabled')}} {t('Status')} ( } /> } /> )} /> {errors.statusText && {errors.statusText.message}} {!allowUserStatusMessageChange && {t('StatusMessage_Change_Disabled')}} {allowUserStatusMessageChange && {t('Status_you_can_use_emoji')}} {t('Status_clear_after')} validateStatusExpiration(value, { statusCustomDate, statusCustomTime }, t), }} render={({ field: { value, onChange } }) => (