Skip to content

Conversation

@aleksandernsilva
Copy link
Contributor

@aleksandernsilva aleksandernsilva commented Jun 11, 2025

Proposed changes (including videos or screenshots)

This PR introduces the Outbound Message feature to Omnichannel, allowing organizations to initiate proactive communication with contacts through their preferred messaging channel directly from Rocket.Chat.

Screen.Recording.2025-09-16.at.11.21.01.mov

Issue(s)

CTZ-186

Steps to test or reproduce

Further comments

Summary by CodeRabbit

  • New Features

    • Full Outbound Message experience: multi-step wizard (Recipient, Message, Replies, Review), modal with discard confirmation and upsell flows, send preview, and outbound actions in menus and contact UI (shown only with proper license/permission).
  • Improvements

    • New autocompletes and selects for contacts, providers, departments, and agents; template editor, placeholders and live preview; phone formatting, channel hints, keyboard submit, better loading/retry UX, accessibility, and expanded EN/PT‑BR translations.
  • Chores

    • Dev dependency bumps for UI library.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Jun 11, 2025

Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Jun 11, 2025

🦋 Changeset detected

Latest commit: b6fcc40

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 44 packages
Name Type
@rocket.chat/web-ui-registration Major
@rocket.chat/storybook-config Patch
@rocket.chat/fuselage-ui-kit Major
@rocket.chat/ui-theming Patch
@rocket.chat/ui-video-conf Major
@rocket.chat/uikit-playground Patch
@rocket.chat/ui-composer Patch
@rocket.chat/gazzodown Major
@rocket.chat/ui-avatar Major
@rocket.chat/ui-client Major
@rocket.chat/ui-voip Major
@rocket.chat/core-typings Minor
@rocket.chat/apps-engine Minor
@rocket.chat/license Minor
@rocket.chat/i18n Minor
@rocket.chat/meteor Minor
@rocket.chat/mock-providers Patch
@rocket.chat/livechat Patch
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/freeswitch Patch
@rocket.chat/http-router Patch
@rocket.chat/model-typings Patch
@rocket.chat/rest-typings Minor
@rocket.chat/ui-contexts Major
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/stream-hub-service Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/models Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov
Copy link

codecov bot commented Jun 11, 2025

Codecov Report

❌ Patch coverage is 86.48214% with 420 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.23%. Comparing base (3c28676) to head (b6fcc40).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #36207      +/-   ##
===========================================
+ Coverage    66.58%   70.23%   +3.65%     
===========================================
  Files         3346     3090     -256     
  Lines       114666   106231    -8435     
  Branches     21099    18845    -2254     
