feat(livechat): hide or change logo (#31820)

Co-authored-by: Marcos Spessatto Defendi <marcos.defendi@rocket.chat>
Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>
pull/32060/head
Aleksander Nicacio da Silva 2 years ago committed by GitHub
parent 53db2f651d
commit 3eb4dd7f50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 11
      .changeset/clean-cars-teach.md
  2. 51
      apps/meteor/app/assets/server/assets.ts
  3. 2
      apps/meteor/app/livechat/server/api/lib/livechat.ts
  4. 2
      apps/meteor/app/livechat/server/lib/LivechatTyped.ts
  5. 12
      apps/meteor/ee/app/livechat-enterprise/server/settings.ts
  6. 1
      packages/core-typings/src/IRocketChatAssets.ts
  7. 3
      packages/i18n/src/locales/en.i18n.json
  8. 1
      packages/i18n/src/locales/pt-BR.i18n.json
  9. 29
      packages/livechat/src/components/Screen/ChatButton.tsx
  10. 5
      packages/livechat/src/components/Screen/ScreenProvider.tsx
  11. 40
      packages/livechat/src/components/Screen/index.js
  12. 2
      packages/livechat/src/store/index.tsx

@ -0,0 +1,11 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/core-typings": minor
"@rocket.chat/livechat": minor
---
**Added the ability for premium workspaces to hide Rocket.Chat's watermark as well as change the Livechat widget's logo**
The new settings (named below) can be found in the Omnichannel workspace settings within the livechat section.
- Hide "powered by Rocket.Chat"
- Livechat widget logo (svg, png, jpg)

@ -1,7 +1,7 @@
import crypto from 'crypto';
import type { ServerResponse, IncomingMessage } from 'http';
import type { IRocketChatAssets, IRocketChatAsset } from '@rocket.chat/core-typings';
import type { IRocketChatAssets, IRocketChatAsset, ISetting } from '@rocket.chat/core-typings';
import { Settings } from '@rocket.chat/models';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import type { NextHandleFunction } from 'connect';
@ -20,7 +20,10 @@ import { getURL } from '../../utils/server/getURL';
const RocketChatAssetsInstance = new RocketChatFile.GridFS({
name: 'assets',
});
const assets: IRocketChatAssets = {
type IRocketChatAssetsConfig = Record<keyof IRocketChatAssets, IRocketChatAsset & { settingOptions?: Partial<ISetting> }>;
const assets: IRocketChatAssetsConfig = {
logo: {
label: 'logo (svg, png, jpg)',
defaultUrl: 'images/logo/logo.svg',
@ -189,9 +192,27 @@ const assets: IRocketChatAssets = {
extensions: ['svg'],
},
},
livechat_widget_logo: {
label: 'widget logo (svg, png, jpg)',
constraints: {
type: 'image',
extensions: ['svg', 'png', 'jpg', 'jpeg'],
},
settingOptions: {
section: 'Livechat',
group: 'Omnichannel',
invalidValue: {
defaultUrl: undefined,
},
enableQuery: { _id: 'Livechat_enabled', value: true },
enterprise: true,
modules: ['livechat-enterprise'],
sorter: 999 + 1,
},
},
};
function getAssetByKey(key: string): IRocketChatAsset {
function getAssetByKey(key: string) {
return assets[key as keyof IRocketChatAssets];
}
@ -325,7 +346,7 @@ class RocketChatAssetsClass {
export const RocketChatAssets = new RocketChatAssetsClass();
async function addAssetToSetting(asset: string, value: IRocketChatAsset): Promise<void> {
export async function addAssetToSetting(asset: string, value: IRocketChatAsset, options?: Partial<ISetting>): Promise<void> {
const key = `Assets_${asset}`;
await settingsRegistry.add(
@ -334,19 +355,21 @@ async function addAssetToSetting(asset: string, value: IRocketChatAsset): Promis
defaultUrl: value.defaultUrl,
},
{
type: 'asset',
group: 'Assets',
fileConstraints: value.constraints,
i18nLabel: value.label,
asset,
public: true,
wizard: value.wizard,
...{
type: 'asset',
group: 'Assets',
fileConstraints: value.constraints,
i18nLabel: value.label,
asset,
public: true,
},
...options,
},
);
const currentValue = settings.get<IRocketChatAsset>(key);
if (typeof currentValue === 'object' && currentValue.defaultUrl !== getAssetByKey(asset).defaultUrl) {
if (currentValue && typeof currentValue === 'object' && currentValue.defaultUrl !== getAssetByKey(asset).defaultUrl) {
currentValue.defaultUrl = getAssetByKey(asset).defaultUrl;
await Settings.updateValueById(key, currentValue);
}
@ -354,8 +377,8 @@ async function addAssetToSetting(asset: string, value: IRocketChatAsset): Promis
void (async () => {
for await (const key of Object.keys(assets)) {
const value = getAssetByKey(key);
await addAssetToSetting(key, value);
const { wizard, settingOptions, ...value } = getAssetByKey(key);
await addAssetToSetting(key, value, { ...settingOptions, wizard });
}
})();

@ -171,6 +171,8 @@ export async function settings({ businessUnit = '' }: { businessUnit?: string }
initSettings.Livechat_enable_message_character_limit &&
(initSettings.Livechat_message_character_limit || initSettings.Message_MaxAllowedSize),
hiddenSystemMessages: initSettings.Livechat_hide_system_messages,
livechatLogo: initSettings.Assets_livechat_widget_logo,
hideWatermark: initSettings.Livechat_hide_watermark || false,
},
theme: {
title: initSettings.Livechat_title,

@ -1129,6 +1129,8 @@ class LivechatClass {
'Livechat_hide_system_messages',
'Livechat_widget_position',
'Livechat_background',
'Assets_livechat_widget_logo',
'Livechat_hide_watermark',
] as const;
type SettingTypes = (typeof validSettings)[number] | 'Livechat_Show_Connecting';

@ -223,6 +223,18 @@ export const createSettings = async (): Promise<void> => {
enableQuery: omnichannelEnabledQuery,
});
await settingsRegistry.add('Livechat_hide_watermark', false, {
type: 'boolean',
group: 'Omnichannel',
section: 'Livechat',
invalidValue: false,
enableQuery: omnichannelEnabledQuery,
i18nDescription: 'Livechat_hide_watermark_description',
enterprise: true,
sorter: 999,
modules: ['livechat-enterprise'],
});
await settingsRegistry.add('Omnichannel_contact_manager_routing', true, {
type: 'boolean',
group: 'Omnichannel',

@ -53,4 +53,5 @@ export interface IRocketChatAssets {
tile_310_square: IRocketChatAsset;
tile_310_wide: IRocketChatAsset;
safari_pinned: IRocketChatAsset;
livechat_widget_logo: IRocketChatAsset;
}

@ -3223,6 +3223,9 @@
"Livechat_Calls": "Livechat Calls",
"Livechat_visitor_email_and_transcript_email_do_not_match": "Visitor's email and transcript's email do not match",
"Livechat_visitor_transcript_request": "{{guest}} requested the chat transcript",
"Assets_livechat_widget_logo": "Livechat widget logo (svg, png, jpg)",
"Livechat_hide_watermark": "Hide \"powered by Rocket.Chat\"",
"Livechat_hide_watermark_description": "Remove the Rocket.Chat logo from the widget",
"LiveStream & Broadcasting": "LiveStream & Broadcasting",
"LiveStream & Broadcasting_Description": "This integration between Rocket.Chat and YouTube Live allows channel owners to broadcast their camera feed live to livestream inside a channel.",
"Livestream": "Livestream",

@ -2725,6 +2725,7 @@
"Livechat_Calls": "Chamadas do livechat",
"Livechat_visitor_email_and_transcript_email_do_not_match": "O e-mail do visitante e o e-mail da transcrição não correspondem",
"Livechat_visitor_transcript_request": "{{guest}} solicitou a transcrição da conversa",
"Assets_livechat_widget_logo": "Logotipo do widget do Livechat (svg, png, jpg)",
"LiveStream & Broadcasting": "LiveStream e transmissão",
"Livestream": "Livestream",
"Livestream_close": "Fechar Livestream",

@ -0,0 +1,29 @@
import ChatIcon from '../../icons/chat.svg';
import CloseIcon from '../../icons/close.svg';
import { Button } from '../Button';
type ChatButtonProps = {
text: string;
minimized: boolean;
badge: number;
onClick: () => void;
triggered?: boolean;
className?: string;
logoUrl?: string;
};
export const ChatButton = ({ text, minimized, badge, onClick, triggered = false, className, logoUrl }: ChatButtonProps) => {
const openIcon = logoUrl ? <img src={logoUrl} width={30} height={30} alt='Livechat' /> : <ChatIcon />;
return (
<Button
icon={minimized || triggered ? openIcon : <CloseIcon />}
badge={badge}
onClick={onClick}
className={className}
data-qa-id='chat-button'
>
{text}
</Button>
);
};

@ -9,6 +9,8 @@ import Triggers from '../../lib/triggers';
import { StoreContext } from '../../store';
export type ScreenContextValue = {
hideWatermark: boolean;
livechatLogo: { url: string } | undefined;
notificationsEnabled: boolean;
minimized: boolean;
expanded: boolean;
@ -72,6 +74,7 @@ export const ScreenProvider: FunctionalComponent = ({ children }) => {
} = useContext(StoreContext);
const { department, name, email } = iframe.guest || {};
const { color, position: configPosition, background } = config.theme || {};
const { livechatLogo, hideWatermark = false } = config.settings || {};
const {
color: customColor,
@ -165,6 +168,8 @@ export const ScreenProvider: FunctionalComponent = ({ children }) => {
minimized: !poppedOut && (minimized || undocked),
expanded: !minimized && expanded,
windowed: !minimized && poppedOut,
livechatLogo,
hideWatermark,
sound,
alerts,
modal,

@ -1,12 +1,12 @@
import { useContext, useEffect } from 'preact/hooks';
import { createClassName } from '../../helpers/createClassName';
import ChatIcon from '../../icons/chat.svg';
import CloseIcon from '../../icons/close.svg';
import { Button } from '../Button';
import { Footer, FooterContent, PoweredBy } from '../Footer';
import { PopoverContainer } from '../Popover';
import { Sound } from '../Sound';
import { ChatButton } from './ChatButton';
import ScreenHeader from './Header';
import { ScreenContext } from './ScreenProvider';
import styles from './styles.scss';
@ -15,29 +15,20 @@ export const ScreenContent = ({ children, nopadding, triggered = false, full = f
<main className={createClassName(styles, 'screen__main', { nopadding, triggered, full })}>{children}</main>
);
export const ScreenFooter = ({ children, options, limit }) => (
<Footer>
{children && <FooterContent>{children}</FooterContent>}
<FooterContent>
{options}
{limit}
<PoweredBy />
</FooterContent>
</Footer>
);
export const ScreenFooter = ({ children, options, limit }) => {
const { hideWatermark } = useContext(ScreenContext);
const ChatButton = ({ text, minimized, badge, onClick, triggered = false, agent }) => (
<Button
icon={minimized || triggered ? <ChatIcon /> : <CloseIcon />}
badge={badge}
onClick={onClick}
className={createClassName(styles, 'screen__chat-button')}
data-qa-id='chat-button'
img={triggered && agent && agent.avatar.src}
>
{text}
</Button>
);
return (
<Footer>
{children && <FooterContent>{children}</FooterContent>}
<FooterContent>
{options}
{limit}
{!hideWatermark && <PoweredBy />}
</FooterContent>
</Footer>
);
};
const CssVar = ({ theme }) => {
useEffect(() => {
@ -81,6 +72,7 @@ const CssVar = ({ theme }) => {
export const Screen = ({ title, color, agent, children, className, unread, triggered = false, queueInfo, onSoundStop }) => {
const {
theme = {},
livechatLogo,
notificationsEnabled,
minimized = false,
expanded = false,
@ -150,6 +142,8 @@ export const Screen = ({ title, color, agent, children, className, unread, trigg
text={title}
badge={unread}
minimized={minimized}
logoUrl={livechatLogo?.url}
className={createClassName(styles, 'screen__chat-button')}
onClick={minimized ? onRestore : onMinimize}
/>

@ -55,6 +55,8 @@ export type StoreState = {
limitTextLength?: any;
displayOfflineForm?: boolean;
hiddenSystemMessages?: LivechatHiddenSytemMessageType[];
hideWatermark?: boolean;
livechatLogo?: { url: string };
};
online?: boolean;
departments: Department[];

Loading…
Cancel
Save