diff --git a/.changeset/popular-games-dress.md b/.changeset/popular-games-dress.md new file mode 100644 index 0000000000000..2f6cfa4a356ea --- /dev/null +++ b/.changeset/popular-games-dress.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/livechat": minor +--- + +#### Adds support for hiding the "Expand chat" button in the Livechat widget. +This can be configured via the widget API by passing the `hideExpandChat` option to the `setTheme` method, or through the Livechat Appearance settings page by enabling the "Hide 'Expand chat'" option. diff --git a/apps/meteor/app/livechat/imports/server/rest/appearance.ts b/apps/meteor/app/livechat/imports/server/rest/appearance.ts index 126c93d5fc93c..85ef01e66eda7 100644 --- a/apps/meteor/app/livechat/imports/server/rest/appearance.ts +++ b/apps/meteor/app/livechat/imports/server/rest/appearance.ts @@ -53,6 +53,7 @@ API.v1.addRoute( 'Livechat_widget_position', 'Livechat_hide_system_messages', 'Omnichannel_allow_visitors_to_close_conversation', + 'Livechat_hide_expand_chat', ]; const valid = settings.every((setting) => validSettingList.includes(setting._id)); diff --git a/apps/meteor/app/livechat/server/api/lib/appearance.ts b/apps/meteor/app/livechat/server/api/lib/appearance.ts index 0fc7d3547b2c3..c93db42911838 100644 --- a/apps/meteor/app/livechat/server/api/lib/appearance.ts +++ b/apps/meteor/app/livechat/server/api/lib/appearance.ts @@ -29,6 +29,7 @@ export async function findAppearance(): Promise<{ appearance: ISetting[] }> { 'Livechat_widget_position', 'Livechat_hide_system_messages', 'Omnichannel_allow_visitors_to_close_conversation', + 'Livechat_hide_expand_chat', ], }, }; diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index e19a703624c9a..ed29cd5c86c80 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -146,6 +146,7 @@ export async function settings({ businessUnit = '', userId }: { businessUnit?: s offlineColor: initSettings.Livechat_offline_title_color, position: initSettings.Livechat_widget_position || 'right', background: initSettings.Livechat_background, + hideExpandChat: initSettings.Livechat_hide_expand_chat, actionLinks: { webrtc: [ { diff --git a/apps/meteor/app/livechat/server/lib/settings.ts b/apps/meteor/app/livechat/server/lib/settings.ts index 4bc6e90f19b4f..f4a917b966d7a 100644 --- a/apps/meteor/app/livechat/server/lib/settings.ts +++ b/apps/meteor/app/livechat/server/lib/settings.ts @@ -44,6 +44,7 @@ export async function getInitSettings() { 'Assets_livechat_widget_logo', 'Livechat_hide_watermark', 'Omnichannel_allow_visitors_to_close_conversation', + 'Livechat_hide_expand_chat', ] as const; type SettingTypes = (typeof validSettings)[number] | 'Livechat_Show_Connecting'; diff --git a/apps/meteor/client/views/omnichannel/appearance/AppearanceFieldLabel.tsx b/apps/meteor/client/views/omnichannel/appearance/AppearanceFieldLabel.tsx index 3c681eb6f5a48..b56c1fb67831b 100644 --- a/apps/meteor/client/views/omnichannel/appearance/AppearanceFieldLabel.tsx +++ b/apps/meteor/client/views/omnichannel/appearance/AppearanceFieldLabel.tsx @@ -9,17 +9,17 @@ type AppearanceFieldLabelProps = ComponentProps & { children: string; }; -const AppearanceFieldLabel = ({ children, premium = false }: AppearanceFieldLabelProps) => { +const AppearanceFieldLabel = ({ children, premium = false, ...props }: AppearanceFieldLabelProps) => { const { t } = useTranslation(); const hasLicense = useHasLicenseModule('livechat-enterprise'); const shouldDisableEnterprise = premium && !hasLicense; if (!shouldDisableEnterprise) { - return {children}; + return {children}; } return ( - + {children} diff --git a/apps/meteor/client/views/omnichannel/appearance/AppearanceForm.tsx b/apps/meteor/client/views/omnichannel/appearance/AppearanceForm.tsx index 55ece7a967ab1..17f5c9ab666d2 100644 --- a/apps/meteor/client/views/omnichannel/appearance/AppearanceForm.tsx +++ b/apps/meteor/client/views/omnichannel/appearance/AppearanceForm.tsx @@ -52,6 +52,7 @@ const AppearanceForm = () => { const livechatBackgroundField = useId(); const livechatHideSystemMessagesField = useId(); const omnichannelVisitorsCanCloseConversationField = useId(); + const livechatHideExpandChat = useId(); return ( @@ -153,6 +154,17 @@ const AppearanceForm = () => { /> + + + {t('Livechat_hide_expand_chat')} + } + /> + + {t('Livechat_hide_expand_chat_description')} + diff --git a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts index 0c54c8d0cfe91..6712cfa1bbdf9 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts @@ -329,4 +329,16 @@ export const createSettings = async (): Promise => { key: 'Load_Rotation', i18nLabel: 'Load_Rotation', }); + + await settingsRegistry.add('Livechat_hide_expand_chat', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Livechat', + i18nDescription: 'Livechat_hide_expand_chat_description', + invalidValue: false, + modules: ['livechat-enterprise'], + enterprise: false, + public: false, + enableQuery: omnichannelEnabledQuery, + }); }; diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts index 76bb10f9f7d8b..c9bf2490d10ee 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts @@ -39,7 +39,14 @@ declare const window: Window & { setGuestName: (name: string) => void; setGuestToken: (token: string) => void; setParentUrl: (url: string) => void; - setTheme: (theme: { color?: string; fontColor?: string; iconColor?: string; title?: string; offlineTitle?: string }) => void; + setTheme: (theme: { + color?: string; + fontColor?: string; + iconColor?: string; + title?: string; + offlineTitle?: string; + hideExpandChat?: boolean; + }) => void; setLanguage: (language: string) => void; transferChat: (department: string) => void; onChatMaximized: (callback: () => void) => void; @@ -167,6 +174,20 @@ test.describe('OC - Livechat API', () => { await expect(page.frameLocator('#rocketchat-iframe').locator('header')).toHaveCSS('color', 'rgb(50, 50, 50)'); }); + await test.step('expect setTheme set hideExpandChat', async () => { + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget()); + + await expect(poLiveChat.btnExpandChat).toBeVisible(); + + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.setTheme({ hideExpandChat: true })); + + await expect(poLiveChat.btnExpandChat).not.toBeVisible(); + + await poLiveChat.page.evaluate(() => window.RocketChat.livechat.setTheme({ hideExpandChat: false })); + + await expect(poLiveChat.btnExpandChat).toBeVisible(); + }); + // TODO: fix iconColor setTheme property // await test.step('Expect setTheme set iconColor', async () => { // await poLiveChat.page.evaluate(() => { diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-hide-expand-chat.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-hide-expand-chat.spec.ts new file mode 100644 index 0000000000000..2e6640c1b435c --- /dev/null +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-hide-expand-chat.spec.ts @@ -0,0 +1,67 @@ +import { IS_EE } from '../config/constants'; +import { createAuxContext } from '../fixtures/createAuxContext'; +import { Users } from '../fixtures/userStates'; +import { OmnichannelLiveChat } from '../page-objects'; +import { OmnichannelLivechatAppearance } from '../page-objects/omnichannel-livechat-appearance'; +import { createAgent, makeAgentAvailable } from '../utils/omnichannel/agents'; +import { test, expect } from '../utils/test'; + +test.skip(!IS_EE, 'Enterprise Only'); + +test.use({ storageState: Users.admin.state }); + +test.describe('OC - Livechat - Hide "Expand chat"', async () => { + let agent: Awaited>; + let poLiveChat: OmnichannelLiveChat; + let poLivechatAppearance: OmnichannelLivechatAppearance; + + test.beforeAll(async ({ api }) => { + agent = await createAgent(api, 'user1'); + + const res = await makeAgentAvailable(api, agent.data._id); + + await expect(res.status()).toBe(200); + }); + + test.beforeEach(async ({ browser, api }) => { + const { page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false); + + poLiveChat = new OmnichannelLiveChat(livechatPage, api); + }); + + test.afterEach(async () => { + await poLiveChat.page.close(); + }); + + test.beforeEach(async ({ page }) => { + poLivechatAppearance = new OmnichannelLivechatAppearance(page); + + await page.goto('/omnichannel/appearance'); + }); + + test.afterAll(async ({ api }) => { + const res = await api.post('/settings/Livechat_hide_expand_chat', { value: false }); + await expect(res.status()).toBe(200); + }); + + test('OC - Livechat - Hide "Expand chat"', async () => { + await test.step('expect to open Livechat', async () => { + await poLiveChat.openLiveChat(); + }); + + await test.step('expect "Expand chat" button to start visible (default)', async () => { + await expect(poLiveChat.btnExpandChat).toBeVisible(); + }); + + await test.step('expect to change setting', async () => { + await poLivechatAppearance.labelHideExpandChat.click(); + await poLivechatAppearance.btnSave.click(); + }); + + await test.step('expect "Expand chat" button to be hidden', async () => { + await poLiveChat.page.reload(); + await poLiveChat.openLiveChat(); + await expect(poLiveChat.btnExpandChat).toBeHidden(); + }); + }); +}); diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-appearance.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-appearance.ts index d20c5ca10a650..728eea5d54d92 100644 --- a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-appearance.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-appearance.ts @@ -15,6 +15,14 @@ export class OmnichannelLivechatAppearance extends OmnichannelAdministration { return this.page.locator('[name="Livechat_title"]'); } + get inputHideExpandChat(): Locator { + return this.page.getByRole('checkbox', { name: 'Hide "Expand chat"' }); + } + + get labelHideExpandChat(): Locator { + return this.page.locator('label', { has: this.inputHideExpandChat }); + } + findHideSystemMessageOption(option: string): Locator { return this.page.locator(`[role="option"][value="${option}"]`); } diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts index 3990d5fce7bbb..0b69b433007d3 100644 --- a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts @@ -119,6 +119,10 @@ export class OmnichannelLiveChatEmbedded { return this.page.frameLocator('#rocketchat-iframe').locator('footer div div div:nth-child(3) button'); } + get btnExpandChat(): Locator { + return this.page.frameLocator('#rocketchat-iframe').getByRole('button', { name: 'Expand chat', exact: true }); + } + get firstAutoMessage(): Locator { return this.page.frameLocator('#rocketchat-iframe').locator('div.message-text__WwYco p'); } diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts index b8cf77d63f716..5fcf966bd0f91 100644 --- a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts @@ -42,6 +42,10 @@ export class OmnichannelLiveChat { return this.page.locator(`button >> text="Yes"`); } + get btnExpandChat(): Locator { + return this.page.getByRole('button', { name: 'Expand chat' }); + } + get txtHeaderTitle(): Locator { return this.page.locator('div >> text="Chat Finished"'); } diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 5830282ebff0a..c73e7bc322618 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -3011,6 +3011,8 @@ "Livechat_forward_open_chats_timeout": "Timeout (in seconds) to forward chats", "Livechat_guest_count": "Guest Counter", "Livechat_hide_system_messages": "Hide system messages", + "Livechat_hide_expand_chat": "Hide \"Expand chat\"", + "Livechat_hide_expand_chat_description": "Remove the \"Expand chat\" button from the widget", "Livechat_hide_watermark": "Hide \"powered by Rocket.Chat\"", "Livechat_hide_watermark_description": "Remove the Rocket.Chat logo from the widget", "Livechat_last_chatted_agent_routing": "Last-Chatted Agent Preferred", diff --git a/packages/livechat/src/components/Screen/Header.tsx b/packages/livechat/src/components/Screen/Header.tsx index a2080382dc91a..098b544f223b5 100644 --- a/packages/livechat/src/components/Screen/Header.tsx +++ b/packages/livechat/src/components/Screen/Header.tsx @@ -14,7 +14,7 @@ import Header from '../Header'; import Tooltip from '../Tooltip'; import type { ScreenContextValue } from './ScreenProvider'; -type screenHeaderProps = { +type ScreenHeaderProps = { alerts: { id: string; children: ComponentChildren; [key: string]: unknown }[]; agent: Agent; notificationsEnabled: boolean; @@ -31,6 +31,7 @@ type screenHeaderProps = { spot: number; }; title: string; + hideExpandChat: boolean; }; const ScreenHeader = ({ @@ -48,7 +49,8 @@ const ScreenHeader = ({ onOpenWindow, queueInfo, title, -}: screenHeaderProps) => { + hideExpandChat, +}: ScreenHeaderProps) => { const { t } = useTranslation(); const headerRef = useRef(null); @@ -114,7 +116,7 @@ const ScreenHeader = ({ )} - {!expanded && !windowed && ( + {!hideExpandChat && !expanded && !windowed && ( diff --git a/packages/livechat/src/components/Screen/ScreenProvider.tsx b/packages/livechat/src/components/Screen/ScreenProvider.tsx index 49d3fc4dec918..28e5009888d76 100644 --- a/packages/livechat/src/components/Screen/ScreenProvider.tsx +++ b/packages/livechat/src/components/Screen/ScreenProvider.tsx @@ -40,6 +40,7 @@ export type ScreenContextValue = { background?: string; hideGuestAvatar?: boolean; hideAgentAvatar?: boolean; + hideExpandChat?: boolean; }; }; @@ -50,6 +51,7 @@ export const ScreenContext = createContext({ iconColor: '', hideAgentAvatar: false, hideGuestAvatar: true, + hideExpandChat: false, }, notificationsEnabled: true, minimized: true, @@ -65,7 +67,7 @@ export const ScreenProvider: FunctionalComponent = ({ children }) => { const store = useContext(StoreContext); const { token, dispatch, config, sound, minimized = true, undocked, expanded = false, alerts, modal, iframe, customFieldsQueue } = store; const { department, name, email } = iframe.guest || {}; - const { color, position: configPosition, background } = config.theme || {}; + const { color, position: configPosition, background, hideExpandChat } = config.theme || {}; const { livechatLogo, hideWatermark = false } = config.settings || {}; const { @@ -78,6 +80,7 @@ export const ScreenProvider: FunctionalComponent = ({ children }) => { background: customBackground, hideAgentAvatar = false, hideGuestAvatar = true, + hideExpandChat: customHideExpandChat = false, } = iframe.theme || {}; const [poppedOut, setPopedOut] = useState(false); @@ -147,6 +150,7 @@ export const ScreenProvider: FunctionalComponent = ({ children }) => { background: customBackground || background, hideAgentAvatar, hideGuestAvatar, + hideExpandChat: customHideExpandChat || hideExpandChat, }, notificationsEnabled: sound?.enabled, minimized: !poppedOut && (minimized || undocked), diff --git a/packages/livechat/src/components/Screen/index.js b/packages/livechat/src/components/Screen/index.js index 4123a832d8eef..4fa234e4016bb 100644 --- a/packages/livechat/src/components/Screen/index.js +++ b/packages/livechat/src/components/Screen/index.js @@ -128,6 +128,7 @@ export const Screen = ({ title, color, agent, children, className, unread, trigg onRestore={onRestore} onOpenWindow={onOpenWindow} queueInfo={queueInfo} + hideExpandChat={theme.hideExpandChat} /> )} diff --git a/packages/livechat/src/store/index.tsx b/packages/livechat/src/store/index.tsx index 3e8d0663d2fa6..d27cacc75b4f0 100644 --- a/packages/livechat/src/store/index.tsx +++ b/packages/livechat/src/store/index.tsx @@ -30,6 +30,7 @@ export type StoreState = { offlineColor?: string; position: 'left' | 'right'; background?: string; + hideExpandChat?: boolean; actionLinks?: { webrtc: { actionLinksAlignment: string; @@ -89,6 +90,7 @@ export type StoreState = { background?: string; hideGuestAvatar?: boolean; hideAgentAvatar?: boolean; + hideExpandChat?: boolean; }; visible?: boolean; department?: string;