import type { ILivechatContact, Serialized } from '@rocket.chat/core-typings'; import { ButtonGroup, Button, IconButton, Divider } from '@rocket.chat/fuselage'; import { Field, FieldLabel, FieldRow, FieldError, TextInput } from '@rocket.chat/fuselage-forms'; import { validateEmail } from '@rocket.chat/tools'; import { CustomFieldsForm, ContextualbarScrollableContent, ContextualbarFooter, ContextualbarHeader, ContextualbarIcon, ContextualbarTitle, ContextualbarClose, ContextualbarDialog, ContextualbarSkeleton, } from '@rocket.chat/ui-client'; import { useEndpoint, useSetModal } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import { Fragment, useId } from 'react'; import { Controller, useFieldArray, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import AdvancedContactModal from './AdvancedContactModal'; import { useCreateContact } from './hooks/useCreateContact'; import { useEditContact } from './hooks/useEditContact'; import { useFormSubmitWithDirtyCheck } from '../../../hooks/useFormSubmitWithDirtyCheck'; import { useHasLicenseModule } from '../../../hooks/useHasLicenseModule'; import { hasAtLeastOnePermission } from '../../../lib/authorization'; import { omnichannelQueryKeys } from '../../../lib/queryKeys'; import { ContactManagerInput } from '../additionalForms'; import { useCustomFieldsMetadata } from '../directory/hooks/useCustomFieldsMetadata'; type ContactNewEditProps = { contactData?: Serialized | null; onClose: () => void; onCancel: () => void; }; type ContactFormData = { name: string; emails: { address: string }[]; phones: { phoneNumber: string }[]; customFields: Record; contactManager: string; }; const DEFAULT_VALUES = { name: '', emails: [], phones: [], contactManager: '', customFields: {}, }; const getInitialValues = (data: ContactNewEditProps['contactData']): ContactFormData => { if (!data) { return DEFAULT_VALUES; } const { name, phones, emails, customFields, contactManager } = data ?? {}; return { name: name ?? '', emails: emails ?? [], phones: phones ?? [], customFields: customFields ?? {}, contactManager: contactManager ?? '', }; }; const validateMultipleFields = (fieldsLength: number, hasLicense: boolean) => fieldsLength >= 1 && !hasLicense; const EditContactInfo = ({ contactData, onClose, onCancel }: ContactNewEditProps) => { const { t } = useTranslation(); const setModal = useSetModal(); const { data: hasLicense = false } = useHasLicenseModule('contact-id-verification'); const canViewCustomFields = hasAtLeastOnePermission(['view-livechat-room-customfields', 'edit-livechat-room-customfields']); const editContact = useEditContact(['current-contacts']); const createContact = useCreateContact(['current-contacts']); const checkExistenceEndpoint = useEndpoint('GET', '/v1/omnichannel/contacts.checkExistence'); const queryClient = useQueryClient(); const handleOpenUpSellModal = () => setModal( setModal(null)} />); const { data: customFieldsMetadata = [], isLoading: isLoadingCustomFields } = useCustomFieldsMetadata({ scope: 'visitor', enabled: canViewCustomFields, }); const initialValue = getInitialValues(contactData); const { formState: { errors, isSubmitting, isDirty }, control, watch, handleSubmit, } = useForm({ reValidateMode: 'onBlur', defaultValues: initialValue, }); const { fields: emailFields, append: appendEmail, remove: removeEmail, } = useFieldArray({ control, name: 'emails', }); const { fields: phoneFields, append: appendPhone, remove: removePhone, } = useFieldArray({ control, name: 'phones', }); const { emails, phones } = watch(); const validateEmailFormat = async (emailValue: string) => { if (!emails) { return true; } const currentEmails = emails.map(({ address }) => address); if (!validateEmail(emailValue)) { return t('error-invalid-email-address'); } if (currentEmails.filter((email) => email === emailValue).length > 1) { return t('Email_already_exists'); } const initialEmails = initialValue.emails.map(({ address }) => address); if (!initialEmails.includes(emailValue) && (await checkExistenceEndpoint({ email: emailValue })).exists) { return t('Email_already_exists'); } return true; }; const validatePhone = async (phoneValue: string) => { if (!phones) { return true; } const currentPhones = phones.map(({ phoneNumber }) => phoneNumber); if (currentPhones.filter((phone) => phone === phoneValue).length > 1) { return t('Phone_already_exists'); } const initialPhones = initialValue.phones.map(({ phoneNumber }) => phoneNumber); if (!initialPhones.includes(phoneValue) && (await checkExistenceEndpoint({ phone: phoneValue })).exists) { return t('Phone_already_exists'); } return true; }; const validateName = (v: string): string | boolean => (!v.trim() ? t('Required_field', { field: t('Name') }) : true); const handleSave = useFormSubmitWithDirtyCheck( async (data: ContactFormData): Promise => { const { name, phones, emails, customFields, contactManager } = data; const payload = { name, phones: phones.map(({ phoneNumber }) => phoneNumber), emails: emails.map(({ address }) => address), customFields, contactManager, }; if (contactData) { await editContact.mutateAsync({ contactId: contactData?._id, ...payload }); await queryClient.invalidateQueries({ queryKey: omnichannelQueryKeys.contacts() }); return; } await createContact.mutateAsync(payload); await queryClient.invalidateQueries({ queryKey: omnichannelQueryKeys.contacts() }); }, { isDirty, }, ); const formId = useId(); if (isLoadingCustomFields) { return ; } return ( {contactData ? t('Edit_Contact_Profile') : t('New_contact')} {t('Name')} } /> {errors.name && {errors.name.message}} {t('Email')} {emailFields.map((field, index) => ( } /> removeEmail(index)} marginInlineStart={8} icon='trash' /> {errors.emails?.[index]?.address && {errors.emails?.[index]?.address?.message}} ))} {t('Phone')} {phoneFields.map((field, index) => ( } /> removePhone(index)} marginInlineStart={8} icon='trash' /> {errors.phones?.[index]?.phoneNumber && {errors.phones?.[index]?.phoneNumber?.message}} ))} {t('Contact_Manager')} } /> {canViewCustomFields && } ); }; export default EditContactInfo;