diff --git a/app/controllers/widget_tests_controller.rb b/app/controllers/widget_tests_controller.rb
index 22a8c0da8..fff47d907 100644
--- a/app/controllers/widget_tests_controller.rb
+++ b/app/controllers/widget_tests_controller.rb
@@ -1,5 +1,8 @@
class WidgetTestsController < ActionController::Base
- before_action :set_web_widget
+ before_action :ensure_web_widget
+ before_action :ensure_widget_position
+ before_action :ensure_widget_type
+ before_action :ensure_widget_style
def index
render
@@ -7,7 +10,24 @@ class WidgetTestsController < ActionController::Base
private
- def set_web_widget
- @web_widget = Channel::WebWidget.first
+ def ensure_widget_style
+ @widget_style = params[:widget_style] || 'standard'
+ end
+
+ def ensure_widget_position
+ @widget_position = params[:position] || 'left'
+ end
+
+ def ensure_widget_type
+ @widget_type = params[:type] || 'expanded_bubble'
+ end
+
+ def inbox_id
+ @inbox_id ||= params[:inbox_id] || Channel::WebWidget.first.inbox.id
+ end
+
+ def ensure_web_widget
+ @inbox = Inbox.find(inbox_id)
+ @web_widget = @inbox.channel
end
end
diff --git a/app/javascript/packs/sdk.js b/app/javascript/packs/sdk.js
index 10e21741a..c4bfcdb08 100755
--- a/app/javascript/packs/sdk.js
+++ b/app/javascript/packs/sdk.js
@@ -1,25 +1,11 @@
import Cookies from 'js-cookie';
import { IFrameHelper } from '../sdk/IFrameHelper';
-import { getBubbleView } from '../sdk/bubbleHelpers';
-import md5 from 'md5';
-import { getUserCookieName } from '../sdk/cookieHelpers';
-
-const REQUIRED_USER_KEYS = ['avatar_url', 'email', 'name'];
-
-const ALLOWED_USER_ATTRIBUTES = [...REQUIRED_USER_KEYS, 'identifier_hash'];
-
-export const getUserString = ({ identifier = '', user }) => {
- const userStringWithSortedKeys = ALLOWED_USER_ATTRIBUTES.reduce(
- (acc, key) => `${acc}${key}${user[key] || ''}`,
- ''
- );
- return `${userStringWithSortedKeys}identifier${identifier}`;
-};
-
-const computeHashForUserData = (...args) => md5(getUserString(...args));
-
-export const hasUserKeys = user =>
- REQUIRED_USER_KEYS.reduce((acc, key) => acc || !!user[key], false);
+import { getBubbleView } from '../sdk/settingsHelper';
+import {
+ computeHashForUserData,
+ getUserCookieName,
+ hasUserKeys,
+} from '../sdk/cookieHelpers';
const runSDK = ({ baseUrl, websiteToken }) => {
if (window.$chatwoot) {
@@ -38,6 +24,7 @@ const runSDK = ({ baseUrl, websiteToken }) => {
type: getBubbleView(chatwootSettings.type),
launcherTitle: chatwootSettings.launcherTitle || '',
showPopoutButton: chatwootSettings.showPopoutButton || false,
+ widgetStyle: chatwootSettings.widgetStyle || 'standard',
toggle(state) {
IFrameHelper.events.toggleBubble(state);
diff --git a/app/javascript/sdk/IFrameHelper.js b/app/javascript/sdk/IFrameHelper.js
index a5a90dee7..2d851ba49 100644
--- a/app/javascript/sdk/IFrameHelper.js
+++ b/app/javascript/sdk/IFrameHelper.js
@@ -26,6 +26,7 @@ import { dispatchWindowEvent } from 'shared/helpers/CustomEventHelper';
import { CHATWOOT_ERROR, CHATWOOT_READY } from '../widget/constants/sdkEvents';
import { SET_USER_ERROR } from '../widget/constants/errorTypes';
import { getUserCookieName } from './cookieHelpers';
+import { isFlatWidgetStyle } from './settingsHelper';
export const IFrameHelper = {
getUrl({ baseUrl, websiteToken }) {
@@ -52,6 +53,10 @@ export const IFrameHelper = {
if (window.$chatwoot.hideMessageBubble) {
holderClassName += ` woot-widget--without-bubble`;
}
+ if (isFlatWidgetStyle(window.$chatwoot.widgetStyle)) {
+ holderClassName += ` woot-widget-holder--flat`;
+ }
+
addClass(widgetHolder, holderClassName);
widgetHolder.appendChild(iframe);
body.appendChild(widgetHolder);
@@ -121,6 +126,7 @@ export const IFrameHelper = {
position: window.$chatwoot.position,
hideMessageBubble: window.$chatwoot.hideMessageBubble,
showPopoutButton: window.$chatwoot.showPopoutButton,
+ widgetStyle: window.$chatwoot.widgetStyle,
});
IFrameHelper.onLoad({
widgetColor: message.config.channelConfig.widgetColor,
@@ -222,21 +228,27 @@ export const IFrameHelper = {
createBubbleHolder();
onLocationChangeListener();
if (!window.$chatwoot.hideMessageBubble) {
+ let className = 'woot-widget-bubble';
+ let closeBtnClassName = `woot-elements--${window.$chatwoot.position} woot-widget-bubble woot--close woot--hide`;
+
+ if (isFlatWidgetStyle(window.$chatwoot.widgetStyle)) {
+ className += ' woot-widget-bubble--flat';
+ closeBtnClassName += ' woot-widget-bubble--flat';
+ }
+
const chatIcon = createBubbleIcon({
- className: 'woot-widget-bubble',
+ className,
src: bubbleImg,
target: chatBubble,
});
- const closeIcon = closeBubble;
- const closeIconclassName = `woot-elements--${window.$chatwoot.position} woot-widget-bubble woot--close woot--hide`;
- addClass(closeIcon, closeIconclassName);
+ addClass(closeBubble, closeBtnClassName);
chatIcon.style.background = widgetColor;
- closeIcon.style.background = widgetColor;
+ closeBubble.style.background = widgetColor;
bubbleHolder.appendChild(chatIcon);
- bubbleHolder.appendChild(closeIcon);
+ bubbleHolder.appendChild(closeBubble);
bubbleHolder.appendChild(createNotificationBubble());
onClickChatBubble();
}
diff --git a/app/javascript/sdk/bubbleHelpers.js b/app/javascript/sdk/bubbleHelpers.js
index 740d83733..18f8e2fbc 100644
--- a/app/javascript/sdk/bubbleHelpers.js
+++ b/app/javascript/sdk/bubbleHelpers.js
@@ -1,6 +1,6 @@
import { addClass, removeClass, toggleClass, wootOn } from './DOMHelpers';
import { IFrameHelper } from './IFrameHelper';
-import { BUBBLE_DESIGN } from './constants';
+import { isExpandedView } from './settingsHelper';
export const bubbleImg =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAAwgJEBk0TVheY2R5eo+ut8jb5OXs8fX2+cjRDTIAAADsSURBVHgBldZbkoMgFIThRgQv8SKKgGf/C51UnJqaRI30/9zfe+NQUQ3TvG7bOk9DVeCmshmj/CuOTYnrdBfkUOg0zlOtl9OWVuEk4+QyZ3DIevmSt/ioTvK1VH/s5bY3YdM9SBZ/mUUyWgx+U06ycgp7D8msxSvtc4HXL9BLdj2elSEfhBJAI0QNgJEBI1BEBsQClVBVGDgwYOLAhJkDM1YOrNg4sLFAsLJgZsHEgoEFFQt0JAFGFjQsKAMJ0LFAexKgZYFyJIDxJIBNJEDNAtSJBLCeBDCOBFAPzwFA94ED+zmhwDO9358r8ANtIsMXi7qVAwAAAABJRU5ErkJggg==';
@@ -13,10 +13,6 @@ export const chatBubble = document.createElement('button');
export const closeBubble = document.createElement('button');
export const notificationBubble = document.createElement('span');
-export const getBubbleView = type =>
- BUBBLE_DESIGN.includes(type) ? type : BUBBLE_DESIGN[0];
-export const isExpandedView = type => getBubbleView(type) === BUBBLE_DESIGN[1];
-
export const setBubbleText = bubbleText => {
if (isExpandedView(window.$chatwoot.type)) {
const textNode = document.getElementById('woot-widget--expanded__text');
diff --git a/app/javascript/sdk/constants.js b/app/javascript/sdk/constants.js
index f35378a47..52faf1540 100644
--- a/app/javascript/sdk/constants.js
+++ b/app/javascript/sdk/constants.js
@@ -1 +1,2 @@
export const BUBBLE_DESIGN = ['standard', 'expanded_bubble'];
+export const WIDGET_DESIGN = ['standard', 'flat'];
diff --git a/app/javascript/sdk/cookieHelpers.js b/app/javascript/sdk/cookieHelpers.js
index d56448569..afdc12cc2 100644
--- a/app/javascript/sdk/cookieHelpers.js
+++ b/app/javascript/sdk/cookieHelpers.js
@@ -1,5 +1,23 @@
+import md5 from 'md5';
+
+const REQUIRED_USER_KEYS = ['avatar_url', 'email', 'name'];
+const ALLOWED_USER_ATTRIBUTES = [...REQUIRED_USER_KEYS, 'identifier_hash'];
+
export const getUserCookieName = () => {
const SET_USER_COOKIE_PREFIX = 'cw_user_';
const { websiteToken: websiteIdentifier } = window.$chatwoot;
return `${SET_USER_COOKIE_PREFIX}${websiteIdentifier}`;
};
+
+export const getUserString = ({ identifier = '', user }) => {
+ const userStringWithSortedKeys = ALLOWED_USER_ATTRIBUTES.reduce(
+ (acc, key) => `${acc}${key}${user[key] || ''}`,
+ ''
+ );
+ return `${userStringWithSortedKeys}identifier${identifier}`;
+};
+
+export const computeHashForUserData = (...args) => md5(getUserString(...args));
+
+export const hasUserKeys = user =>
+ REQUIRED_USER_KEYS.reduce((acc, key) => acc || !!user[key], false);
diff --git a/app/javascript/sdk/sdk.js b/app/javascript/sdk/sdk.js
index 2e2aca7aa..bbd920382 100644
--- a/app/javascript/sdk/sdk.js
+++ b/app/javascript/sdk/sdk.js
@@ -1,5 +1,10 @@
-export const SDK_CSS = `.woot-widget-holder {
- box-shadow: 0 5px 40px rgba(0, 0, 0, .16) !important;
+export const SDK_CSS = `
+:root {
+ --b-100: #F2F3F7;
+}
+
+.woot-widget-holder {
+ box-shadow: 0 5px 40px rgba(0, 0, 0, .16);
opacity: 1;
will-change: transform, opacity;
transform: translateY(0);
@@ -9,6 +14,12 @@ export const SDK_CSS = `.woot-widget-holder {
z-index: 2147483000 !important;
}
+.woot-widget-holder.woot-widget-holder--flat {
+ box-shadow: none;
+ border-radius: 0;
+ border: 1px solid var(--b-100);
+}
+
.woot-widget-holder iframe {
border: 0;
height: 100% !important;
@@ -22,21 +33,45 @@ export const SDK_CSS = `.woot-widget-holder {
height: auto;
bottom: 94px;
box-shadow: none !important;
+ border: 0;
}
.woot-widget-bubble {
background: #1f93ff;
- border-radius: 100px !important;
+ border-radius: 100px;
border-width: 0px;
bottom: 20px;
- padding: 0px;
box-shadow: 0 8px 24px rgba(0, 0, 0, .16) !important;
cursor: pointer;
- height: 64px !important;
+ height: 64px;
+ padding: 0px;
position: fixed;
- width: 64px !important;
- z-index: 2147483000 !important;
user-select: none;
+ width: 64px;
+ z-index: 2147483000 !important;
+}
+
+.woot-widget-bubble.woot-widget-bubble--flat {
+ border-radius: 0;
+}
+
+.woot-widget-holder.woot-widget-holder--flat {
+ bottom: 90px;
+}
+
+.woot-widget-bubble.woot-widget-bubble--flat {
+ height: 56px;
+ width: 56px;
+}
+
+.woot-widget-bubble.woot-widget-bubble--flat img {
+ margin: 16px;
+}
+
+.woot-widget-bubble.woot-widget-bubble--flat.woot--close::before,
+.woot-widget-bubble.woot-widget-bubble--flat.woot--close::after {
+ left: 28px;
+ top: 16px;
}
.woot-widget-bubble.unread-notification::after {
@@ -184,7 +219,7 @@ export const SDK_CSS = `.woot-widget-holder {
@media only screen and (min-width: 667px) {
.woot-widget-holder {
- border-radius: 16px !important;
+ border-radius: 16px;
bottom: 104px;
height: calc(85% - 64px - 20px);
max-height: 590px !important;
diff --git a/app/javascript/sdk/settingsHelper.js b/app/javascript/sdk/settingsHelper.js
new file mode 100644
index 000000000..4e256b53a
--- /dev/null
+++ b/app/javascript/sdk/settingsHelper.js
@@ -0,0 +1,11 @@
+import { BUBBLE_DESIGN, WIDGET_DESIGN } from './constants';
+
+export const getBubbleView = type =>
+ BUBBLE_DESIGN.includes(type) ? type : BUBBLE_DESIGN[0];
+
+export const isExpandedView = type => getBubbleView(type) === BUBBLE_DESIGN[1];
+
+export const getWidgetStyle = style =>
+ WIDGET_DESIGN.includes(style) ? style : WIDGET_DESIGN[0];
+
+export const isFlatWidgetStyle = style => style === 'flat';
diff --git a/app/javascript/sdk/specs/bubbleHelpers.spec.js b/app/javascript/sdk/specs/bubbleHelpers.spec.js
deleted file mode 100644
index e67cc4c96..000000000
--- a/app/javascript/sdk/specs/bubbleHelpers.spec.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { getBubbleView, isExpandedView } from '../bubbleHelpers';
-
-describe('#getBubbleView', () => {
- it('returns correct view', () => {
- expect(getBubbleView('')).toEqual('standard');
- expect(getBubbleView('standard')).toEqual('standard');
- expect(getBubbleView('expanded_bubble')).toEqual('expanded_bubble');
- });
-});
-
-describe('#isExpandedView', () => {
- it('returns true if it is expanded view', () => {
- expect(isExpandedView('')).toEqual(false);
- expect(isExpandedView('standard')).toEqual(false);
- expect(isExpandedView('expanded_bubble')).toEqual(true);
- });
-});
diff --git a/app/javascript/sdk/specs/cookieHelpers.spec.js b/app/javascript/sdk/specs/cookieHelpers.spec.js
index c0edc1348..103224760 100644
--- a/app/javascript/sdk/specs/cookieHelpers.spec.js
+++ b/app/javascript/sdk/specs/cookieHelpers.spec.js
@@ -1,4 +1,8 @@
-import { getUserCookieName } from '../cookieHelpers';
+import {
+ getUserCookieName,
+ getUserString,
+ hasUserKeys,
+} from '../cookieHelpers';
describe('#getUserCookieName', () => {
it('returns correct cookie name', () => {
@@ -6,3 +10,40 @@ describe('#getUserCookieName', () => {
expect(getUserCookieName()).toBe('cw_user_123456');
});
});
+
+describe('#getUserString', () => {
+ it('returns correct user string', () => {
+ expect(
+ getUserString({
+ user: {
+ name: 'Pranav',
+ email: 'pranav@example.com',
+ avatar_url: 'https://images.chatwoot.com/placeholder',
+ identifier_hash: '12345',
+ },
+ identifier: '12345',
+ })
+ ).toBe(
+ 'avatar_urlhttps://images.chatwoot.com/placeholderemailpranav@example.comnamePranavidentifier_hash12345identifier12345'
+ );
+
+ expect(
+ getUserString({
+ user: {
+ email: 'pranav@example.com',
+ avatar_url: 'https://images.chatwoot.com/placeholder',
+ },
+ })
+ ).toBe(
+ 'avatar_urlhttps://images.chatwoot.com/placeholderemailpranav@example.comnameidentifier_hashidentifier'
+ );
+ });
+});
+
+describe('#hasUserKeys', () => {
+ it('checks whether the allowed list of keys are present', () => {
+ expect(hasUserKeys({})).toBe(false);
+ expect(hasUserKeys({ randomKey: 'randomValue' })).toBe(false);
+ expect(hasUserKeys({ avatar_url: 'randomValue' })).toBe(true);
+ });
+});
diff --git a/app/javascript/sdk/specs/settingsHelpers.spec.js b/app/javascript/sdk/specs/settingsHelpers.spec.js
new file mode 100644
index 000000000..55648f3f2
--- /dev/null
+++ b/app/javascript/sdk/specs/settingsHelpers.spec.js
@@ -0,0 +1,38 @@
+import {
+ getBubbleView,
+ getWidgetStyle,
+ isExpandedView,
+ isFlatWidgetStyle,
+} from '../settingsHelper';
+
+describe('#getBubbleView', () => {
+ it('returns correct view', () => {
+ expect(getBubbleView('')).toEqual('standard');
+ expect(getBubbleView('standard')).toEqual('standard');
+ expect(getBubbleView('expanded_bubble')).toEqual('expanded_bubble');
+ });
+});
+
+describe('#isExpandedView', () => {
+ it('returns true if it is expanded view', () => {
+ expect(isExpandedView('')).toEqual(false);
+ expect(isExpandedView('standard')).toEqual(false);
+ expect(isExpandedView('expanded_bubble')).toEqual(true);
+ });
+});
+
+describe('#getWidgetStyle', () => {
+ it('returns correct view', () => {
+ expect(getWidgetStyle('')).toEqual('standard');
+ expect(getWidgetStyle('standard')).toEqual('standard');
+ expect(getWidgetStyle('flat')).toEqual('flat');
+ });
+});
+
+describe('#isFlatWidgetStyle', () => {
+ it('returns true if it is expanded view', () => {
+ expect(isFlatWidgetStyle('')).toEqual(false);
+ expect(isFlatWidgetStyle('standard')).toEqual(false);
+ expect(isFlatWidgetStyle('flat')).toEqual(true);
+ });
+});
diff --git a/app/javascript/specs/packs/sdk.spec.js b/app/javascript/specs/packs/sdk.spec.js
deleted file mode 100644
index c9c546cdb..000000000
--- a/app/javascript/specs/packs/sdk.spec.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { getUserString, hasUserKeys } from '../../packs/sdk';
-
-describe('#getUserString', () => {
- it('returns correct user string', () => {
- expect(
- getUserString({
- user: {
- name: 'Pranav',
- email: 'pranav@example.com',
- avatar_url: 'https://images.chatwoot.com/placeholder',
- identifier_hash: '12345',
- },
- identifier: '12345',
- })
- ).toBe(
- 'avatar_urlhttps://images.chatwoot.com/placeholderemailpranav@example.comnamePranavidentifier_hash12345identifier12345'
- );
-
- expect(
- getUserString({
- user: {
- email: 'pranav@example.com',
- avatar_url: 'https://images.chatwoot.com/placeholder',
- },
- })
- ).toBe(
- 'avatar_urlhttps://images.chatwoot.com/placeholderemailpranav@example.comnameidentifier_hashidentifier'
- );
- });
-});
-
-describe('#hasUserKeys', () => {
- it('checks whether the allowed list of keys are present', () => {
- expect(hasUserKeys({})).toBe(false);
- expect(hasUserKeys({ randomKey: 'randomValue' })).toBe(false);
- expect(hasUserKeys({ avatar_url: 'randomValue' })).toBe(true);
- });
-});
diff --git a/app/javascript/widget/App.vue b/app/javascript/widget/App.vue
index 7b047f7cc..8b34c51bf 100755
--- a/app/javascript/widget/App.vue
+++ b/app/javascript/widget/App.vue
@@ -12,6 +12,7 @@
'is-mobile': isMobile,
'is-widget-right': isRightAligned,
'is-bubble-hidden': hideMessageBubble,
+ 'is-flat-design': isWidgetStyleFlat,
}"
>
@@ -61,6 +62,7 @@ export default {
isWidgetOpen: 'appConfig/getIsWidgetOpen',
messageCount: 'conversation/getMessageCount',
unreadMessageCount: 'conversation/getUnreadMessageCount',
+ isWidgetStyleFlat: 'appConfig/isWidgetStyleFlat',
}),
isIFrame() {
return IFrameHelper.isIFrame();
diff --git a/app/javascript/widget/assets/scss/woot.scss b/app/javascript/widget/assets/scss/woot.scss
index 29d5851fa..986dd390b 100755
--- a/app/javascript/widget/assets/scss/woot.scss
+++ b/app/javascript/widget/assets/scss/woot.scss
@@ -57,3 +57,30 @@ body {
padding-left: $space-normal;
}
}
+
+.is-flat-design {
+ .chat-bubble {
+ border-bottom-left-radius: 0 !important;
+ border-bottom-right-radius: 0 !important;
+ border-top-left-radius: 0 !important;
+ border-top-right-radius: 0 !important;
+ box-shadow: none;
+ }
+
+ button {
+ border-radius: 0 !important;
+ }
+
+ input {
+ border-radius: 0;
+ }
+
+ .chat-message--input {
+ border-radius: 0 !important;
+ box-shadow: none !important;
+
+ &.is-focused {
+ box-shadow: none !important;
+ }
+ }
+}
diff --git a/app/javascript/widget/components/ChatFooter.vue b/app/javascript/widget/components/ChatFooter.vue
index f399ff252..423eb2642 100755
--- a/app/javascript/widget/components/ChatFooter.vue
+++ b/app/javascript/widget/components/ChatFooter.vue
@@ -1,7 +1,8 @@