Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/witty-impalas-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/ui-client': minor
'@rocket.chat/meteor': minor
---

Improves the Outbound Message modal’s scrolling on smaller viewports and with large templates
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
ILivechatContact,
} from '@rocket.chat/core-typings';
import { Box, Margins } from '@rocket.chat/fuselage';
import type { ComponentProps } from 'react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';

Expand All @@ -14,7 +15,7 @@ import { formatPhoneNumber } from '../../../../../lib/formatPhoneNumber';
import type { TemplateParameters } from '../../definitions/template';
import TemplatePreview from '../TemplatePreview';

type OutboundMessagePreviewProps = {
type OutboundMessagePreviewProps = ComponentProps<typeof Box> & {
template?: IOutboundProviderTemplate;
contactName?: ILivechatContact['name'];
providerName?: IOutboundProviderMetadata['providerName'];
Expand Down Expand Up @@ -50,6 +51,7 @@ const OutboundMessagePreview = ({
sender: rawSender,
recipient: rawRecipient,
templateParameters,
...props
}: OutboundMessagePreviewProps) => {
const { t } = useTranslation();

Expand All @@ -68,7 +70,7 @@ const OutboundMessagePreview = ({
}, [agentName, agentUsername, departmentName]);

return (
<div>
<Box {...props} is='section'>
<ul>
<Margins blockStart={24}>
<Box is='li' mbs={0}>
Expand Down Expand Up @@ -99,7 +101,7 @@ const OutboundMessagePreview = ({
<TemplatePreview template={template} parameters={templateParameters} />
</Box>
) : null}
</div>
</Box>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@ const OutboundMessageWizard = ({ defaultValues = {}, onSuccess, onError }: Outbo

return (
<ErrorBoundary fallbackRender={() => <GenericError icon='circle-exclamation' />}>
<Wizard api={wizardApi}>
<Wizard api={wizardApi} display='flex' flexDirection='column' height='100%'>
<WizardTabs />

<Box mbs={16}>
<Box mbs={16} minHeight={0} flexGrow={1}>
<WizardContent id='recipient'>
<RecipientStep defaultValues={state} onDirty={handleDirtyStep} onSubmit={handleSubmit} />
</WizardContent>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Box } from '@rocket.chat/fuselage';
import type { ComponentProps } from 'react';

type OutboundMessageFormProps = ComponentProps<typeof Box> & {
onSubmit?: () => void;
};

const OutboundMessageForm = ({ onSubmit, ...props }: OutboundMessageFormProps) => (
<Box is='form' display='flex' flexDirection='column' height='100%' flexGrow={1} flexShrink={0} onSubmit={onSubmit} {...props} />
);

export default OutboundMessageForm;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IOutboundProviderTemplate, Serialized, ILivechatContact } from '@rocket.chat/core-typings';
import { Box, Button, FieldGroup } from '@rocket.chat/fuselage';
import { Box, Button, FieldGroup, Scrollable } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar';
import type { ReactNode } from 'react';
Expand All @@ -12,6 +12,7 @@ import TemplatePlaceholderField from './components/TemplatePlaceholderField';
import TemplatePreviewForm from './components/TemplatePreviewField';
import type { TemplateParameters } from '../../../../definitions/template';
import { extractParameterMetadata } from '../../../../utils/template';
import Form from '../../components/OutboundMessageForm';
import { FormFetchError } from '../../utils/errors';

export type MessageFormData = {
Expand Down Expand Up @@ -78,16 +79,18 @@ const MessageForm = (props: MessageFormProps) => {
});

return (
<form id={messageFormId} onSubmit={handleSubmit(submit)} noValidate>
<FieldGroup>
<TemplateField control={control} templates={templates} onChange={() => setValue('templateParameters', {})} />
<Form id={messageFormId} onSubmit={handleSubmit(submit)} noValidate>
<Scrollable vertical>
<FieldGroup justifyContent='start' pi={2}>
<TemplateField control={control} templates={templates} onChange={() => setValue('templateParameters', {})} />

{parametersMetadata.map((metadata) => (
<TemplatePlaceholderField key={metadata.id} control={control} metadata={metadata} contact={contact} />
))}
{parametersMetadata.map((metadata) => (
<TemplatePlaceholderField key={metadata.id} control={control} metadata={metadata} contact={contact} />
))}

{template ? <TemplatePreviewForm control={control} template={template} /> : null}
</FieldGroup>
{template ? <TemplatePreviewForm control={control} template={template} /> : null}
</FieldGroup>
</Scrollable>

{customActions ?? (
<Box mbs={24} display='flex' justifyContent='end'>
Expand All @@ -96,7 +99,7 @@ const MessageForm = (props: MessageFormProps) => {
</Button>
</Box>
)}
</form>
</Form>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IOutboundProviderTemplate, Serialized } from '@rocket.chat/core-typings';
import { Field, FieldError, FieldHint, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import type { ComponentProps } from 'react';
import { useId } from 'react';
import type { Control } from 'react-hook-form';
import { useController } from 'react-hook-form';
Expand All @@ -11,13 +12,13 @@ import TemplateSelect from '../../../../TemplateSelect';
import { cxp } from '../../../utils/cx';
import type { MessageFormData } from '../MessageForm';

type TemplateFieldProps = {
type TemplateFieldProps = ComponentProps<typeof Field> & {
control: Control<MessageFormData>;
templates: Serialized<IOutboundProviderTemplate>[] | undefined;
onChange?: (templateId: string) => void;
};

const TemplateField = ({ control, templates, onChange: onChangeExternal }: TemplateFieldProps) => {
const TemplateField = ({ control, templates, onChange: onChangeExternal, ...props }: TemplateFieldProps) => {
const { t } = useTranslation();
const templateFieldId = useId();

Expand Down Expand Up @@ -46,7 +47,7 @@ const TemplateField = ({ control, templates, onChange: onChangeExternal }: Templ
});

return (
<Field>
<Field {...props}>
<FieldLabel is='span' required id={templateFieldId}>
{t('Template')}
</FieldLabel>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IOutboundProviderMetadata, Serialized, ILivechatContact } from '@rocket.chat/core-typings';
import { Box, Button, FieldGroup } from '@rocket.chat/fuselage';
import { Box, Button, FieldGroup, Scrollable } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar';
import { useEndpoint } from '@rocket.chat/ui-contexts';
Expand All @@ -14,6 +14,7 @@ import ContactField from './components/ContactField';
import RecipientField from './components/RecipientField';
import SenderField from './components/SenderField';
import { omnichannelQueryKeys } from '../../../../../../../lib/queryKeys';
import Form from '../../components/OutboundMessageForm';
import { ContactNotFoundError, ProviderNotFoundError } from '../../utils/errors';

export type RecipientFormData = {
Expand Down Expand Up @@ -175,43 +176,45 @@ const RecipientForm = (props: RecipientFormProps) => {
});

return (
<form id={recipientFormId} onSubmit={handleSubmit(submit)} noValidate>
<FieldGroup>
<ContactField
control={control}
isError={isErrorContact || isContactNotFound}
isFetching={isFetchingContact}
onRetry={refetchContact}
/>

<ChannelField
control={control}
contact={contact}
disabled={!contactId}
isFetching={isFetchingProvider}
isError={isErrorProvider || isProviderNotFound}
onRetry={refetchProvider}
/>

<RecipientField
control={control}
contact={contact}
type={provider?.providerType || 'phone'}
disabled={!providerId || !contact}
isLoading={isFetchingContact}
/>

<SenderField control={control} provider={provider} disabled={!recipient || !provider} isLoading={isFetchingProvider} />
</FieldGroup>
<Form id={recipientFormId} onSubmit={handleSubmit(submit)} noValidate>
<Scrollable vertical>
<FieldGroup justifyContent='start' pi={2}>
<ContactField
control={control}
isError={isErrorContact || isContactNotFound}
isFetching={isFetchingContact}
onRetry={refetchContact}
/>

<ChannelField
control={control}
contact={contact}
disabled={!contactId}
isFetching={isFetchingProvider}
isError={isErrorProvider || isProviderNotFound}
onRetry={refetchProvider}
/>

<RecipientField
control={control}
contact={contact}
type={provider?.providerType || 'phone'}
disabled={!providerId || !contact}
isLoading={isFetchingContact}
/>

<SenderField control={control} provider={provider} disabled={!recipient || !provider} isLoading={isFetchingProvider} />
</FieldGroup>
</Scrollable>

{customActions ?? (
<Box mbs={24} display='flex' justifyContent='end'>
<Box mbs={24} display='flex' justifyContent='end' flexGrow={0} flexShrink={0}>
<Button type='submit' primary loading={isSubmitting}>
{t('Submit')}
</Button>
</Box>
)}
</form>
</Form>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Serialized, ILivechatDepartment, ILivechatDepartmentAgents } from '@rocket.chat/core-typings';
import { Box, Button, FieldGroup } from '@rocket.chat/fuselage';
import { Box, Button, FieldGroup, Scrollable } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar';
import { useEndpoint, usePermission, useUser } from '@rocket.chat/ui-contexts';
Expand All @@ -13,6 +13,7 @@ import AgentField from './components/AgentField';
import DepartmentField from './components/DepartmentField';
import { useAllowedAgents } from './hooks/useAllowedAgents';
import { omnichannelQueryKeys } from '../../../../../../../lib/queryKeys';
import Form from '../../components/OutboundMessageForm';
import { FormFetchError } from '../../utils/errors';

export type RepliesFormData = {
Expand Down Expand Up @@ -115,25 +116,27 @@ const RepliesForm = (props: RepliesFormProps) => {
});

return (
<form id={repliesFormId} onSubmit={handleSubmit(submit)} noValidate>
<FieldGroup>
<DepartmentField
control={control}
onlyMyDepartments={!canAssignAllDepartments}
isError={isErrorDepartment}
isFetching={isFetchingDepartment}
onRefetch={refetchDepartment}
onChange={() => setValue('agentId', '')}
/>

<AgentField
control={control}
agents={agents}
canAssignAgent={canAssignAnyAgent || canAssignSelfOnlyAgent}
disabled={!departmentId}
isLoading={isFetchingDepartment}
/>
</FieldGroup>
<Form id={repliesFormId} onSubmit={handleSubmit(submit)} noValidate>
<Scrollable vertical>
<FieldGroup justifyContent='start' pi={2}>
<DepartmentField
control={control}
onlyMyDepartments={!canAssignAllDepartments}
isError={isErrorDepartment}
isFetching={isFetchingDepartment}
onRefetch={refetchDepartment}
onChange={() => setValue('agentId', '')}
/>

<AgentField
control={control}
agents={agents}
canAssignAgent={canAssignAnyAgent || canAssignSelfOnlyAgent}
disabled={!departmentId}
isLoading={isFetchingDepartment}
/>
</FieldGroup>
</Scrollable>

{customActions ?? (
<Box mbs={24} display='flex' justifyContent='end'>
Expand All @@ -142,7 +145,7 @@ const RepliesForm = (props: RepliesFormProps) => {
</Button>
</Box>
)}
</form>
</Form>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ILivechatDepartmentAgents, Serialized } from '@rocket.chat/core-typings';
import { Field, FieldError, FieldHint, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
import type { ComponentProps } from 'react';
import { useId } from 'react';
import type { Control } from 'react-hook-form';
import { useController } from 'react-hook-form';
Expand All @@ -9,15 +10,15 @@ import AutoCompleteAgent from '../../../../AutoCompleteDepartmentAgent';
import { cxp } from '../../../utils/cx';
import type { RepliesFormData } from '../RepliesForm';

type AgentFieldProps = {
type AgentFieldProps = ComponentProps<typeof Field> & {
control: Control<RepliesFormData>;
agents: Serialized<ILivechatDepartmentAgents>[];
canAssignAgent?: boolean;
disabled?: boolean;
isLoading?: boolean;
};

const AgentField = ({ control, agents, canAssignAgent, disabled = false, isLoading = false }: AgentFieldProps) => {
const AgentField = ({ control, agents, canAssignAgent, disabled = false, isLoading = false, ...props }: AgentFieldProps) => {
const { t } = useTranslation();
const agentFieldId = useId();

Expand All @@ -30,7 +31,7 @@ const AgentField = ({ control, agents, canAssignAgent, disabled = false, isLoadi
});

return (
<Field>
<Field {...props}>
<FieldLabel htmlFor={agentFieldId}>{`${t('Agent')} (${t('optional')})`}</FieldLabel>
<FieldRow>
<AutoCompleteAgent
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Field, FieldError, FieldHint, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import type { ComponentProps } from 'react';
import { useId } from 'react';
import type { Control } from 'react-hook-form';
import { useController } from 'react-hook-form';
Expand All @@ -10,7 +11,7 @@ import RetryButton from '../../../components/RetryButton';
import { cxp } from '../../../utils/cx';
import type { RepliesFormData } from '../RepliesForm';

type DepartmentFieldProps = {
type DepartmentFieldProps = ComponentProps<typeof Field> & {
control: Control<RepliesFormData>;
onlyMyDepartments?: boolean;
isError: boolean;
Expand All @@ -26,6 +27,7 @@ const DepartmentField = ({
isFetching,
onRefetch,
onChange: onChangeExternal,
...props
}: DepartmentFieldProps) => {
const { t } = useTranslation();
const departmentFieldId = useId();
Expand All @@ -49,7 +51,7 @@ const DepartmentField = ({
});

return (
<Field>
<Field {...props}>
<FieldLabel is='span' id={departmentFieldId}>{`${t('Department')} (${t('optional')})`}</FieldLabel>
<FieldRow>
<AutoCompleteDepartment
Expand Down
Loading
Loading