===========================================
- Hits         76352    74615    -1737     
+ Misses       35619    29643    -5976     
+ Partials      2695     1973     -722     
Flag Coverage Δ
e2e 56.98% <13.15%> (-0.83%) ⬇️
e2e-api ?
unit 71.87% <87.16%> (+0.64%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 17, 2025

PR Preview Action v1.6.2

🚀 View preview at
https://RocketChat.github.io/Rocket.Chat/pr-preview/pr-36207/

Built to branch gh-pages at 2025-08-14 20:23 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@aleksandernsilva aleksandernsilva force-pushed the feat/outbound-msg-ui branch 8 times, most recently from e1f94ba to 672300d Compare June 24, 2025 15:55
@aleksandernsilva aleksandernsilva force-pushed the feat/outbound-msg-ui branch 6 times, most recently from 39df003 to ac91c2b Compare July 2, 2025 18:26
@aleksandernsilva aleksandernsilva force-pushed the feat/outbound-msg-ui branch 11 times, most recently from 52a9cc6 to 49f27be Compare July 9, 2025 13:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (5)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/RecipientField.tsx (1)

31-31: Fix confirmed: phone-only validation now gated by type.

This resolves the earlier bug where email flows were blocked. Good change.

packages/i18n/src/locales/en.i18n.json (1)

3907-3913: Consolidate 'Outbound_Message' vs 'Outbound_message' across repo

Past review flagged mixed casing. Keep a single canonical key (suggest: "Outbound_message") and drop the other variant; update callers accordingly.

Run to verify keys and usages:

#!/bin/bash
# Find duplicate i18n keys and call sites
rg -nP '"Outbound_Message"\s*:' packages/i18n/src/locales/en.i18n.json
rg -nP '"Outbound_message"\s*:' packages/i18n/src/locales/en.i18n.json

# Find UI call sites (ts/tsx/js)
rg -n --type-add 'tsx:*.{ts,tsx,js,jsx}' -e "t\\(['\"]Outbound_Message['\"]\\)" -e "t\\(['\"]Outbound_message['\"]\\)" -C2
apps/meteor/client/views/omnichannel/contactInfo/EditContactInfo.tsx (3)

180-180: Nice: switched to mutateAsync so isSubmitting reflects network work.

This resolves the prior UX concern.


179-185: Fix invalidation targets after edit: don’t invalidate contact() without id; widen contacts invalidation prefix.

  • invalidateQueries({ queryKey: omnichannelQueryKeys.contact() }) won’t match detail queries keyed with a contact id.
  • contactSearch() with a 3-segment key including undefined won’t match list/search variants that pass params.

Use the specific contact id for detail and a 2‑segment prefix for lists/search.

Apply this diff:

-    await editContact.mutateAsync({ contactId: contactData?._id, ...payload });
-    await Promise.all([
-      queryClient.invalidateQueries({ queryKey: omnichannelQueryKeys.contact() }),
-      queryClient.invalidateQueries({ queryKey: omnichannelQueryKeys.contactSearch() }),
-    ]);
+    await editContact.mutateAsync({ contactId: contactData._id, ...payload });
+    await Promise.all([
+      // Detail: precise key for the edited contact
+      queryClient.invalidateQueries({ queryKey: omnichannelQueryKeys.contact(contactData._id) }),
+      // Lists/search: invalidate all variants (any filter/limit)
+      queryClient.invalidateQueries({ queryKey: [...omnichannelQueryKeys.all, 'contacts'], exact: false }),
+    ]);

188-192: Create flow: capture created id (if returned) and invalidate correct prefixes.

  • Dropping contact() without id avoids a no-op.
  • Invalidate all contacts lists; if API returns the new _id, also invalidate its detail.

Apply this diff:

-    await createContact.mutateAsync(payload);
-    await Promise.all([
-      queryClient.invalidateQueries({ queryKey: omnichannelQueryKeys.contact() }),
-      queryClient.invalidateQueries({ queryKey: omnichannelQueryKeys.contactSearch() }),
-    ]);
+    const created = await createContact.mutateAsync(payload);
+    // Lists/search
+    await queryClient.invalidateQueries({ queryKey: [...omnichannelQueryKeys.all, 'contacts'], exact: false });
+    // Detail (best-effort)
+    if (created?._id) {
+      await queryClient.invalidateQueries({ queryKey: omnichannelQueryKeys.contact(created._id) });
+    }
🧹 Nitpick comments (41)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/RecipientField.tsx (6)

50-50: Pass a boolean to error prop.

Use a boolean instead of the string literal to match common prop typing and avoid accidental truthy strings.

-					error={recipientFieldError && 'error'}
+					error={Boolean(recipientFieldError)}

43-55: Preserve RHF semantics: pass onBlur.

Include onBlur so touched state and onBlur validation work as expected.

 					onChange={recipientField.onChange}
+					onBlur={recipientField.onBlur}

29-35: Optional: add symmetric email validation + message.

Mirror the phone validation for email to give users clearer guidance when contacts lack emails.

 			rules: {
 				validate: {
 					noPhoneNumber: () => (type === 'phone' && contact ? !!contact.phones?.length : true),
+					noEmail: () => (type === 'email' && contact ? !!contact.emails?.length : true),
 					required: (value) => (!value ? t('Required_field', { field: t('To') }) : true),
 				},
 			},
 			{recipientFieldError?.type === 'noPhoneNumber' && (
 				<FieldError aria-live='assertive' id={`${recipientFieldId}-error`}>
 					<Trans i18nKey='No_phone_number_yet_edit_contact'>
 						No phone number yet <a href={`/omnichannel-directory/contacts/edit/${contact?._id}`}>Edit contact</a>
 					</Trans>
 				</FieldError>
 			)}
+			{recipientFieldError?.type === 'noEmail' && (
+				<FieldError aria-live='assertive' id={`${recipientFieldId}-error`}>
+					<Trans i18nKey='No_email_yet_edit_contact'>
+						No email yet <a href={`/omnichannel-directory/contacts/edit/${contact?._id}`}>Edit contact</a>
+					</Trans>
+				</FieldError>
+			)}

Also applies to: 62-68


16-19: Minor typing nit: make isLoading optional.

You already default it to false; mark it optional for ergonomics.

-	isLoading: boolean;
+	isLoading?: boolean;

39-39: Tiny cleanup: avoid redundant template literal.

-			<FieldLabel is='span' required id={`${recipientFieldId}`}>
+			<FieldLabel is='span' required id={recipientFieldId}>

64-66: Optional: keep markup out of translations and avoid hardcoded routes.

Prefer Trans components mapping and a route helper to build the URL; this reduces translator risk and centralizes paths.

apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx (3)

34-34: Hook usage OK; consider narrowing deps by destructuring.

Optionally do const { open } = useOutboundMessageModal(); and depend on open instead of the whole object in the memo below.


46-51: Make “Block/Unblock” styling reflect action severity.

Showing “Unblock” as danger is a bit off. Use danger only when the action is “Block”.

Apply:

-        variant: 'danger',
+        variant: blocked ? 'default' : 'danger',

10-10: Re‑export verified — consider lazy‑loading the modal.

  • Verified: apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/index.ts re-exports the hook (export * from './useOutboundMessageModal') and the hook file exports useOutboundMessageModal — the import path is valid.
  • Optional: lazy‑load the modal component inside useOutboundMessageModal (apps/meteor/.../OutboundMessageModal/useOutboundMessageModal.tsx) to avoid pulling modal code into this view’s chunk.
apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoPhoneEntry.tsx (3)

12-15: Narrow prop type: enforce value as string.

Ensures parseOutboundPhoneNumber receives the expected type; avoids accidental ReactNode passing.

-type ContactInfoPhoneEntryProps = Omit<ComponentProps<typeof ContactInfoDetailsEntry>, 'icon' | 'actions'> & {
-	contactId?: string;
-};
+type ContactInfoPhoneEntryProps = Omit<ComponentProps<typeof ContactInfoDetailsEntry>, 'icon' | 'actions' | 'value'> & {
+	value: string;
+	contactId?: string;
+};

16-20: Compute once; copy the displayed value for consistency.

Avoids double parsing and makes the clipboard match the UI value.

-const ContactInfoPhoneEntry = ({ contactId, value, ...props }: ContactInfoPhoneEntryProps) => {
+const ContactInfoPhoneEntry = ({ contactId, value, ...props }: ContactInfoPhoneEntryProps) => {
 	const { t } = useTranslation();
 	const isCallReady = useIsCallReady();
-	const { copy } = useClipboardWithToast(value);
+	const formattedValue = parseOutboundPhoneNumber(value);
+	const { copy } = useClipboardWithToast(formattedValue);
@@
-			value={parseOutboundPhoneNumber(value)}
+			value={formattedValue}

Also applies to: 25-25


21-31: Memoize actions to avoid unnecessary re-renders (optional).

Minor perf tidy-up; keeps props stable when nothing changes.

+import { useMemo } from 'react';
@@
-	return (
-		<ContactInfoDetailsEntry
+	const actions = useMemo(
+		() => [
+			<IconButton key='copy' onClick={copy} tiny icon='copy' title={t('Copy')} aria-label={t('Copy')} />,
+			isCallReady ? <ContactInfoCallButton key='call' phoneNumber={value} /> : null,
+			<ContactInfoOutboundMessageButton key='outbound-message' defaultValues={{ contactId, recipient: value }} />,
+		].filter(Boolean),
+		[copy, isCallReady, value, contactId, t],
+	);
+
+	return (
+		<ContactInfoDetailsEntry
 			{...props}
 			icon='phone'
-			value={formattedValue}
-			actions={[
-				<IconButton key='copy' onClick={copy} tiny icon='copy' title={t('Copy')} aria-label={t('Copy')} />,
-				isCallReady ? <ContactInfoCallButton key='call' phoneNumber={value} /> : null,
-				<ContactInfoOutboundMessageButton key='outbound-message' defaultValues={{ contactId, recipient: value }} />,
-			].filter(Boolean)}
+			value={formattedValue}
+			actions={actions}
 		/>
 	);
packages/i18n/src/locales/pt-BR.i18n.json (5)

671-671: Term consistency: “mensagem ativa” vs “proativa/saída”.

PR introduz “Outbound message” como “mensagem ativa”. Em PT‑BR, “mensagem proativa” ou “mensagem de saída” são mais claros. Sugiro alinhar todas as novas chaves “Outbound_message*” para um único termo. Exemplo de ajuste nesta linha:

- "Are_you_sure_you_want_to_discard_this_outbound_message": "Tem certeza de que deseja descartar esta mensagem ativa?",
+ "Are_you_sure_you_want_to_discard_this_outbound_message": "Tem certeza de que deseja descartar esta mensagem proativa?",

3371-3371: Melhorar naturalidade da frase.

“ O envio da mensagem não pode ser desfeito” soa artificial. Sugestão:

- "Messages_cannot_be_unsent": "O envio da mensagem não pode ser desfeito",
+ "Messages_cannot_be_unsent": "Não é possível desfazer o envio da mensagem",

4581-4592: LGTM – comandos de seleção consistentes.

Opcional: manter o artigo ausente (“Selecionar canal/agente/...”) como feito aqui para consistência.


5006-5009: Ajuste de estilo e naturalidade para “Template”.

  • “Template_message” fica mais natural como “Modelo de mensagem”.
  • Remover espaço desnecessário após o valor de “template”.
- "Template": "Modelo",
- "template": "modelo", 
- "Template_message": "Mensagem modelo",
+ "Template": "Modelo",
+ "template": "modelo",
+ "Template_message": "Modelo de mensagem",

3835-3845: Padronizar “Outbound” → “mensagem proativa” e substituir “admin” → “administrador”

Verificado em packages/i18n/src/locales/en.i18n.json — chaves correspondentes encontradas. Aplique a alteração sugerida:

- "Outbound_message": "Mensagem ativa",
- "Outbound_message_agent_hint": "Deixe em branco para que qualquer agente do departamento designado possa gerenciar as respostas.",
- "Outbound_message_agent_hint_no_permission": "Você não tem permissão para atribuir um agente. A resposta será atribuída ao departamento.",
- "Outbound_message_department_hint": "Atribuir respostas a um departamento.",
- "Outbound_message_not_sent": "Mensagem ativa não enviada.",
- "Outbound_message_sent_to__name__": "Mensagem ativa enviada para: {{name}}",
- "Outbound_message_upsell_alt": "Ilustração de um smartphone recebendo uma nova notificação de mensagem.",
- "Outbound_message_upsell_annotation": "Entre em contato com o admin do seu workspace para habilitar a mensagem ativa",
- "Outbound_message_upsell_description": "Envie mensagens ativas personalizadas no WhatsApp e em outros canais - ideal para lembretes, alertas e acompanhamentos.",
- "Outbound_message_upsell_title": "Dê o primeiro passo na conversa"
+ "Outbound_message": "Mensagem proativa",
+ "Outbound_message_agent_hint": "Deixe em branco para que qualquer agente do departamento designado possa gerenciar as respostas.",
+ "Outbound_message_agent_hint_no_permission": "Você não tem permissão para atribuir um agente. A resposta será atribuída ao departamento.",
+ "Outbound_message_department_hint": "Atribua respostas a um departamento.",
+ "Outbound_message_not_sent": "A mensagem proativa não foi enviada.",
+ "Outbound_message_sent_to__name__": "Mensagem proativa enviada para: {{name}}",
+ "Outbound_message_upsell_alt": "Ilustração de um smartphone recebendo uma nova notificação de mensagem.",
+ "Outbound_message_upsell_annotation": "Entre em contato com o administrador do seu workspace para habilitar mensagens proativas.",
+ "Outbound_message_upsell_description": "Envie mensagens proativas personalizadas no WhatsApp e em outros canais — ideal para lembretes, alertas e acompanhamentos.",
+ "Outbound_message_upsell_title": "Dê o primeiro passo na conversa"
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessagePreview/PreviewItem.tsx (1)

3-11: Minor a11y/type polish: mark icon decorative, group the labelled content, and use PropsWithChildren.

  • Silence SRs for the decorative icon.
  • Give the content container an explicit group role tied to the label.
  • Prefer PropsWithChildren over a manual children prop.

Apply:

-import type { ReactNode } from 'react';
+import type { PropsWithChildren } from 'react';
@@
-type PreviewItemProps = {
-	label: string;
-	icon: Keys;
-	children: ReactNode;
-};
+type PreviewItemProps = PropsWithChildren<{
+	label: string;
+	icon: Keys;
+}>;
@@
-			<Icon size={18} name={icon} />
+			<Icon size={18} name={icon} aria-hidden='true' />
@@
-				<div aria-labelledby={id}>
+				<div role='group' aria-labelledby={id}>

Please confirm the i18n key t('None') exists in the bundles used here so the fallback always renders as translated.

Also applies to: 19-19, 24-24

apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePreview.tsx (1)

41-44: Improve preview region semantics and avoid rendering empty blocks.

  • Mark the callout as a region for AT navigation.
  • Render body/footer only when present.
-			<Callout type='info' icon='balloon-text' title={content.header || ''} aria-labelledby={previewId}>
-				<MarkdownText parseEmoji content={content.body || ''} />
-				<MarkdownText parseEmoji color='annotation' content={content.footer || ''} />
+			<Callout type='info' icon='balloon-text' title={content.header || ''} aria-labelledby={previewId} role='region'>
+				{content.body ? <MarkdownText parseEmoji content={content.body} /> : null}
+				{content.footer ? <MarkdownText parseEmoji color='annotation' content={content.footer} /> : null}
 			</Callout>
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.spec.tsx (2)

26-30: Fix lint error: do not export from test files.

Biome flags this export. Keep the fixture local.

-export const template2 = createFakeOutboundTemplate({
+const template2 = createFakeOutboundTemplate({
 	id: 'template-2',
 	name: 'Template Two',
 	components: [component.header, component.body],
 });

16-18: Align header component shape with production types (add format: 'text').

Without format, header may be treated as non‑text (e.g., shown as “[Media]” in previews). Add explicit format to match real data.

-	header: { type: 'header', text: 'New {{1}} appointment' },
+	header: { type: 'header', format: 'text', text: 'New {{1}} appointment' },
packages/i18n/src/locales/en.i18n.json (6)

2923-2925: Punctuate time labels to match existing style

Align with “Last_message__date__”: add colons.

-  "Last_contact__time__": "Last contact {{time}}",
-  "Last_message_received__time__": "Last message received {{time}}",
+  "Last_contact__time__": "Last contact: {{time}}",
+  "Last_message_received__time__": "Last message received: {{time}}",

1158-1158: Pluralize “detail” labels

More natural English in UI.

-  "Contact_detail": "Contact detail",
+  "Contact_detail": "Contact details",
-  "Workspace_detail": "Workspace detail",
+  "Workspace_detail": "Workspace details",

Also applies to: 5777-5777


399-399: Confirm intent of new lowercase nouns duplicating existing capitalized labels

You’ve added “agent/contact/Custom_fields/department” alongside existing “Agent/Contact/Custom_Fields/Department”. If both are needed (e.g., sentence‑case vs title‑case), consider namespacing to avoid accidental misuse (e.g., Outbound_message_agent_label).

Also applies to: 1154-1154, 1514-1514, 1626-1626


5094-5097: Avoid duplicate concept keys: 'Template', 'template', 'Template_message'

These may confuse callers. Prefer one canonical label (e.g., 'Template'); reserve others only if semantics differ (e.g., 'Template_message' as content type).

If only the label is needed, remove the extra entry and update callers accordingly.


3365-3365: Tidy sentence and newline in error text

Consistent punctuation and avoid unnecessary newline in short toasts.

-  "Message_not_sent_try_again": "Message not sent. \nPlease try again",
+  "Message_not_sent_try_again": "Message not sent. Please try again.",

4012-4012: Overly generic key name

“Placeholder” is too generic and easy to misuse. Consider a scoped key (e.g., “Outbound_message_placeholder”) for maintainability.

apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/OutboundMessageModal.spec.tsx (2)

12-21: Add missing i18n key to make the test deterministic.

The title assertion relies on "Outbound message", but the mock app root doesn't provide the Outbound_message key. Add it to avoid depending on external/default locales.

 const appRoot = mockAppRoot()
 	.withTranslations('en', 'core', {
 		Close: 'Close',
 		Discard: 'Discard',
 		Discard_message: 'Discard message',
 		This_action_cannot_be_undone: 'This action cannot be undone',
 		Keep_editing: 'Keep editing',
 		Are_you_sure_you_want_to_discard_this_outbound_message: 'Are you sure you want to discard this outbound message?',
+		Outbound_message: 'Outbound message',
 	})
 	.build();

33-35: Make a11y description assertion resilient to copy changes.

Use a substring/regex match so minor text tweaks don’t break the test.

-	expect(screen.getByRole('dialog', { name: 'Discard message' })).toHaveAccessibleDescription(
-		'Are you sure you want to discard this outbound message?',
-	);
+	expect(screen.getByRole('dialog', { name: 'Discard message' })).toHaveAccessibleDescription(
+		expect.stringContaining('Are you sure you want to discard this outbound message?'),
+	);

(Apply in both places.)

Also applies to: 49-51

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/ReviewStep.spec.tsx (2)

35-43: Provide 'Back' translation to avoid locale bleed-through.

The spec queries the Back button by its label but doesn’t stub the translation. Add it to prevent false negatives in environments without default en resources.

 const appRoot = mockAppRoot()
 	.withJohnDoe()
 	.withTranslations('en', 'core', {
 		Send: 'Send',
+		Back: 'Back',
 	})

81-87: Optional: add an assertion for Back disabled while sending (if we disable it).

If we gate Back during send (see component suggestion), add a quick check here.

  render(<ReviewStep {...defaultProps} />, { wrapper: appRoot });
  const sendButton = screen.getByRole('button', { name: 'Send' });
  await userEvent.click(sendButton);
- await waitFor(() => expect(sendButton).toBeDisabled());
+ await waitFor(() => expect(sendButton).toBeDisabled());
+ const backButton = screen.getByRole('button', { name: 'Back' });
+ expect(backButton).toBeDisabled();

Also applies to: 99-117

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/ReviewStep.tsx (2)

16-17: Add a mutation key for easier debugging and DevTools grouping.

Not required, but helpful for observability and testability.

-	const sendMutation = useMutation({ mutationFn: onSend });
+	const sendMutation = useMutation({ mutationKey: ['outbound-message', 'send'], mutationFn: onSend });

24-29: Disable Back while sending to prevent mid-send navigation.

Prevents accidental state changes while the mutation is in-flight.

-				<WizardBackButton />
+				<WizardBackButton disabled={sendMutation.isPending} />
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.tsx (3)

34-35: Default to an empty array when templates for the sender are missing.

Avoid passing undefined to MessageStep when the selected sender has no templates.

-	const templates = sender ? provider?.templates[sender] : [];
+	const templates = sender ? provider?.templates[sender] ?? [] : [];

18-18: Fix naming typo for the skeleton component.

Improves readability and avoids confusion in future greps.

-import OutboubdMessageWizardSkeleton from './components/OutboundMessageWizardSkeleton';
+import OutboundMessageWizardSkeleton from './components/OutboundMessageWizardSkeleton';
...
-		return <OutboubdMessageWizardSkeleton />;
+		return <OutboundMessageWizardSkeleton />;

Also applies to: 141-142


66-70: Optional: consider upsell when Omnichannel module is missing.

You compute hasOmnichannelModule but don’t gate on it. If outbound messaging requires livechat-enterprise, include it in the upsell condition.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplateSelect.spec.tsx (4)

9-11: Header mock likely missing required “format” field

The header component in our mocks usually requires a format (e.g., 'text'). Add it to avoid TS/type issues when overriding components.

- header: { type: 'header', text: 'New {{1}} appointment' },
+ header: { type: 'header', format: 'text', text: 'New {{1}} appointment' },

49-59: Apply userEvent.setup() here as well

Mirror the pattern in this test to keep behavior consistent.

 it('should display the template language as the option description', async () => {
   render(<TemplateSelect templates={mockTemplates} placeholder='Select template' value='' onChange={jest.fn()} />, { wrapper: appRoot });
 
-  await userEvent.click(screen.getByPlaceholderText('Select template'));
+  const user = userEvent.setup();
+  await user.click(screen.getByPlaceholderText('Select template'));

31-60: Optional: add an interaction assertion

Consider a test that selects an option and asserts onChange is called with the expected value. This guards the wiring, not just rendering.


37-44: Use userEvent.setup() for reliability

yarn.lock resolves @testing-library/user-event to 14.5.2 — userEvent.setup() is supported. Replace the direct userEvent call in the test:

 it('should display the correct template options with language descriptions', async () => {
   render(<TemplateSelect templates={mockTemplates} placeholder='Select template' value='' onChange={jest.fn()} />, { wrapper: appRoot });
 
-  await userEvent.click(screen.getByPlaceholderText('Select template'));
+  const user = userEvent.setup();
+  await user.click(screen.getByPlaceholderText('Select template'));
apps/meteor/client/views/omnichannel/contactInfo/EditContactInfo.tsx (1)

190-191: Optional: use a single predicate invalidation for all contact keys.

If you prefer one call, predicate keeps intent explicit and avoids key-shape pitfalls.

Apply this alternative snippet instead of multiple calls:

-    await Promise.all([
-      queryClient.invalidateQueries({ queryKey: omnichannelQueryKeys.contact(contactData._id) }),
-      queryClient.invalidateQueries({ queryKey: [...omnichannelQueryKeys.all, 'contacts'], exact: false }),
-    ]);
+    await queryClient.invalidateQueries({
+      predicate: (q) =>
+        Array.isArray(q.queryKey) &&
+        q.queryKey[0] === 'omnichannel' &&
+        (q.queryKey[1] === 'contact' || q.queryKey[1] === 'contacts'),
+    });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36a76e9 and b7b0542.

⛔ Files ignored due to path filters (5)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/__snapshots__/MessageStep.spec.tsx.snap is excluded by !**/*.snap
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/__snapshots__/RecipientStep.spec.tsx.snap is excluded by !**/*.snap
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/__snapshots__/RepliesStep.spec.tsx.snap is excluded by !**/*.snap
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/__snapshots__/ReviewStep.spec.tsx.snap is excluded by !**/*.snap
  • apps/meteor/public/images/outbound-message-upsell.svg is excluded by !**/*.svg
📒 Files selected for processing (107)
  • .changeset/rich-rules-sleep.md (1 hunks)
  • apps/meteor/client/NavBarV2/NavBarPagesGroup/hooks/useCreateNewItems.tsx (3 hunks)
  • apps/meteor/client/components/AutoCompleteContact/AutoCompleteContact.tsx (1 hunks)
  • apps/meteor/client/components/AutoCompleteContact/index.ts (1 hunks)
  • apps/meteor/client/components/AutoCompleteContact/useContactsList.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/AutoCompleteDepartmentAgent.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/AutoCompleteDepartmentAgent.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/AutoCompleteOutboundProvider.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessagePreview/OutboundMessagePreview.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessagePreview/PreviewItem.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessagePreview/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.stories.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/components/OutboundMessageWizardErrorState.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/components/OutboundMessageWizardSkeleton.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/components/RetryButton.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplatePlaceholderField.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplatePreviewField.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/ChannelField.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/ContactField.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/RecipientField.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/SenderField.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/hooks/useFormKeyboardSubmit.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/MessageStep.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/MessageStep.stories.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/MessageStep.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RecipientStep.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RecipientStep.stories.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RecipientStep.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RepliesStep.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RepliesStep.stories.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RepliesStep.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/ReviewStep.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/ReviewStep.stories.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/ReviewStep.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/utils/cx.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/utils/errors.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/RecipientSelect.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/SenderSelect.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderButton.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderInput.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderInput.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderSelector.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/hooks/useAgentSection.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/hooks/useContactSection.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/hooks/useCustomFieldsSection.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePreview.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplateSelect.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplateSelect.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/definitions/template.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/hooks/useOutboundProvidersList.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/OutboundMessageCloseConfirmationModal.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/OutboundMessageModal.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/OutboundMessageModal.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/useOutboundMessageModal.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.stories.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/utils/findLastChatFromChannel.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/utils/outbound-message.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/utils/template.spec.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/utils/template.ts (1 hunks)
  • apps/meteor/client/lib/formatPhoneNumber.spec.ts (1 hunks)
  • apps/meteor/client/lib/formatPhoneNumber.ts (1 hunks)
  • apps/meteor/client/lib/queryKeys.ts (2 hunks)
  • apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomItems.tsx (3 hunks)
  • apps/meteor/client/views/omnichannel/components/Info.tsx (1 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/ContactInfo/ContactInfo.tsx (2 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/EditContactInfo.tsx (4 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannels.tsx (3 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx (2 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoDetails.tsx (2 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoDetailsEntry.tsx (1 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoDetailsGroup.tsx (0 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoOutboundMessageButton.tsx (1 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoPhoneEntry.tsx (1 hunks)
  • apps/meteor/ee/app/livechat-enterprise/server/outboundcomms/rest.ts (1 hunks)
  • apps/meteor/package.json (1 hunks)
  • apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts (1 hunks)
  • apps/meteor/tests/mocks/data.ts (2 hunks)
  • apps/meteor/tests/mocks/data/outbound-message.ts (1 hunks)
  • apps/uikit-playground/package.json (1 hunks)
  • ee/packages/license/src/v2/convertToV3.ts (1 hunks)
  • ee/packages/ui-theming/package.json (1 hunks)
  • packages/apps-engine/src/definition/outboundComunication/IOutboundMessage.ts (1 hunks)
  • packages/core-typings/src/omnichannel/outbound.ts (1 hunks)
  • packages/fuselage-ui-kit/package.json (1 hunks)
  • packages/gazzodown/package.json (1 hunks)
  • packages/i18n/src/locales/en.i18n.json (23 hunks)
  • packages/i18n/src/locales/pt-BR.i18n.json (15 hunks)
⛔ Files not processed due to max files limit (7)
  • packages/storybook-config/package.json
  • packages/ui-avatar/package.json
  • packages/ui-client/package.json
  • packages/ui-composer/package.json
  • packages/ui-video-conf/package.json
  • packages/ui-voip/package.json
  • packages/web-ui-registration/package.json
🔥 Files not summarized due to errors (1)
  • packages/i18n/src/locales/pt-BR.i18n.json: Error: Server error: no LLM provider could handle the message
💤 Files with no reviewable changes (1)
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoDetailsGroup.tsx
🚧 Files skipped from review as they are similar to previous changes (91)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessagePreview/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RepliesStep.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/SenderSelect.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderSelector.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplatePreviewField.tsx
  • apps/uikit-playground/package.json
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm.spec.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderInput.spec.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm.spec.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.stories.tsx
  • .changeset/rich-rules-sleep.md
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/hooks/useFormKeyboardSubmit.tsx
  • apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts
  • apps/meteor/tests/mocks/data/outbound-message.ts
  • apps/meteor/package.json
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/components/OutboundMessageWizardSkeleton.tsx
  • ee/packages/license/src/v2/convertToV3.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RecipientStep.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/OutboundMessageCloseConfirmationModal.tsx
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoOutboundMessageButton.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RecipientStep.spec.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.spec.tsx
  • apps/meteor/client/components/AutoCompleteContact/AutoCompleteContact.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/MessageStep.spec.tsx
  • apps/meteor/tests/mocks/data.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/RecipientSelect.tsx
  • apps/meteor/client/lib/queryKeys.ts
  • apps/meteor/client/components/AutoCompleteContact/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/utils/outbound-message.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/ReviewStep.stories.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/components/OutboundMessageWizardErrorState.tsx
  • packages/gazzodown/package.json
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/SenderField.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/useOutboundMessageModal.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/AutoCompleteOutboundProvider.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/hooks/useContactSection.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/utils/errors.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/components/RetryButton.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderButton.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/utils/template.spec.ts
  • apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomItems.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/AutoCompleteDepartmentAgent.spec.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/AutoCompleteDepartmentAgent.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/hooks/useOutboundProvidersList.ts
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannels.tsx
  • apps/meteor/ee/app/livechat-enterprise/server/outboundcomms/rest.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RecipientStep.stories.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/hooks/useCustomFieldsSection.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx
  • apps/meteor/client/lib/formatPhoneNumber.spec.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/MessageStep.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessagePreview/OutboundMessagePreview.tsx
  • packages/core-typings/src/omnichannel/outbound.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplateSelect.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.stories.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplatePlaceholderField.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/utils/cx.ts
  • apps/meteor/client/NavBarV2/NavBarPagesGroup/hooks/useCreateNewItems.tsx
  • apps/meteor/client/components/AutoCompleteContact/useContactsList.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RepliesStep.stories.tsx
  • apps/meteor/client/views/omnichannel/contactInfo/ContactInfo/ContactInfo.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/RepliesStep.spec.tsx
  • packages/apps-engine/src/definition/outboundComunication/IOutboundMessage.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/ContactField.tsx
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoDetails.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/ChannelField.tsx
  • apps/meteor/client/views/omnichannel/components/Info.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.spec.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/definitions/template.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/MessageStep.stories.tsx
  • packages/fuselage-ui-kit/package.json
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoDetailsEntry.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/hooks/useAgentSection.tsx
  • apps/meteor/client/lib/formatPhoneNumber.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/OutboundMessageModal.tsx
  • ee/packages/ui-theming/package.json
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/index.ts
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderInput.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/utils/template.ts
🧰 Additional context used
🧬 Code graph analysis (9)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/ReviewStep.spec.tsx (2)
packages/mock-providers/src/index.ts (1)
  • mockAppRoot (3-3)
packages/ui-client/src/components/Wizard/WizardContext.tsx (1)
  • WizardContext (17-17)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/RecipientField.tsx (1)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.tsx (1)
  • RecipientFormData (20-25)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePreview.tsx (2)
apps/meteor/client/components/Omnichannel/OutboundMessage/definitions/template.ts (2)
  • TemplateParameters (10-10)
  • ComponentType (3-3)
apps/meteor/client/components/Omnichannel/OutboundMessage/utils/template.ts (1)
  • processTemplatePreviewText (65-86)
apps/meteor/client/views/omnichannel/contactInfo/EditContactInfo.tsx (1)
apps/meteor/client/lib/queryKeys.ts (1)
  • omnichannelQueryKeys (30-81)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.spec.tsx (3)
apps/meteor/tests/mocks/data/outbound-message.ts (1)
  • createFakeOutboundTemplate (25-64)
apps/meteor/tests/mocks/data.ts (1)
  • createFakeContactWithManagerData (352-366)
packages/mock-providers/src/index.ts (1)
  • mockAppRoot (3-3)
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/OutboundMessageModal.spec.tsx (1)
packages/mock-providers/src/index.ts (1)
  • mockAppRoot (3-3)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.tsx (6)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/index.ts (1)
  • SubmitPayload (5-5)
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx (1)
  • useOutboundMessageUpsellModal (9-21)
apps/meteor/client/hooks/useEndpointMutation.ts (1)
  • useEndpointMutation (23-43)
packages/ui-client/src/components/Wizard/useWizard.tsx (1)
  • useWizard (20-101)
apps/meteor/client/components/Omnichannel/OutboundMessage/utils/outbound-message.ts (4)
  • isRecipientStepValid (14-16)
  • isMessageStepValid (18-20)
  • isRepliesStepValid (22-24)
  • formatOutboundMessagePayload (48-79)
apps/meteor/client/lib/formatPhoneNumber.ts (1)
  • formatPhoneNumber (24-36)
apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx (1)
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/useOutboundMessageModal.tsx (1)
  • useOutboundMessageModal (8-18)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplateSelect.spec.tsx (2)
apps/meteor/tests/mocks/data/outbound-message.ts (1)
  • createFakeOutboundTemplate (25-64)
packages/mock-providers/src/index.ts (1)
  • mockAppRoot (3-3)
🪛 Biome (2.1.2)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.spec.tsx

[error] 25-30: Do not export from a test file.

(lint/suspicious/noExportsInTest)

apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplateSelect.spec.tsx

[error] 19-25: Do not export from a test file.

(lint/suspicious/noExportsInTest)

apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoPhoneEntry.tsx

[error] 28-28: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 🔎 Code Check / Code Lint
  • GitHub Check: 🔨 Test Storybook / Test Storybook
  • GitHub Check: 📦 Meteor Build - coverage
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (24)
apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoChannels/ContactInfoChannelsItem.tsx (4)

6-6: Imports look good.

useMemo is appropriately added and used; useState is already in use.


15-18: Prop additions are sensible; clarify invariants.

contactId? and canSendOutboundMessage? make sense. Invariants assumed later: opening the modal requires both contactId and details.id. We gate this below, but ensure callers set canSendOutboundMessage only when both exist.


20-27: Component signature changes LGTM.

Destructuring the new props is consistent with the type update.


58-59: i18n key exists — no action required.
Outbound_message is defined in packages/i18n/src/locales/en.i18n.json and packages/i18n/src/locales/pt-BR.i18n.json.

apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoPhoneEntry.tsx (1)

27-30: Confirm which phone format to pass to call/outbound buttons

UI renders a formatted value but this snippet passes the raw value — verify which form the downstream components expect (display-formatted vs dialable/E.164) and align accordingly.

File: apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoPhoneEntry.tsx (around lines 27-30)

				<IconButton key={`${value}-copy`} onClick={() => copy()} tiny icon='copy' title={t('Copy')} />,
				isCallReady ? <ContactInfoCallButton phoneNumber={value} /> : null,
				<ContactInfoOutboundMessageButton key={`${value}-outbound-message`} defaultValues={{ contactId, recipient: value }} />,
  • Check ContactInfoCallButton: inspect the phoneNumber prop/type and its downstream dialer integration — if it requires dialable/E.164, normalize before passing.
  • Check ContactInfoOutboundMessageButton: inspect defaultValues.recipient expectation — normalize if it requires E.164, otherwise pass formattedValue.
  • If a phone-normalization util exists (toE164/normalizePhoneNumber/etc.), use it here; add one if missing.
packages/i18n/src/locales/pt-BR.i18n.json (8)

1703-1703: LGTM – tradução correta e direta.


1778-1778: LGTM – placeholders corretos e mensagem clara.


2656-2656: LGTM – frase natural no contexto de edição.


2877-2877: LGTM – placeholder e ordem natural (“Última mensagem recebida {{time}}”).


4138-4138: LGTM – “Destinatário” adequado ao contexto.


5718-5718: LGTM – mensagem padrão de permissão consistente com o restante do arquivo.


6346-6346: LGTM – termo comum em toda a UI.


5683-5683: Verificar chave "Workspace_detail" — confirmada

Encontrada em en‑US (linha indicada na saída: 5777) e em pt‑BR (packages/i18n/src/locales/pt-BR.i18n.json:5683). Chave é singular em ambos — sem divergência.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplatePreview.tsx (1)

42-43: Confirm Markdown sanitization to prevent XSS in previews.

Templates may contain user‑entered text. Please confirm MarkdownText sanitizes HTML/URLs and that dangerous markdown is stripped in preview mode. If not, enable sanitization or pass the appropriate props.

apps/meteor/client/components/Omnichannel/OutboundMessage/utils/findLastChatFromChannel.ts (1)

3-5: Add explicit return type and confirm channel discriminator (name vs providerId)

  • Make the function's return type explicit to aid callers/TS. Suggested change:
-export const findLastChatFromChannel = (channels: Serialized<ILivechatContactChannel>[] = [], providerId: string) => {
+export const findLastChatFromChannel = (
+	channels: Serialized<ILivechatContactChannel>[] = [],
+	providerId: string,
+): Date | string | undefined => {
 	const channel = channels.find((channel) => channel.name === providerId);
 	return channel?.lastChat?.ts;
 };
  • Verify that ILivechatContactChannel actually uses name as the provider identifier (it may be providerId or a nested field); ILivechatContactChannel typings were not found in this checkout — confirm the correct discriminator and the exact type of lastChat.ts, then update the return type accordingly.
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/OutboundMessageModal.spec.tsx (2)

7-10: Confirm mocked import path matches the component’s import.

If OutboundMessageModal.tsx imports the wizard via a different relative path (index vs. file path), this mock won’t intercept it. Please verify path parity.


23-40: Close-confirmation flow test reads well.

Good coverage of open → confirm → invoke onClose. No changes needed.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/ReviewStep.spec.tsx (1)

81-87: Tests are comprehensive and exercise async states.

Good use of waitFor, pending/reject flows, and wizard navigation.

Also applies to: 89-97, 99-117, 119-137

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/steps/ReviewStep.tsx (1)

27-29: Verify react-query version: isPending vs isLoading.

If the repo is on @tanstack/react-query v4, isPending doesn’t exist (use isLoading). Confirm version alignment to avoid runtime/typing errors.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.tsx (1)

81-128: Success/error flows look solid.

Step validation, payload formatting, toast messages, and debug logging are consistent.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/TemplateSelect.spec.tsx (1)

32-35: Baseline render test: looks good

Placeholder assertion is a simple, effective smoke test for initial render.

apps/meteor/client/views/omnichannel/contactInfo/EditContactInfo.tsx (3)

5-5: Import looks good.

Adding useQueryClient is appropriate for post-mutation cache management.


27-27: Right source of truth for keys.

Importing omnichannelQueryKeys is correct and keeps invalidations consistent.


81-81: LGTM: single queryClient instance.

Correct place and usage within the component.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
.changeset/rich-rules-sleep.md (1)

20-20: Polish release note and surface gating details.

Add punctuation and briefly mention the permission gate to aid consumers scanning the changelog.

-Introduces the Outbound Message feature to Omnichannel, allowing organizations to initiate proactive communication with contacts through their preferred messaging channel directly from Rocket.Chat
+Introduces the Omnichannel Outbound Message feature, enabling proactive communication with contacts via their preferred messaging channel directly from Rocket.Chat.
+Requires permission: `outbound.send-messages`.
apps/meteor/client/views/omnichannel/contactInfo/EditContactInfo.tsx (1)

180-180: Drop unnecessary optional chaining on guarded id.

Inside if (contactData), contactData is truthy; use contactData._id to avoid an accidental undefined.

- await editContact.mutateAsync({ contactId: contactData?._id, ...payload });
+ await editContact.mutateAsync({ contactId: contactData._id, ...payload });
apps/meteor/client/lib/queryKeys.ts (2)

76-78: Ensure const‑tuple typing for both branches.

The contacts builder lacks as const on the no‑arg branch, weakening type safety.

- contacts: (query?: { filter: string; limit?: number }) =>
-   !query ? [...omnichannelQueryKeys.all, 'contacts'] : ([...omnichannelQueryKeys.all, 'contacts', query] as const),
+ contacts: (query?: { filter: string; limit?: number }) =>
+   !query ? ([...omnichannelQueryKeys.all, 'contacts'] as const) : ([...omnichannelQueryKeys.all, 'contacts', query] as const),

78-78: Avoid emitting undefined in query keys.

contact(contactId?: string) can append undefined to the key. Prefer requiring an id (or branch like other builders).

- contact: (contactId?: string) => [...omnichannelQueryKeys.contacts(), contactId] as const,
+ // Option A (preferred): require id
+ contact: (contactId: string) => [...omnichannelQueryKeys.contacts(), contactId] as const,
+ // Option B: branch by presence
+ // contact: (contactId?: string) =>
+ //   contactId ? ([...omnichannelQueryKeys.contacts(), contactId] as const) : ([...omnichannelQueryKeys.contacts()] as const),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7b0542 and f79c74f.

📒 Files selected for processing (4)
  • .changeset/rich-rules-sleep.md (1 hunks)
  • apps/meteor/client/lib/queryKeys.ts (2 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/EditContactInfo.tsx (4 hunks)
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoPhoneEntry.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/client/views/omnichannel/contactInfo/tabs/ContactInfoDetails/ContactInfoPhoneEntry.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
apps/meteor/client/views/omnichannel/contactInfo/EditContactInfo.tsx (1)
apps/meteor/client/lib/queryKeys.ts (1)
  • omnichannelQueryKeys (30-82)
apps/meteor/client/lib/queryKeys.ts (1)
packages/core-typings/src/omnichannel/outbound.ts (1)
  • IOutboundProvider (134-139)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 🔨 Test Storybook / Test Storybook
  • GitHub Check: 🔎 Code Check / Code Lint
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 📦 Meteor Build - coverage
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (5)
.changeset/rich-rules-sleep.md (2)

1-18: Changeset front‑matter structure looks valid.

YAML block and second --- separator are correctly formed for a multi‑package changeset. Please just confirm SemVer intent for each bump, especially for @rocket.chat/core-typings and @rocket.chat/apps-engine where non‑backward‑compatible type changes would require a major bump.


2-17: Verify package coverage and bump levels align with actual changes — rerun diagnostic

The prior diagnostic failed (awk / conditional errors). Run the corrected script below and paste its output; then ensure every changed workspace is listed in the changeset and that UI-only/internal copy changes are "patch" while public API/typings changes are "minor".

#!/usr/bin/env bash
set -euo pipefail

CS_FILE=".changeset/rich-rules-sleep.md"
[[ -f "$CS_FILE" ]] || { echo "ERROR: $CS_FILE not found"; exit 0; }

# parse changeset (name|bump)
grep -E "^'[^']+':" "$CS_FILE" | sed -E "s/^'([^']+)':[[:space:]]*([a-zA-Z]+)/\1|\2/" > /tmp/changeset_pkgs.txt || true
echo "Packages listed in this changeset:"
cat /tmp/changeset_pkgs.txt || true

BASE_REF=$(git merge-base origin/develop HEAD 2>/dev/null || true)
if [[ -n "$BASE_REF" ]]; then RANGE="$BASE_REF...HEAD"; else RANGE="HEAD"; fi

git diff --name-only "$RANGE" | grep -E '^(apps|packages)/[^/]+' | sed -E 's|^((apps|packages)/[^/]+)/.*|\1|' | sort -u > /tmp/changed_ws.txt || true
echo
echo "Workspaces with files changed in this PR:"
cat /tmp/changed_ws.txt || true

> /tmp/changed_pkg_names.txt
while IFS= read -r ws; do
  [[ -z "$ws" ]] && continue
  pkg_json=$(find "$ws" -maxdepth 3 -name package.json -print -quit 2>/dev/null || true)
  if [[ -f "$pkg_json" ]]; then
    name=$(jq -r '.name // empty' "$pkg_json" 2>/dev/null || true)
    echo "$ws -> $name"
    [[ -n "$name" ]] && echo "$name" >> /tmp/changed_pkg_names.txt
  else
    echo "$ws -> (no package.json found)"
  fi
done < /tmp/changed_ws.txt || true

echo
echo "Changed package names (from package.json):"
sort -u /tmp/changed_pkg_names.txt || true

echo
echo "Changed-but-not-in-changeset:"
comm -23 <(sort -u /tmp/changed_pkg_names.txt) <(sort -u /tmp/changeset_pkgs.txt) || true

echo
echo "In-changeset-but-not-changed:"
comm -13 <(sort -u /tmp/changed_pkg_names.txt) <(sort -u /tmp/changeset_pkgs.txt) || true

If the script shows any entries under "Changed-but-not-in-changeset", add them to the .changeset; if any appear under "In-changeset-but-not-changed", remove them or adjust bump level.

apps/meteor/client/views/omnichannel/contactInfo/EditContactInfo.tsx (2)

180-186: Good fix: mutateAsync + prefix-key invalidation.

Using mutateAsync keeps isSubmitting accurate, and invalidating omnichannelQueryKeys.contacts() will refresh both lists and per-contact queries that share the prefix. This addresses the earlier UX/cache concerns.


5-5: LGTM: react-query integration.

Importing useQueryClient, centralizing keys via omnichannelQueryKeys, and instantiating queryClient locally is clean and consistent.

Also applies to: 27-27, 81-81

apps/meteor/client/lib/queryKeys.ts (1)

79-81: Outbound provider keys: LGTM.

Key shapes look consistent and specific enough for targeted invalidation/refetch.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/meteor/client/components/Omnichannel/OutboundMessage/constants.ts (3)

1-2: Avoid scattering hard-coded external URLs; prefer a single source of truth.
Consider grouping these under one exported map to ease future changes and reduce import churn:

export const OUTBOUND_LINKS = {
  docs: OUTBOUND_DOCS_LINK,
  contactSales: CONTACT_SALES_LINK,
} as const;

I can help extract or consolidate other existing RC external links into this module if desired.


1-2: Ensure consumers open external links safely.
Where these constants are used with target="_blank", also set rel="noopener noreferrer" (or use a shared ExternalLink component) to prevent tabnabbing.


1-2: *Prefer _URL suffix — rename these constants for consistency.
Repo search shows 17 *_URL vs 10 *_LINK; rename OUTBOUND_DOCS_LINK → OUTBOUND_DOCS_URL and CONTACT_SALES_LINK → CONTACT_SALES_URL in apps/meteor/client/components/Omnichannel/OutboundMessage/constants.ts (lines 1–2).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0f847 and 78a076a.

📒 Files selected for processing (4)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/constants.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/OutboundMessageModal.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageModal/OutboundMessageModal.spec.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: Builds matrix rust bindings against alpine
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (2)
apps/meteor/client/components/Omnichannel/OutboundMessage/constants.ts (2)

1-2: LGTM: extracting external links into constants improves reuse.


1-2: Links validated — both URLs resolve successfully (HTTP 200).
docs.rocket.chat returns 200 at https://docs.rocket.chat/docs/p2p-outbound-messaging; go.rocket.chat redirects to https://www.rocket.chat/sales-contact (final) and returns 200. No 404s or redirect loops found.

@aleksandernsilva aleksandernsilva added the stat: QA assured Means it has been tested and approved by a company insider label Sep 16, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Sep 16, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/i18n/src/locales/pt-BR.i18n.json (1)

1144-1149: Duplicate “contact” key resolved — thank you.

A duplicidade sinalizada anteriormente foi removida; permanece apenas “contact”: “contato”. Nada mais a fazer.

🧹 Nitpick comments (3)
packages/i18n/src/locales/pt-BR.i18n.json (3)

2877-2877: Add missing colon after label.

Fica mais natural e consistente com “Última mensagem: {{date}}”.

Apply this diff:

-"Last_message_received__time__": "Última mensagem recebida {{time}}",
+"Last_message_received__time__": "Última mensagem recebida: {{time}}",

3835-3845: Outbound message strings read well; one terminology tweak.

Padronizar “admin” → “administrador” para seguir o restante do arquivo.

Apply this diff:

-"Outbound_message_upsell_annotation": "Entre em contato com o admin do seu workspace para habilitar a mensagem ativa",
+"Outbound_message_upsell_annotation": "Entre em contato com o administrador do seu workspace para habilitar a mensagem ativa",

5006-5009: Naturalness nit: “Template_message”.

“Mensagem do modelo” soa mais natural em pt‑BR.

Apply this diff:

-"Template_message": "Mensagem modelo",
+"Template_message": "Mensagem do modelo",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4b9b1b8 and b6fcc40.

📒 Files selected for processing (2)
  • packages/i18n/src/locales/en.i18n.json (23 hunks)
  • packages/i18n/src/locales/pt-BR.i18n.json (15 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/i18n/src/locales/en.i18n.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (8)
packages/i18n/src/locales/pt-BR.i18n.json (8)

671-671: Wording OK for discard confirmation.

Tradução consistente com “Mensagem ativa”. Nada a ajustar aqui.


1703-1703: LGTM: “Discard_message”.

Texto curto e claro.


1978-1978: LGTM: placeholder preservado.

“{{name}}” mantido corretamente.


3656-3656: LGTM: “Keep_editing”.

Boa escolha verbal (“Continuar editando”).


4138-4138: LGTM: selection/recipient keys.

Terminologia consistente (Destinatário/Selecionar ...).

Also applies to: 4581-4592


4646-4646: LGTM: “Message_preview”.

Termo adequado para UI.


5684-5684: LGTM: “Workspace_detail”.

Coerente com demais entradas.


5718-5718: LGTM: authorization message.

Clara e direta.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants