Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -184,43 +185,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