apps/meteor/client/views/omnichannel/customFields/EditCustomFields.tsx
import type { ILivechatCustomField, Serialized } from '@rocket.chat/core-typings';
import type { SelectOption } from '@rocket.chat/fuselage';
import {
FieldError,
Button,
ButtonGroup,
Field,
FieldGroup,
FieldLabel,
FieldRow,
Select,
TextInput,
ToggleSwitch,
Box,
} from '@rocket.chat/fuselage';
import { useMutableCallback, useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useMethod, useTranslation, useRouter } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
import React, { useMemo } from 'react';
import { FormProvider, useForm, Controller } from 'react-hook-form';
import {
Contextualbar,
ContextualbarTitle,
ContextualbarHeader,
ContextualbarClose,
ContextualbarFooter,
ContextualbarScrollableContent,
} from '../../../components/Contextualbar';
import { CustomFieldsAdditionalForm } from '../additionalForms';
import { useRemoveCustomField } from './useRemoveCustomField';
const getInitialValues = (customFieldData: Serialized<ILivechatCustomField> | undefined) => ({
field: customFieldData?._id || '',
label: customFieldData?.label || '',
scope: customFieldData?.scope || 'visitor',
visibility: customFieldData?.visibility === 'visible',
searchable: !!customFieldData?.searchable,
regexp: customFieldData?.regexp || '',
// additional props
type: customFieldData?.type || 'input',
required: !!customFieldData?.required,
defaultValue: customFieldData?.defaultValue || '',
options: customFieldData?.options || '',
public: !!customFieldData?.public,
});
const EditCustomFields = ({ customFieldData }: { customFieldData?: Serialized<ILivechatCustomField> }) => {
const t = useTranslation();
const router = useRouter();
const queryClient = useQueryClient();
const dispatchToastMessage = useToastMessageDispatch();
const handleDelete = useRemoveCustomField();
const methods = useForm({ mode: 'onBlur', values: getInitialValues(customFieldData) });
const {
control,
handleSubmit,
formState: { isDirty, errors },
} = methods;
const saveCustomField = useMethod('livechat:saveCustomField');
const handleSave = useMutableCallback(async ({ visibility, ...data }) => {
try {
await saveCustomField(customFieldData?._id as unknown as string, {
visibility: visibility ? 'visible' : 'hidden',
...data,
});
dispatchToastMessage({ type: 'success', message: t('Saved') });
queryClient.invalidateQueries(['livechat-customFields']);
router.navigate('/omnichannel/customfields');
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
}
});
const scopeOptions: SelectOption[] = useMemo(
() => [
['visitor', t('Visitor')],
['room', t('Room')],
],
[t],
);
const formId = useUniqueId();
const fieldField = useUniqueId();
const labelField = useUniqueId();
const scopeField = useUniqueId();
const visibilityField = useUniqueId();
const searchableField = useUniqueId();
const regexpField = useUniqueId();
return (
<Contextualbar>
<ContextualbarHeader>
<ContextualbarTitle>{customFieldData?._id ? t('Edit_Custom_Field') : t('New_Custom_Field')}</ContextualbarTitle>
<ContextualbarClose onClick={() => router.navigate('/omnichannel/customfields')} />
</ContextualbarHeader>
<ContextualbarScrollableContent>
<FormProvider {...methods}>
<form id={formId} onSubmit={handleSubmit(handleSave)}>
<FieldGroup>
<Field>
<FieldLabel htmlFor={fieldField} required>
{t('Field')}
</FieldLabel>
<FieldRow>
<Controller
name='field'
control={control}
rules={{
required: t('The_field_is_required', t('Field')),
validate: (value) => (!/^[0-9a-zA-Z-_]+$/.test(value) ? t('error-invalid-custom-field-name') : undefined),
}}
render={({ field }) => (
<TextInput
id={fieldField}
{...field}
readOnly={Boolean(customFieldData?._id)}
aria-required={true}
aria-invalid={Boolean(errors.field)}
aria-describedby={`${fieldField}-error`}
/>
)}
/>
</FieldRow>
{errors?.field && (
<FieldError aria-live='assertive' id={`${fieldField}-error`}>
{errors.field.message}
</FieldError>
)}
</Field>
<Field>
<FieldLabel htmlFor={labelField} required>
{t('Label')}
</FieldLabel>
<FieldRow>
<Controller
name='label'
control={control}
rules={{ required: t('The_field_is_required', t('Label')) }}
render={({ field }) => (
<TextInput
id={labelField}
{...field}
aria-required={true}
aria-invalid={Boolean(errors.label)}
aria-describedby={`${labelField}-error`}
/>
)}
/>
</FieldRow>
{errors?.label && (
<FieldError aria-live='assertive' id={`${labelField}-error`}>
{errors.label.message}
</FieldError>
)}
</Field>
<Field>
<FieldLabel htmlFor={scopeField}>{t('Scope')}</FieldLabel>
<FieldRow>
<Controller
name='scope'
control={control}
render={({ field }) => <Select id={scopeField} {...field} options={scopeOptions} />}
/>
</FieldRow>
</Field>
<Field>
<FieldRow>
<FieldLabel htmlFor={visibilityField}>{t('Visible')}</FieldLabel>
<Controller
name='visibility'
control={control}
render={({ field: { value, ...field } }) => <ToggleSwitch id={visibilityField} {...field} checked={value} />}
/>
</FieldRow>
</Field>
<Field>
<FieldRow>
<FieldLabel htmlFor={searchableField}>{t('Searchable')}</FieldLabel>
<Controller
name='searchable'
control={control}
render={({ field: { value, ...field } }) => <ToggleSwitch id={searchableField} {...field} checked={value} />}
/>
</FieldRow>
</Field>
<Field>
<FieldLabel htmlFor={regexpField}>{t('Validation')}</FieldLabel>
<FieldRow>
<Controller name='regexp' control={control} render={({ field }) => <TextInput id={regexpField} {...field} />} />
</FieldRow>
</Field>
{CustomFieldsAdditionalForm && <CustomFieldsAdditionalForm />}
</FieldGroup>
</form>
</FormProvider>
</ContextualbarScrollableContent>
<ContextualbarFooter>
<ButtonGroup stretch>
<Button onClick={() => router.navigate('/omnichannel/customfields')}>{t('Cancel')}</Button>
<Button form={formId} data-qa-id='BtnSaveEditCustomFieldsPage' primary type='submit' disabled={!isDirty}>
{t('Save')}
</Button>
</ButtonGroup>
{customFieldData?._id && (
<Box mbs={8}>
<ButtonGroup stretch>
<Button icon='trash' danger onClick={() => handleDelete(customFieldData._id)}>
{t('Delete')}
</Button>
</ButtonGroup>
</Box>
)}
</ContextualbarFooter>
</Contextualbar>
);
};
export default EditCustomFields;