chore: Replace `useForm` in favor of RHF on Omnichannel Triggers (#30776)
parent
5d55a9394e
commit
e24af45c8c
@ -0,0 +1,31 @@ |
||||
import { Box, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage'; |
||||
import type { Keys as IconName } from '@rocket.chat/icons'; |
||||
import { useTranslation } from '@rocket.chat/ui-contexts'; |
||||
import React from 'react'; |
||||
|
||||
type GenericErrorProps = { |
||||
icon?: IconName; |
||||
title?: string; |
||||
buttonTitle?: string; |
||||
buttonAction?: () => void; |
||||
}; |
||||
|
||||
const GenericError = ({ icon = 'magnifier', title, buttonTitle, buttonAction }: GenericErrorProps) => { |
||||
const t = useTranslation(); |
||||
|
||||
return ( |
||||
<Box display='flex' height='100%' flexDirection='column' justifyContent='center'> |
||||
<States> |
||||
<StatesIcon name={icon} variation='danger' /> |
||||
<StatesTitle>{title || t('Something_went_wrong')}</StatesTitle> |
||||
{buttonAction && ( |
||||
<StatesActions> |
||||
<StatesAction onClick={buttonAction}>{buttonTitle || t('Reload_page')}</StatesAction> |
||||
</StatesActions> |
||||
)} |
||||
</States> |
||||
</Box> |
||||
); |
||||
}; |
||||
|
||||
export default GenericError; |
||||
@ -0,0 +1 @@ |
||||
export { default } from './GenericError'; |
||||
@ -0,0 +1,306 @@ |
||||
import type { ILivechatTrigger, ILivechatTriggerCondition, Serialized } from '@rocket.chat/core-typings'; |
||||
import type { SelectOption } from '@rocket.chat/fuselage'; |
||||
import { |
||||
FieldGroup, |
||||
Button, |
||||
ButtonGroup, |
||||
Box, |
||||
Field, |
||||
FieldLabel, |
||||
FieldRow, |
||||
FieldError, |
||||
TextInput, |
||||
ToggleSwitch, |
||||
Select, |
||||
TextAreaInput, |
||||
} from '@rocket.chat/fuselage'; |
||||
import { useUniqueId } from '@rocket.chat/fuselage-hooks'; |
||||
import { useToastMessageDispatch, useRouter, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; |
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'; |
||||
import React, { useMemo } from 'react'; |
||||
import { Controller, useFieldArray, useForm } from 'react-hook-form'; |
||||
|
||||
import { |
||||
ContextualbarScrollableContent, |
||||
ContextualbarTitle, |
||||
ContextualbarFooter, |
||||
Contextualbar, |
||||
ContextualbarHeader, |
||||
ContextualbarClose, |
||||
} from '../../../components/Contextualbar'; |
||||
|
||||
const getInitialValues = (triggerData: Serialized<ILivechatTrigger> | undefined) => ({ |
||||
name: triggerData?.name || '', |
||||
description: triggerData?.description || '', |
||||
enabled: triggerData?.enabled || true, |
||||
runOnce: !!triggerData?.runOnce || false, |
||||
conditions: triggerData?.conditions.map(({ name, value }) => ({ name: name || 'page-url', value: value || '' })) || [ |
||||
{ name: 'page-url' as unknown as ILivechatTriggerCondition['name'], value: '' }, |
||||
], |
||||
actions: triggerData?.actions.map(({ name, params }) => ({ |
||||
name: name || '', |
||||
params: { |
||||
sender: params?.sender || 'queue', |
||||
msg: params?.msg || '', |
||||
name: params?.name || '', |
||||
}, |
||||
})) || [ |
||||
{ |
||||
name: 'send-message', |
||||
params: { |
||||
sender: 'queue', |
||||
msg: '', |
||||
name: '', |
||||
}, |
||||
}, |
||||
], |
||||
}); |
||||
|
||||
type TriggersPayload = { |
||||
name: string; |
||||
description: string; |
||||
enabled: boolean; |
||||
runOnce: boolean; |
||||
// In the future, this will be an array
|
||||
conditions: ILivechatTrigger['conditions']; |
||||
// In the future, this will be an array
|
||||
actions: ILivechatTrigger['actions']; |
||||
}; |
||||
|
||||
const EditTrigger = ({ triggerData }: { triggerData?: Serialized<ILivechatTrigger> }) => { |
||||
const t = useTranslation(); |
||||
const router = useRouter(); |
||||
const queryClient = useQueryClient(); |
||||
const dispatchToastMessage = useToastMessageDispatch(); |
||||
|
||||
const saveTrigger = useEndpoint('POST', '/v1/livechat/triggers'); |
||||
|
||||
const { |
||||
control, |
||||
handleSubmit, |
||||
formState: { isDirty, errors }, |
||||
watch, |
||||
} = useForm<TriggersPayload>({ mode: 'onBlur', values: getInitialValues(triggerData) }); |
||||
|
||||
const { fields: conditionsFields } = useFieldArray({ |
||||
control, |
||||
name: 'conditions', |
||||
}); |
||||
|
||||
const { fields: actionsFields } = useFieldArray({ |
||||
control, |
||||
name: 'actions', |
||||
}); |
||||
|
||||
const { description, conditions, actions } = watch(); |
||||
|
||||
const conditionOptions: SelectOption[] = useMemo( |
||||
() => [ |
||||
['page-url', t('Visitor_page_URL')], |
||||
['time-on-site', t('Visitor_time_on_site')], |
||||
['chat-opened-by-visitor', t('Chat_opened_by_visitor')], |
||||
['after-guest-registration', t('After_guest_registration')], |
||||
], |
||||
[t], |
||||
); |
||||
|
||||
const conditionValuePlaceholders: { [conditionName: string]: string } = useMemo( |
||||
() => ({ |
||||
'page-url': t('Enter_a_regex'), |
||||
'time-on-site': t('Time_in_seconds'), |
||||
}), |
||||
[t], |
||||
); |
||||
|
||||
const senderOptions: SelectOption[] = useMemo( |
||||
() => [ |
||||
['queue', t('Impersonate_next_agent_from_queue')], |
||||
['custom', t('Custom_agent')], |
||||
], |
||||
[t], |
||||
); |
||||
|
||||
const saveTriggerMutation = useMutation({ |
||||
mutationFn: saveTrigger, |
||||
onSuccess: () => { |
||||
dispatchToastMessage({ type: 'success', message: t('Saved') }); |
||||
queryClient.invalidateQueries(['livechat-triggers']); |
||||
router.navigate('/omnichannel/triggers'); |
||||
}, |
||||
onError: (error) => { |
||||
dispatchToastMessage({ type: 'error', message: error }); |
||||
}, |
||||
}); |
||||
|
||||
const handleSave = async (data: TriggersPayload) => { |
||||
saveTriggerMutation.mutateAsync({ ...data, _id: triggerData?._id }); |
||||
}; |
||||
|
||||
const formId = useUniqueId(); |
||||
const enabledField = useUniqueId(); |
||||
const runOnceField = useUniqueId(); |
||||
const nameField = useUniqueId(); |
||||
const descriptionField = useUniqueId(); |
||||
const conditionField = useUniqueId(); |
||||
const actionField = useUniqueId(); |
||||
const actionMessageField = useUniqueId(); |
||||
|
||||
return ( |
||||
<Contextualbar> |
||||
<ContextualbarHeader> |
||||
<ContextualbarTitle>{triggerData?._id ? t('Edit_Trigger') : t('New_Trigger')}</ContextualbarTitle> |
||||
<ContextualbarClose onClick={() => router.navigate('/omnichannel/triggers')} /> |
||||
</ContextualbarHeader> |
||||
<ContextualbarScrollableContent> |
||||
<form id={formId} onSubmit={handleSubmit(handleSave)}> |
||||
<FieldGroup> |
||||
<Field> |
||||
<Box display='flex' flexDirection='row'> |
||||
<FieldLabel htmlFor={enabledField}>{t('Enabled')}</FieldLabel> |
||||
<FieldRow> |
||||
<Controller |
||||
name='enabled' |
||||
control={control} |
||||
render={({ field: { value, ...field } }) => <ToggleSwitch id={enabledField} {...field} checked={value} />} |
||||
/> |
||||
</FieldRow> |
||||
</Box> |
||||
</Field> |
||||
<Field> |
||||
<Box display='flex' flexDirection='row'> |
||||
<FieldLabel htmlFor={runOnceField}>{t('Run_only_once_for_each_visitor')}</FieldLabel> |
||||
<FieldRow> |
||||
<Controller |
||||
name='runOnce' |
||||
control={control} |
||||
render={({ field: { value, ...field } }) => <ToggleSwitch id={runOnceField} {...field} checked={value} />} |
||||
/> |
||||
</FieldRow> |
||||
</Box> |
||||
</Field> |
||||
<Field> |
||||
<FieldLabel htmlFor={nameField} required> |
||||
{t('Name')} |
||||
</FieldLabel> |
||||
<FieldRow> |
||||
<Controller |
||||
name='name' |
||||
control={control} |
||||
rules={{ required: t('The_field_is_required', t('Name')) }} |
||||
render={({ field }) => ( |
||||
<TextInput |
||||
{...field} |
||||
id={nameField} |
||||
error={errors?.name?.message} |
||||
aria-required={true} |
||||
aria-invalid={Boolean(errors?.name)} |
||||
aria-describedby={`${nameField}-error`} |
||||
/> |
||||
)} |
||||
/> |
||||
</FieldRow> |
||||
{errors?.name && ( |
||||
<FieldError aria-live='assertive' id={`${nameField}-error`}> |
||||
{errors?.name.message} |
||||
</FieldError> |
||||
)} |
||||
</Field> |
||||
<Field> |
||||
<FieldLabel htmlFor={descriptionField}>{t('Description')}</FieldLabel> |
||||
<FieldRow> |
||||
<Controller |
||||
name='description' |
||||
control={control} |
||||
render={({ field }) => <TextInput id={descriptionField} {...field} value={description} />} |
||||
/> |
||||
</FieldRow> |
||||
</Field> |
||||
{conditionsFields.map((_, index) => { |
||||
const conditionValuePlaceholder = conditionValuePlaceholders[conditions[index].name]; |
||||
return ( |
||||
<Field key={index}> |
||||
<FieldLabel htmlFor={conditionField}>{t('Condition')}</FieldLabel> |
||||
<FieldRow> |
||||
<Controller |
||||
name={`conditions.${index}.name`} |
||||
control={control} |
||||
render={({ field }) => <Select id={conditionField} {...field} options={conditionOptions} />} |
||||
/> |
||||
</FieldRow> |
||||
{conditionValuePlaceholder && ( |
||||
<FieldRow> |
||||
<Controller |
||||
name={`conditions.${index}.value`} |
||||
control={control} |
||||
render={({ field }) => <TextInput {...field} placeholder={conditionValuePlaceholder} />} |
||||
/> |
||||
</FieldRow> |
||||
)} |
||||
</Field> |
||||
); |
||||
})} |
||||
{actionsFields.map((_, index) => ( |
||||
<Field key={index}> |
||||
<FieldLabel htmlFor={actionField}>{t('Action')}</FieldLabel> |
||||
<FieldRow> |
||||
<TextInput value={t('Send_a_message')} readOnly /> |
||||
</FieldRow> |
||||
<FieldRow> |
||||
<Controller |
||||
name={`actions.${index}.params.sender`} |
||||
control={control} |
||||
render={({ field }) => ( |
||||
<Select id={actionField} {...field} options={senderOptions} placeholder={t('Select_an_option')} /> |
||||
)} |
||||
/> |
||||
</FieldRow> |
||||
{actions[index].params?.sender === 'custom' && ( |
||||
<FieldRow> |
||||
<Controller |
||||
name={`actions.${index}.params.name`} |
||||
control={control} |
||||
render={({ field }) => <TextInput {...field} placeholder={t('Name_of_agent')} />} |
||||
/> |
||||
</FieldRow> |
||||
)} |
||||
<FieldRow> |
||||
<Controller |
||||
name={`actions.${index}.params.msg`} |
||||
control={control} |
||||
rules={{ required: t('The_field_is_required', t('Message')) }} |
||||
render={({ field }) => ( |
||||
<TextAreaInput |
||||
error={errors.actions?.[index]?.params?.msg?.message} |
||||
aria-invalid={Boolean(errors.actions?.[index]?.params?.msg)} |
||||
aria-describedby={`${actionMessageField}-error`} |
||||
aria-required={true} |
||||
{...field} |
||||
rows={3} |
||||
placeholder={`${t('Message')}*`} |
||||
/> |
||||
)} |
||||
/> |
||||
</FieldRow> |
||||
{errors.actions?.[index]?.params?.msg && ( |
||||
<FieldError aria-live='assertive' id={`${actionMessageField}-error`}> |
||||
{errors.actions?.[index]?.params?.msg?.message} |
||||
</FieldError> |
||||
)} |
||||
</Field> |
||||
))} |
||||
</FieldGroup> |
||||
</form> |
||||
</ContextualbarScrollableContent> |
||||
<ContextualbarFooter> |
||||
<ButtonGroup stretch> |
||||
<Button onClick={() => router.navigate('/omnichannel/triggers')}>{t('Cancel')}</Button> |
||||
<Button form={formId} type='submit' primary disabled={!isDirty}> |
||||
{t('Save')} |
||||
</Button> |
||||
</ButtonGroup> |
||||
</ContextualbarFooter> |
||||
</Contextualbar> |
||||
); |
||||
}; |
||||
|
||||
export default EditTrigger; |
||||
@ -1,104 +0,0 @@ |
||||
import { FieldGroup, Button, ButtonGroup } from '@rocket.chat/fuselage'; |
||||
import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; |
||||
import { useToastMessageDispatch, useRoute, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; |
||||
import React from 'react'; |
||||
|
||||
import { ContextualbarScrollableContent, ContextualbarFooter } from '../../../components/Contextualbar'; |
||||
import { useForm } from '../../../hooks/useForm'; |
||||
import TriggersForm from './TriggersForm'; |
||||
|
||||
const getInitialValues = ({ |
||||
name, |
||||
description, |
||||
enabled, |
||||
runOnce, |
||||
conditions: [{ name: condName, value: condValue }], |
||||
actions: [ |
||||
{ |
||||
action: actName, |
||||
params: { sender: actSender, msg: actMsg, name: actSenderName }, |
||||
}, |
||||
], |
||||
}) => ({ |
||||
name: name ?? '', |
||||
description: description ?? '', |
||||
enabled: !!enabled, |
||||
runOnce: !!runOnce, |
||||
conditions: { |
||||
name: condName ?? 'page-url', |
||||
value: condValue ?? '', |
||||
}, |
||||
actions: { |
||||
name: actName ?? '', |
||||
params: { |
||||
sender: actSender ?? 'queue', |
||||
msg: actMsg ?? '', |
||||
name: actSenderName ?? '', |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
const EditTriggerPage = ({ data, onSave }) => { |
||||
const dispatchToastMessage = useToastMessageDispatch(); |
||||
const t = useTranslation(); |
||||
|
||||
const router = useRoute('omnichannel-triggers'); |
||||
|
||||
const save = useEndpoint('POST', '/v1/livechat/triggers'); |
||||
|
||||
const { values, handlers, hasUnsavedChanges } = useForm(getInitialValues(data)); |
||||
|
||||
const handleSave = useMutableCallback(async () => { |
||||
try { |
||||
const { |
||||
actions: { |
||||
params: { sender, msg, name }, |
||||
}, |
||||
...restValues |
||||
} = values; |
||||
await save({ |
||||
_id: data._id, |
||||
...restValues, |
||||
conditions: [values.conditions], |
||||
actions: [ |
||||
{ |
||||
name: 'send-message', |
||||
params: { |
||||
sender, |
||||
msg, |
||||
...(sender === 'custom' && { name }), |
||||
}, |
||||
}, |
||||
], |
||||
}); |
||||
dispatchToastMessage({ type: 'success', message: t('Saved') }); |
||||
onSave(); |
||||
router.push({}); |
||||
} catch (error) { |
||||
dispatchToastMessage({ type: 'error', message: error }); |
||||
} |
||||
}); |
||||
|
||||
const { name } = values; |
||||
|
||||
const canSave = name && hasUnsavedChanges; |
||||
|
||||
return ( |
||||
<> |
||||
<ContextualbarScrollableContent> |
||||
<FieldGroup> |
||||
<TriggersForm values={values} handlers={handlers} /> |
||||
</FieldGroup> |
||||
</ContextualbarScrollableContent> |
||||
<ContextualbarFooter> |
||||
<ButtonGroup stretch> |
||||
<Button primary onClick={handleSave} disabled={!canSave}> |
||||
{t('Save')} |
||||
</Button> |
||||
</ButtonGroup> |
||||
</ContextualbarFooter> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export default EditTriggerPage; |
||||
@ -1,25 +0,0 @@ |
||||
import { Callout } from '@rocket.chat/fuselage'; |
||||
import { useTranslation } from '@rocket.chat/ui-contexts'; |
||||
import React from 'react'; |
||||
|
||||
import PageSkeleton from '../../../components/PageSkeleton'; |
||||
import { AsyncStatePhase } from '../../../hooks/useAsyncState'; |
||||
import { useEndpointData } from '../../../hooks/useEndpointData'; |
||||
import EditTriggerPage from './EditTriggerPage'; |
||||
|
||||
const EditTriggerPageContainer = ({ id, onSave }) => { |
||||
const t = useTranslation(); |
||||
const { value: data, phase: state } = useEndpointData('/v1/livechat/triggers/:_id', { keys: { _id: id } }); |
||||
|
||||
if (state === AsyncStatePhase.LOADING) { |
||||
return <PageSkeleton />; |
||||
} |
||||
|
||||
if (state === AsyncStatePhase.REJECTED || !data?.trigger) { |
||||
return <Callout>{t('Error')}: error</Callout>; |
||||
} |
||||
|
||||
return <EditTriggerPage data={data.trigger} onSave={onSave} />; |
||||
}; |
||||
|
||||
export default EditTriggerPageContainer; |
||||
@ -0,0 +1,28 @@ |
||||
import type { ILivechatTrigger } from '@rocket.chat/core-typings'; |
||||
import { Callout } from '@rocket.chat/fuselage'; |
||||
import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; |
||||
import { useQuery } from '@tanstack/react-query'; |
||||
import React from 'react'; |
||||
|
||||
import { ContextualbarSkeleton } from '../../../components/Contextualbar'; |
||||
import EditTrigger from './EditTrigger'; |
||||
|
||||
const EditTriggerWithData = ({ triggerId }: { triggerId: ILivechatTrigger['_id'] }) => { |
||||
const t = useTranslation(); |
||||
const getTriggersById = useEndpoint('GET', '/v1/livechat/triggers/:_id', { _id: triggerId }); |
||||
const { data, isLoading, isError } = useQuery(['livechat-getTriggersById', triggerId], async () => getTriggersById(), { |
||||
refetchOnWindowFocus: false, |
||||
}); |
||||
|
||||
if (isLoading) { |
||||
return <ContextualbarSkeleton />; |
||||
} |
||||
|
||||
if (isError) { |
||||
return <Callout>{t('Error')}</Callout>; |
||||
} |
||||
|
||||
return <EditTrigger triggerData={data.trigger} />; |
||||
}; |
||||
|
||||
export default EditTriggerWithData; |
||||
@ -1,94 +0,0 @@ |
||||
import { Button, FieldGroup, ButtonGroup } from '@rocket.chat/fuselage'; |
||||
import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; |
||||
import { useToastMessageDispatch, useRoute, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; |
||||
import React, { useMemo } from 'react'; |
||||
|
||||
import { ContextualbarScrollableContent, ContextualbarFooter } from '../../../components/Contextualbar'; |
||||
import { useForm } from '../../../hooks/useForm'; |
||||
import TriggersForm from './TriggersForm'; |
||||
|
||||
const NewTriggerPage = ({ onSave }) => { |
||||
const dispatchToastMessage = useToastMessageDispatch(); |
||||
const t = useTranslation(); |
||||
|
||||
const router = useRoute('omnichannel-triggers'); |
||||
|
||||
const save = useEndpoint('POST', '/v1/livechat/triggers'); |
||||
|
||||
const { values, handlers } = useForm({ |
||||
name: '', |
||||
description: '', |
||||
enabled: true, |
||||
runOnce: false, |
||||
conditions: { |
||||
name: 'page-url', |
||||
value: '', |
||||
}, |
||||
actions: { |
||||
name: '', |
||||
params: { |
||||
sender: 'queue', |
||||
msg: '', |
||||
name: '', |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
const handleSave = useMutableCallback(async () => { |
||||
try { |
||||
const { |
||||
actions: { |
||||
params: { sender, msg, name }, |
||||
}, |
||||
...restValues |
||||
} = values; |
||||
await save({ |
||||
...restValues, |
||||
conditions: [values.conditions], |
||||
actions: [ |
||||
{ |
||||
name: 'send-message', |
||||
params: { |
||||
sender, |
||||
msg, |
||||
...(sender === 'custom' && { name }), |
||||
}, |
||||
}, |
||||
], |
||||
}); |
||||
dispatchToastMessage({ type: 'success', message: t('Saved') }); |
||||
onSave(); |
||||
router.push({}); |
||||
} catch (error) { |
||||
dispatchToastMessage({ type: 'error', message: error }); |
||||
} |
||||
}); |
||||
|
||||
const { |
||||
name, |
||||
actions: { |
||||
params: { msg }, |
||||
}, |
||||
} = values; |
||||
|
||||
const canSave = useMemo(() => name && msg, [name, msg]); |
||||
|
||||
return ( |
||||
<> |
||||
<ContextualbarScrollableContent> |
||||
<FieldGroup> |
||||
<TriggersForm values={values} handlers={handlers} /> |
||||
</FieldGroup> |
||||
</ContextualbarScrollableContent> |
||||
<ContextualbarFooter> |
||||
<ButtonGroup stretch> |
||||
<Button primary onClick={handleSave} disabled={!canSave}> |
||||
{t('Save')} |
||||
</Button> |
||||
</ButtonGroup> |
||||
</ContextualbarFooter> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export default NewTriggerPage; |
||||
@ -1,212 +0,0 @@ |
||||
import type { SelectOption } from '@rocket.chat/fuselage'; |
||||
import { Box, Field, FieldLabel, FieldRow, FieldError, TextInput, ToggleSwitch, Select, TextAreaInput } from '@rocket.chat/fuselage'; |
||||
import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; |
||||
import { useTranslation } from '@rocket.chat/ui-contexts'; |
||||
import type { ComponentProps, FC, FormEvent } from 'react'; |
||||
import React, { useMemo, useState } from 'react'; |
||||
|
||||
import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; |
||||
|
||||
type TriggerConditions = { |
||||
name: string; |
||||
value: string | number; |
||||
}; |
||||
|
||||
type TriggerActions = { |
||||
name: string; |
||||
params: { |
||||
sender: string | undefined; |
||||
msg: string; |
||||
name: string; |
||||
}; |
||||
}; |
||||
|
||||
type TriggersFormProps = { |
||||
values: { |
||||
name: string; |
||||
description: string; |
||||
enabled: boolean; |
||||
runOnce: boolean; |
||||
// In the future, this will be an array
|
||||
conditions: TriggerConditions; |
||||
// In the future, this will be an array
|
||||
actions: TriggerActions; |
||||
}; |
||||
handlers: { |
||||
handleName: (event: FormEvent<HTMLInputElement>) => void; |
||||
handleDescription: (event: FormEvent<HTMLInputElement>) => void; |
||||
handleEnabled: (event: FormEvent<HTMLInputElement>) => void; |
||||
handleRunOnce: (event: FormEvent<HTMLInputElement>) => void; |
||||
handleConditions: (value: TriggerConditions) => void; |
||||
handleActions: (value: TriggerActions) => void; |
||||
}; |
||||
className?: ComponentProps<typeof Field>['className']; |
||||
}; |
||||
|
||||
const TriggersForm: FC<TriggersFormProps> = ({ values, handlers, className }) => { |
||||
const [nameError, setNameError] = useState(''); |
||||
const [msgError, setMsgError] = useState(''); |
||||
const t = useTranslation(); |
||||
const { name, description, enabled, runOnce, conditions, actions } = values; |
||||
|
||||
const { handleName, handleDescription, handleEnabled, handleRunOnce, handleConditions, handleActions } = handlers; |
||||
|
||||
const { name: conditionName, value: conditionValue } = conditions; |
||||
|
||||
const { |
||||
params: { sender: actionSender, msg: actionMsg, name: actionAgentName }, |
||||
} = actions; |
||||
|
||||
const conditionOptions: SelectOption[] = useMemo( |
||||
() => [ |
||||
['page-url', t('Visitor_page_URL')], |
||||
['time-on-site', t('Visitor_time_on_site')], |
||||
['chat-opened-by-visitor', t('Chat_opened_by_visitor')], |
||||
['after-guest-registration', t('After_guest_registration')], |
||||
], |
||||
[t], |
||||
); |
||||
|
||||
const conditionValuePlaceholders: { [conditionName: string]: string } = useMemo( |
||||
() => ({ |
||||
'page-url': t('Enter_a_regex'), |
||||
'time-on-site': t('Time_in_seconds'), |
||||
}), |
||||
[t], |
||||
); |
||||
|
||||
const conditionValuePlaceholder = conditionValuePlaceholders[conditionName]; |
||||
|
||||
const senderOptions: SelectOption[] = useMemo( |
||||
() => [ |
||||
['queue', t('Impersonate_next_agent_from_queue')], |
||||
['custom', t('Custom_agent')], |
||||
], |
||||
[t], |
||||
); |
||||
|
||||
const handleConditionName = useMutableCallback((name) => { |
||||
handleConditions({ |
||||
name, |
||||
value: '', |
||||
}); |
||||
}); |
||||
|
||||
const handleConditionValue = useMutableCallback(({ currentTarget: { value } }) => { |
||||
handleConditions({ |
||||
...conditions, |
||||
value, |
||||
}); |
||||
}); |
||||
|
||||
const handleActionAgentName = useMutableCallback(({ currentTarget: { value: name } }) => { |
||||
handleActions({ |
||||
...actions, |
||||
params: { |
||||
...actions.params, |
||||
name, |
||||
}, |
||||
}); |
||||
}); |
||||
|
||||
const handleActionSender = useMutableCallback((sender) => { |
||||
handleActions({ |
||||
...actions, |
||||
params: { |
||||
...actions.params, |
||||
sender, |
||||
}, |
||||
}); |
||||
}); |
||||
|
||||
const handleActionMessage = useMutableCallback(({ currentTarget: { value: msg } }) => { |
||||
handleActions({ |
||||
...actions, |
||||
params: { |
||||
...actions.params, |
||||
msg, |
||||
}, |
||||
}); |
||||
}); |
||||
useComponentDidUpdate(() => { |
||||
setNameError(!name ? t('The_field_is_required', t('Name')) : ''); |
||||
}, [t, name]); |
||||
useComponentDidUpdate(() => { |
||||
setMsgError(!actionMsg ? t('The_field_is_required', t('Message')) : ''); |
||||
}, [t, actionMsg]); |
||||
return ( |
||||
<> |
||||
<Field className={className}> |
||||
<Box display='flex' flexDirection='row'> |
||||
<FieldLabel>{t('Enabled')}</FieldLabel> |
||||
<FieldRow> |
||||
<ToggleSwitch checked={enabled} onChange={handleEnabled} /> |
||||
</FieldRow> |
||||
</Box> |
||||
</Field> |
||||
<Field className={className}> |
||||
<Box display='flex' flexDirection='row'> |
||||
<FieldLabel>{t('Run_only_once_for_each_visitor')}</FieldLabel> |
||||
<FieldRow> |
||||
<ToggleSwitch checked={runOnce} onChange={handleRunOnce} /> |
||||
</FieldRow> |
||||
</Box> |
||||
</Field> |
||||
<Field className={className}> |
||||
<FieldLabel>{t('Name')}*</FieldLabel> |
||||
<FieldRow> |
||||
<TextInput value={name} error={nameError} onChange={handleName} placeholder={t('Name')} /> |
||||
</FieldRow> |
||||
<FieldError>{nameError}</FieldError> |
||||
</Field> |
||||
<Field className={className}> |
||||
<FieldLabel>{t('Description')}</FieldLabel> |
||||
<FieldRow> |
||||
<TextInput value={description} onChange={handleDescription} placeholder={t('Description')} /> |
||||
</FieldRow> |
||||
</Field> |
||||
<Field className={className}> |
||||
<FieldLabel>{t('Condition')}</FieldLabel> |
||||
<FieldRow> |
||||
<Select name='condition' options={conditionOptions} value={conditionName} onChange={handleConditionName} /> |
||||
</FieldRow> |
||||
{conditionValuePlaceholder && ( |
||||
<FieldRow> |
||||
<TextInput |
||||
name='conditionValue' |
||||
value={conditionValue} |
||||
onChange={handleConditionValue} |
||||
placeholder={conditionValuePlaceholder} |
||||
/> |
||||
</FieldRow> |
||||
)} |
||||
</Field> |
||||
<Field className={className}> |
||||
<FieldLabel>{t('Action')}</FieldLabel> |
||||
<FieldRow> |
||||
<TextInput value={t('Send_a_message')} disabled /> |
||||
</FieldRow> |
||||
<FieldRow> |
||||
<Select |
||||
name='sender' |
||||
options={senderOptions} |
||||
value={actionSender} |
||||
onChange={handleActionSender} |
||||
placeholder={t('Select_an_option')} |
||||
/> |
||||
</FieldRow> |
||||
{actionSender === 'custom' && ( |
||||
<FieldRow> |
||||
<TextInput name='agentName' value={actionAgentName} onChange={handleActionAgentName} placeholder={t('Name_of_agent')} /> |
||||
</FieldRow> |
||||
)} |
||||
<FieldRow> |
||||
<TextAreaInput name='triggerMessage' rows={3} value={actionMsg} onChange={handleActionMessage} placeholder={`${t('Message')}*`} /> |
||||
</FieldRow> |
||||
<FieldError>{msgError}</FieldError> |
||||
</Field> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export default TriggersForm; |
||||
@ -0,0 +1,17 @@ |
||||
import { usePermission } from '@rocket.chat/ui-contexts'; |
||||
import React from 'react'; |
||||
|
||||
import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; |
||||
import TriggersPage from './TriggersPage'; |
||||
|
||||
const TriggersRoute = () => { |
||||
const canViewTriggers = usePermission('view-livechat-triggers'); |
||||
|
||||
if (!canViewTriggers) { |
||||
return <NotAuthorizedPage />; |
||||
} |
||||
|
||||
return <TriggersPage />; |
||||
}; |
||||
|
||||
export default TriggersRoute; |
||||
Loading…
Reference in new issue