diff --git a/.changeset/fuzzy-vans-own.md b/.changeset/fuzzy-vans-own.md new file mode 100644 index 0000000000000..3508c6786a737 --- /dev/null +++ b/.changeset/fuzzy-vans-own.md @@ -0,0 +1,16 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/livechat": minor +--- + +Improved Livechat's theming capabilities + + +| Name (`setTheme`, `initialize`) | Workspace setting | Default value | Description | +|---------------------------------|----------------------------------------|---------------|-------------| +| `position` | Livechat widget position on the screen | `right` | Changes the widget position on the screen. Can be `left` or `right` | +| `background` | Livechat background | `N/A` | Changes the message list background. Accepts the same values as the CSS property [background](https://developer.mozilla.org/en-US/docs/Web/CSS/background) | +| `guestBubbleBackgroundColor` | `N/A` | `N/A` | Changes the guest's message bubble background color | +| `agentBubbleBackgroundColor` | `N/A` | `N/A` | Changes the agent's message bubble background color | +| `hideGuestAvatar` | `N/A` | `false` | Hides/shows the guest avatar | +| `hideAgentAvatar` | `N/A` | `true` | Hides/shows the agent avatar | diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index 0f7da0b7c9e97..097f66f350557 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -177,6 +177,8 @@ export async function settings({ businessUnit = '' }: { businessUnit?: string } color: initSettings.Livechat_title_color, offlineTitle: initSettings.Livechat_offline_title, offlineColor: initSettings.Livechat_offline_title_color, + position: initSettings.Livechat_widget_position || 'right', + background: initSettings.Livechat_background, actionLinks: { webrtc: [ { diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index 776990beaa21a..2db2a6109fb60 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -1127,6 +1127,8 @@ class LivechatClass { 'Livechat_clear_local_storage_when_chat_ended', 'Livechat_history_monitor_type', 'Livechat_hide_system_messages', + 'Livechat_widget_position', + 'Livechat_background', ] as const; type SettingTypes = (typeof validSettings)[number] | 'Livechat_Show_Connecting'; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts index c2f66bc793559..27a9888fe342f 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts @@ -194,6 +194,35 @@ export const createSettings = async (): Promise => { modules: ['livechat-enterprise'], }); + await settingsRegistry.add('Livechat_widget_position', 'right', { + type: 'select', + group: 'Omnichannel', + section: 'Livechat', + i18nLabel: 'Livechat_widget_position_on_the_screen', + public: true, + values: [ + { key: 'left', i18nLabel: 'Left' }, + { key: 'right', i18nLabel: 'Right' }, + ], + enterprise: true, + invalidValue: 'right', + modules: ['livechat-enterprise'], + enableQuery: omnichannelEnabledQuery, + }); + + await settingsRegistry.add('Livechat_background', '', { + type: 'string', + group: 'Omnichannel', + section: 'Livechat', + i18nDescription: 'Livechat_background_description', + placeholder: '#FFFFFF', + public: true, + enterprise: true, + invalidValue: '', + modules: ['livechat-enterprise'], + enableQuery: omnichannelEnabledQuery, + }); + await settingsRegistry.add('Omnichannel_contact_manager_routing', true, { type: 'boolean', group: 'Omnichannel', diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 849dcab49c5c9..8178b4fcb0391 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -3117,6 +3117,7 @@ "leave-p_description": "Permission to leave private groups", "Lets_get_you_new_one_": "Let's get you a new one!", "Let_them_know": "Let them know", + "Left": "Left", "License": "License", "Line": "Line", "Link": "Link", @@ -3176,6 +3177,9 @@ "Livechat_offline_message_sent": "Livechat offline message sent", "Livechat_OfflineMessageToChannel_enabled": "Send Livechat offline messages to a channel", "Livechat_hide_system_messages": "Hide system messages", + "Livechat_widget_position_on_the_screen": "Livechat widget position on the screen", + "Livechat_background": "Livechat background", + "Livechat_background_description": "Define a background using hexadecimal (#F5455C), a color name (red) or an image using a URL (`url('https://example.com/image.png')`). This field follows CSS standards. [See documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/background).", "Omnichannel_chat_closed_due_to_inactivity": "The chat was automatically closed because we haven't received any reply from {{guest}} in {{timeout}} seconds", "Omnichannel_actions": "Omnichannel actions", "Omnichannel_on_hold_chat_resumed": "On Hold Chat Resumed: {{comment}}", @@ -4449,6 +4453,7 @@ "Review_devices": "Review when and where devices are connecting from", "Ringing": "Ringing", "Ringtones_and_visual_indicators_notify_people_of_incoming_calls": "Ringtones and visual indicators notify people of incoming calls.", + "Right": "Right", "Robot_Instructions_File_Content": "Robots.txt File Contents", "Root": "Root", "Required_action": "Required action", diff --git a/packages/i18n/src/locales/pt-BR.i18n.json b/packages/i18n/src/locales/pt-BR.i18n.json index 76fc3db44e7e2..01f29bd65b4fa 100644 --- a/packages/i18n/src/locales/pt-BR.i18n.json +++ b/packages/i18n/src/locales/pt-BR.i18n.json @@ -2635,6 +2635,7 @@ "leave-p": "Deixar grupos privados", "leave-p_description": "Permissão para deixar grupos privados", "Lets_get_you_new_one_": "Vamos pegar outro!", + "Left": "Esquerda", "List_of_Channels": "Lista de Canais", "List_of_departments_for_forward": "Lista de departamentos permitidos para encaminhamento (opcional).", "List_of_departments_for_forward_description": "Permite definir uma lista restrita de departamentos que podem receber conversas deste departamento.", @@ -2685,6 +2686,9 @@ "Livechat_offline_message_sent": "Mensagem offline do livechat enviada", "Livechat_OfflineMessageToChannel_enabled": "Envie mensagens offline do livechat para um canal", "Livechat_hide_system_messages": "Ocultar mensagens do sistema", + "Livechat_widget_position_on_the_screen": "Posição do livechat widget na tela", + "Livechat_background": "Livechat plano de fundo", + "Livechat_background_description": "Defina uma plano de fundo usando hexadecimal (#F5455C), um nome de cor (red) ou uma imagem através de uma URL (`url('https://example.com/image.png')`). Este campo segue os padrões do CSS. [See documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/background).", "Omnichannel_on_hold_chat_resumed": "Conversa em espera retomada: {{comment}}", "Omnichannel_on_hold_chat_automatically": "A conversa foi automaticamente retomada de em espera após receber uma nova mensagem de {{guest}}", "Omnichannel_on_hold_chat_resumed_manually": "A conversa foi manualmente retomada de em espera por {{user}}", @@ -3655,6 +3659,7 @@ "Return_to_previous_page": "Retornar para a página anterior", "Return_to_the_queue": "Retornar para fila", "Ringing": "Tocando", + "Right": "Direita", "Robot_Instructions_File_Content": "Conteúdo do arquivo Robots.txt", "Default_Referrer_Policy": "Política de referência padrão", "Default_Referrer_Policy_Description": "Controla o cabeçalho 'referência' que é enviado ao solicitar mídia incorporada de outros servidores. Para obter mais informações, consulte [este link do MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy). Lembre-se de que é necessária uma atualização completa da página para ter resultado.", diff --git a/packages/livechat/src/components/App/App.tsx b/packages/livechat/src/components/App/App.tsx index ba1f435409f36..ba3e941c27558 100644 --- a/packages/livechat/src/components/App/App.tsx +++ b/packages/livechat/src/components/App/App.tsx @@ -22,17 +22,13 @@ import LeaveMessage from '../../routes/LeaveMessage'; import Register from '../../routes/Register'; import SwitchDepartment from '../../routes/SwitchDepartment'; import TriggerMessage from '../../routes/TriggerMessage'; -import type { Dispatch } from '../../store'; +import type { Dispatch, StoreState } from '../../store'; import { ScreenProvider } from '../Screen/ScreenProvider'; type AppProps = { config: { - settings: { - registrationForm?: boolean; - nameFieldRegistrationForm?: boolean; - emailFieldRegistrationForm?: boolean; - forceAcceptDataProcessingConsent?: boolean; - }; + settings: StoreState['config']['settings']; + theme: StoreState['config']['theme']; online?: boolean; departments: Department[]; enabled?: boolean; @@ -64,6 +60,7 @@ type AppProps = { name: string; email: string; }; + theme: StoreState['iframe']['theme']; }; i18n: typeof i18next; }; @@ -141,11 +138,13 @@ export class App extends Component { const { minimized, iframe: { visible }, + config: { theme }, dispatch, } = this.props; parentCall(minimized ? 'minimizeWindow' : 'restoreWindow'); parentCall(visible ? 'showWidget' : 'hideWidget'); + parentCall('setWidgetPosition', theme.position || 'right'); visibility.addListener(this.handleVisibilityChange); diff --git a/packages/livechat/src/components/Messages/Message/index.js b/packages/livechat/src/components/Messages/Message/index.js index 56cd74239e5ab..74387dae6719d 100644 --- a/packages/livechat/src/components/Messages/Message/index.js +++ b/packages/livechat/src/components/Messages/Message/index.js @@ -92,9 +92,20 @@ const getMessageUsernames = (compact, message) => { return [username]; }; -const Message = ({ avatarResolver, attachmentResolver = getAttachmentUrl, use, me, compact, className, style = {}, t, ...message }) => ( +const Message = ({ + avatarResolver, + attachmentResolver = getAttachmentUrl, + use, + me, + compact, + className, + style = {}, + t, + hideAvatar, + ...message +}) => ( - {!message.type && } + {!message.type && !hideAvatar && } {renderContent({ text: message.type ? getSystemMessageText(message, t) : message.msg, diff --git a/packages/livechat/src/components/Messages/MessageAvatars/index.js b/packages/livechat/src/components/Messages/MessageAvatars/index.js index 07ca6644f79f0..aa25528be11b0 100644 --- a/packages/livechat/src/components/Messages/MessageAvatars/index.js +++ b/packages/livechat/src/components/Messages/MessageAvatars/index.js @@ -4,10 +4,18 @@ import { createClassName } from '../../../helpers/createClassName'; import { Avatar } from '../../Avatar'; import styles from './styles.scss'; -export const MessageAvatars = memo(({ avatarResolver = () => null, usernames = [], className, style = {} }) => ( -
- {usernames.map((username) => ( - - ))} -
-)); +export const MessageAvatars = memo(({ avatarResolver = () => null, usernames = [], className, style = {} }) => { + const avatars = usernames.filter(Boolean); + + if (!avatars.length) { + return null; + } + + return ( +
+ {avatars.map((username) => ( + + ))} +
+ ); +}); diff --git a/packages/livechat/src/components/Messages/MessageBubble/styles.scss b/packages/livechat/src/components/Messages/MessageBubble/styles.scss index 1d1e8bc9664e3..e155aa194c24f 100644 --- a/packages/livechat/src/components/Messages/MessageBubble/styles.scss +++ b/packages/livechat/src/components/Messages/MessageBubble/styles.scss @@ -12,11 +12,8 @@ $message-bubble-me-link-color: $color-text-lighter; $message-bubble-nude-padding: 0; $message-bubble-quoted-padding: 12px 12px 12px 0; $message-bubble-quoted-indicator-width: 3px; -$message-bubble-quoted-indicator-margin: - (-2 * $message-bubble-quoted-indicator-width) - (2 * $message-bubble-quoted-indicator-width) - (-2 * $message-bubble-quoted-indicator-width) - $message-bubble-quoted-indicator-width; +$message-bubble-quoted-indicator-margin: (-2 * $message-bubble-quoted-indicator-width) (2 * $message-bubble-quoted-indicator-width) + (-2 * $message-bubble-quoted-indicator-width) $message-bubble-quoted-indicator-width; $message-bubble-quoted-indicator-border-radius: $default-border-radius; $message-bubble-quoted-indicator-color: $color-green; @@ -28,7 +25,7 @@ $message-bubble-quoted-indicator-color: $color-green; color: $message-bubble-color; border-radius: $message-bubble-border-radius; - background-color: $message-bubble-background-color; + background-color: var(--receiver-bubble-background-color, $message-bubble-background-color); align-items: stretch; flex-flow: row nowrap; justify-content: flex-start; @@ -39,7 +36,7 @@ $message-bubble-quoted-indicator-color: $color-green; &--inverse { color: var(--font-color, $message-bubble-me-color); - background-color: var(--color, $message-bubble-me-background-color); + background-color: var(--sender-bubble-background-color, var(--color, $message-bubble-me-background-color)); a { color: var(--font-color, $message-bubble-me-link-color); @@ -55,13 +52,13 @@ $message-bubble-quoted-indicator-color: $color-green; &--quoted { padding: $message-bubble-quoted-padding; - background-color: $message-bubble-background-color; + background-color: var(--receiver-bubble-background-color, $message-bubble-background-color); &::before { width: $message-bubble-quoted-indicator-width; margin: -6px 6px -6px 3px; - content: ""; + content: ''; border-radius: $message-bubble-quoted-indicator-border-radius; background-color: $message-bubble-quoted-indicator-color; diff --git a/packages/livechat/src/components/Messages/MessageList/index.js b/packages/livechat/src/components/Messages/MessageList/index.js index 9e94a5b83893c..cd7e2017397f0 100644 --- a/packages/livechat/src/components/Messages/MessageList/index.js +++ b/packages/livechat/src/components/Messages/MessageList/index.js @@ -144,8 +144,8 @@ export class MessageList extends MemoizedComponent { typingUsernames, }) => { const items = []; - const { incomingCallAlert } = store.state; - const { ongoingCall } = store.state; + const { incomingCallAlert, ongoingCall } = store.state; + const { hideSenderAvatar = false, hideReceiverAvatar = false } = this.props || {}; for (let i = 0; i < messages.length; ++i) { const previousMessage = messages[i - 1]; @@ -180,6 +180,7 @@ export class MessageList extends MemoizedComponent { items.push(); } + const isMe = uid && message.u && uid === message.u._id; items.push( unknown; dismissNotification: () => void; theme?: { - color: string; - fontColor: string; - iconColor: string; + color?: string; + fontColor?: string; + iconColor?: string; + position?: 'left' | 'right'; + guestBubbleBackgroundColor?: string; + agentBubbleBackgroundColor?: string; + background?: string; + hideGuestAvatar?: boolean; + hideAgentAvatar?: boolean; }; }; @@ -38,6 +44,8 @@ export const ScreenContext = createContext({ color: '', fontColor: '', iconColor: '', + hideAgentAvatar: false, + hideGuestAvatar: true, }, notificationsEnabled: true, minimized: true, @@ -50,13 +58,41 @@ export const ScreenContext = createContext({ } as ScreenContextValue); export const ScreenProvider: FunctionalComponent = ({ children }) => { - const { dispatch, config, sound, minimized = true, undocked, expanded = false, alerts, modal, iframe } = useContext(StoreContext); + const { + dispatch, + config, + sound, + minimized = true, + undocked, + expanded = false, + alerts, + modal, + iframe, + ...store + } = useContext(StoreContext); const { department, name, email } = iframe.guest || {}; - const { color } = config.theme || {}; - const { color: customColor, fontColor: customFontColor, iconColor: customIconColor } = iframe.theme || {}; + const { color, position: configPosition, background } = config.theme || {}; + + const { + color: customColor, + fontColor: customFontColor, + iconColor: customIconColor, + guestBubbleBackgroundColor, + agentBubbleBackgroundColor, + position: customPosition, + background: customBackground, + hideAgentAvatar = false, + hideGuestAvatar = true, + } = iframe.theme || {}; const [poppedOut, setPopedOut] = useState(false); + const position = customPosition || configPosition || 'right'; + + useEffect(() => { + parentCall('setWidgetPosition', position || 'right'); + }, [position]); + const handleEnableNotifications = () => { dispatch({ sound: { ...sound, enabled: true } }); }; @@ -118,6 +154,12 @@ export const ScreenProvider: FunctionalComponent = ({ children }) => { color: customColor || color, fontColor: customFontColor, iconColor: customIconColor, + position, + guestBubbleBackgroundColor, + agentBubbleBackgroundColor, + background: customBackground || background, + hideAgentAvatar, + hideGuestAvatar, }, 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 f9bf036e19e23..c3b792f16a541 100644 --- a/packages/livechat/src/components/Screen/index.js +++ b/packages/livechat/src/components/Screen/index.js @@ -69,6 +69,9 @@ const CssVar = ({ theme }) => { ${theme.color ? `--color: ${theme.color};` : ''} ${theme.fontColor ? `--font-color: ${theme.fontColor};` : ''} ${theme.iconColor ? `--icon-color: ${theme.iconColor};` : ''} + ${theme.guestBubbleBackgroundColor ? `--sender-bubble-background-color: ${theme.guestBubbleBackgroundColor};` : ''} + ${theme.agentBubbleBackgroundColor ? `--receiver-bubble-background-color: ${theme.agentBubbleBackgroundColor};` : ''} + ${theme.background ? `--message-list-background: ${theme.background};` : ''} } `} ); @@ -95,7 +98,15 @@ export const Screen = ({ title, color, agent, children, className, unread, trigg } = useContext(ScreenContext); return ( -
+
{triggered && (