feat: Disable attachments and emoji picker in the web widget (#1102)

Signed-off-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
Pranav Raj S 2020-08-05 17:46:17 +05:30 committed by GitHub
parent 3b23aa7913
commit db877453a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 422 additions and 141 deletions

View file

@ -4,7 +4,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
before_action :check_authorization
def index
@inboxes = policy_scope(Current.account.inboxes.includes(:channel, :avatar_attachment))
@inboxes = policy_scope(Current.account.inboxes.order_by_id.includes(:channel, :avatar_attachment))
end
def create
@ -23,7 +23,10 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
def update
@inbox.update(inbox_update_params.except(:channel))
@inbox.channel.update!(inbox_update_params[:channel]) if @inbox.channel.is_a?(Channel::WebWidget) && inbox_update_params[:channel].present?
return unless @inbox.channel.is_a?(Channel::WebWidget) && inbox_update_params[:channel].present?
@inbox.channel.update!(inbox_update_params[:channel])
update_channel_feature_flags
end
def set_agent_bot
@ -67,6 +70,13 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
end
end
def update_channel_feature_flags
return unless inbox_update_params[:channel].key? :selected_feature_flags
@inbox.channel.selected_feature_flags = inbox_update_params[:channel][:selected_feature_flags]
@inbox.channel.save!
end
def permitted_params
params.permit(:id, :avatar, :name, :greeting_message, :greeting_enabled, channel:
[:type, :website_url, :widget_color, :welcome_title, :welcome_tagline, :webhook_url, :email])
@ -74,6 +84,14 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
def inbox_update_params
params.permit(:enable_auto_assignment, :name, :avatar, :greeting_message, :greeting_enabled,
channel: [:website_url, :widget_color, :welcome_title, :welcome_tagline, :webhook_url, :email])
channel: [
:website_url,
:widget_color,
:welcome_title,
:welcome_tagline,
:webhook_url,
:email,
selected_feature_flags: []
])
end
end

View file

@ -1,11 +1,11 @@
html,
body {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
.app-wrapper {
@ -26,36 +26,40 @@ body {
.view-box {
@include full-height;
height: 100vh;
@include margin(0);
@include space-between-column;
height: 100vh;
}
.view-panel {
flex-direction: column;
@include margin($zero);
@include padding($space-normal);
flex-direction: column;
overflow-y: auto;
}
.content-box {
overflow: auto;
@include padding($space-normal);
overflow: auto;
}
.back-button {
@include flex;
align-items: center;
color: $color-woot;
cursor: pointer;
font-size: $font-size-default;
font-weight: $font-weight-normal;
margin-right: $space-normal;
cursor: pointer;
&:before {
vertical-align: text-bottom;
margin-right: $space-smaller;
&::before {
font-size: $font-size-large;
margin-right: $space-small;
vertical-align: text-bottom;
}
}
@ -66,12 +70,14 @@ body {
.no-items-error-message {
@include flex;
@include full-height;
justify-content: center;
align-items: center;
flex-direction: column;
justify-content: center;
img {
max-width: $space-mega;
@include padding($space-one);
max-width: $space-mega;
}
}

View file

@ -202,7 +202,7 @@
}
.settings--content {
@include margin($space-small $space-larger);
@include margin($space-small $space-large);
.title {
font-weight: $font-weight-medium;

View file

@ -7,15 +7,25 @@
<p v-if="headerContent" class="small-12 column">
{{ headerContent }}
</p>
<slot></slot>
</div>
</template>
<script>
export default {
props: {
headerTitle: String,
headerContent: String,
headerImage: String,
headerTitle: {
type: String,
default: '',
},
headerContent: {
type: String,
default: '',
},
headerImage: {
type: String,
default: '',
},
},
};
</script>

View file

@ -57,15 +57,8 @@ import { mapGetters } from 'vuex';
import router from '../../routes';
import adminMixin from '../../mixins/isAdmin';
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
const INBOX_TYPES = {
WEB: 'Channel::WebWidget',
FB: 'Channel::FacebookPage',
TWITTER: 'Channel::TwitterProfile',
TWILIO: 'Channel::TwilioSms',
API: 'Channel::Api',
EMAIL: 'Channel::Email',
};
const getInboxClassByType = type => {
switch (type) {
case INBOX_TYPES.WEB:

View file

@ -1,43 +1,43 @@
<template>
<div
class="small-3 columns channel"
:class="{ inactive: !isActive(channel) }"
:class="{ inactive: !isActive }"
@click="onItemClick"
>
<img
v-if="channel === 'facebook'"
v-if="channel.key === 'facebook'"
src="~dashboard/assets/images/channels/facebook.png"
/>
<img
v-if="channel === 'twitter'"
v-if="channel.key === 'twitter'"
src="~dashboard/assets/images/channels/twitter.png"
/>
<img
v-if="channel === 'telegram'"
v-if="channel.key === 'telegram'"
src="~dashboard/assets/images/channels/telegram.png"
/>
<img
v-if="channel === 'api'"
v-if="channel.key === 'api'"
src="~dashboard/assets/images/channels/api.png"
/>
<img
v-if="channel === 'email'"
v-if="channel.key === 'email'"
src="~dashboard/assets/images/channels/email.png"
/>
<img
v-if="channel === 'line'"
v-if="channel.key === 'line'"
src="~dashboard/assets/images/channels/line.png"
/>
<img
v-if="channel === 'website'"
v-if="channel.key === 'website'"
src="~dashboard/assets/images/channels/website.png"
/>
<img
v-if="channel === 'twilio'"
v-if="channel.key === 'twilio'"
src="~dashboard/assets/images/channels/twilio.png"
/>
<h3 class="channel__title">
{{ channel }}
{{ channel.name }}
</h3>
</div>
</template>
@ -45,7 +45,7 @@
export default {
props: {
channel: {
type: String,
type: Object,
required: true,
},
enabledFeatures: {
@ -53,25 +53,28 @@ export default {
required: true,
},
},
methods: {
isActive(channel) {
computed: {
isActive() {
const { key } = this.channel;
if (Object.keys(this.enabledFeatures) === 0) {
return false;
}
if (channel === 'facebook') {
if (key === 'facebook') {
return this.enabledFeatures.channel_facebook;
}
if (channel === 'twitter') {
if (key === 'twitter') {
return this.enabledFeatures.channel_twitter;
}
if (channel === 'email') {
if (key === 'email') {
return this.enabledFeatures.channel_email;
}
return ['website', 'twilio', 'api'].includes(channel);
return ['website', 'twilio', 'api'].includes(key);
},
},
methods: {
onItemClick() {
if (this.isActive(this.channel)) {
this.$emit('channel-item-click', this.channel);
if (this.isActive) {
this.$emit('channel-item-click', this.channel.key);
}
},
},

View file

@ -96,6 +96,7 @@ import {
hasPressedShift,
} from 'shared/helpers/KeyboardHelpers';
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
import inboxMixin from 'shared/mixins/inboxMixin';
export default {
components: {
@ -104,7 +105,7 @@ export default {
FileUpload,
ResizableTextArea,
},
mixins: [clickaway],
mixins: [clickaway, inboxMixin],
data() {
return {
message: '',
@ -148,9 +149,6 @@ export default {
this.message.length > this.maxLength
);
},
channelType() {
return this.inbox.channel_type;
},
conversationType() {
const { additional_attributes: additionalAttributes } = this.currentChat;
const type = additionalAttributes ? additionalAttributes.type : '';
@ -174,29 +172,6 @@ export default {
}
return MESSAGE_MAX_LENGTH.GENERAL;
},
isATwitterInbox() {
return this.channelType === 'Channel::TwitterProfile';
},
isAFacebookInbox() {
return this.channelType === 'Channel::FacebookPage';
},
isAWebWidgetInbox() {
return this.channelType === 'Channel::WebWidget';
},
isATwilioSMSChannel() {
const { phone_number: phoneNumber = '' } = this.inbox;
return (
this.channelType === 'Channel::TwilioSms' &&
!phoneNumber.startsWith('whatsapp')
);
},
isATwilioWhatsappChannel() {
const { phone_number: phoneNumber = '' } = this.inbox;
return (
this.channelType === 'Channel::TwilioSms' &&
phoneNumber.startsWith('whatsapp')
);
},
showFileUpload() {
return (
this.isAWebWidgetInbox ||

View file

@ -117,16 +117,16 @@
},
"API_CHANNEL": {
"TITLE": "API Channel",
"DESC": "Integrate with API channel and start supporting your customers via chatwoot.",
"DESC": "Integrate with API channel and start supporting your customers.",
"CHANNEL_NAME": {
"LABEL": "Channel Name",
"PLACEHOLDER": "Please enter a channel name",
"ERROR": "This field is required"
},
"WEBHOOK_URL": {
"LABEL": "Webhook Url",
"SUBTITLE": "Configure the url where you want to recieve callbacks from chatwoot on events.",
"PLACEHOLDER": "Webhook Url"
"LABEL": "Webhook URL",
"SUBTITLE": "Configure the URL where you want to recieve callbacks on events.",
"PLACEHOLDER": "Webhook URL"
},
"SUBMIT_BUTTON": "Create API Channel",
"API": {
@ -135,7 +135,7 @@
},
"EMAIL_CHANNEL": {
"TITLE": "Email Channel",
"DESC": "Integrate you email inbox with chatwoot.",
"DESC": "Integrate you email inbox.",
"CHANNEL_NAME": {
"LABEL": "Channel Name",
"PLACEHOLDER": "Please enter a channel name",
@ -212,7 +212,17 @@
"ERROR_MESSAGE": "Could not delete inbox. Please try again later."
}
},
"TABS": {
"SETTINGS": "Settings",
"COLLABORATORS": "Collaborators",
"CONFIGURATION": "Configuration"
},
"SETTINGS": "Settings",
"FEATURES": {
"LABEL": "Features",
"DISPLAY_FILE_PICKER": "Display file picker on the widget",
"DISPLAY_EMOJI_PICKER": "Display emoji picker on the widget"
},
"SETTINGS_POPUP": {
"MESSENGER_HEADING": "Messenger Script",
"MESSENGER_SUB_HEAD": "Place this button inside your body tag",

View file

@ -15,7 +15,7 @@
v-model="selectedAgents"
:options="agentList"
track-by="id"
label="name"
label="available_name"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"

View file

@ -30,14 +30,14 @@ export default {
data() {
return {
channelList: [
'website',
'facebook',
'twitter',
'twilio',
'email',
'api',
'telegram',
'line',
{ key: 'website', name: 'Website' },
{ key: 'facebook', name: 'Facebook' },
{ key: 'twitter', name: 'Twitter' },
{ key: 'twilio', name: 'Twilio' },
{ key: 'email', name: 'Email' },
{ key: 'api', name: 'API' },
{ key: 'telegram', name: 'Telegram' },
{ key: 'line', name: 'Line' },
],
enabledFeatures: {},
};

View file

@ -3,9 +3,18 @@
<woot-modal-header
:header-image="inbox.avatarUrl"
:header-title="inboxName"
>
<woot-tabs :index="selectedTabIndex" @change="onTabChange">
<woot-tabs-item
v-for="tab in tabs"
:key="tab.key"
:name="tab.name"
:show-badge="false"
/>
</woot-tabs>
</woot-modal-header>
<div class="settings--content">
<div v-if="selectedTabKey === 'inbox_settings'" class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_UPDATE_TITLE')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_UPDATE_SUB_TEXT')"
@ -16,7 +25,7 @@
@change="handleImageUpload"
/>
<woot-input
v-if="isAWidgetInbox"
v-if="isAWebWidgetInbox"
v-model.trim="selectedInboxName"
class="medium-9 columns"
:label="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_NAME.LABEL')"
@ -25,7 +34,7 @@
"
/>
<woot-input
v-if="isAWidgetInbox"
v-if="isAWebWidgetInbox"
v-model.trim="channelWebsiteUrl"
class="medium-9 columns"
:label="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_DOMAIN.LABEL')"
@ -34,7 +43,7 @@
"
/>
<woot-input
v-if="isAWidgetInbox"
v-if="isAWebWidgetInbox"
v-model.trim="channelWelcomeTitle"
class="medium-9 columns"
:label="
@ -48,7 +57,7 @@
/>
<woot-input
v-if="isAWidgetInbox"
v-if="isAWebWidgetInbox"
v-model.trim="channelWelcomeTagline"
class="medium-9 columns"
:label="
@ -61,7 +70,7 @@
"
/>
<label v-if="isAWidgetInbox" class="medium-9 columns">
<label v-if="isAWebWidgetInbox" class="medium-9 columns">
{{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.WIDGET_COLOR.LABEL') }}
<woot-color-picker v-model="inbox.widget_color" />
</label>
@ -94,7 +103,6 @@
}}
</p>
</label>
<woot-input
v-if="greetingEnabled"
v-model.trim="greetingMessage"
@ -122,6 +130,33 @@
{{ $t('INBOX_MGMT.SETTINGS_POPUP.AUTO_ASSIGNMENT_SUB_TEXT') }}
</p>
</label>
<label v-if="isAWebWidgetInbox">
{{ $t('INBOX_MGMT.FEATURES.LABEL') }}
</label>
<div v-if="isAWebWidgetInbox" class="widget--feature-flag">
<input
v-model="selectedFeatureFlags"
type="checkbox"
value="attachments"
@input="handleFeatureFlag"
/>
<label for="attachments">
{{ $t('INBOX_MGMT.FEATURES.DISPLAY_FILE_PICKER') }}
</label>
</div>
<div v-if="isAWebWidgetInbox">
<input
v-model="selectedFeatureFlags"
type="checkbox"
value="emoji_picker"
@input="handleFeatureFlag"
/>
<label for="emoji_picker">
{{ $t('INBOX_MGMT.FEATURES.DISPLAY_EMOJI_PICKER') }}
</label>
</div>
<woot-submit-button
:button-text="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
:loading="uiFlags.isUpdatingInbox"
@ -132,7 +167,7 @@
<!-- update agents in inbox -->
<div class="settings--content">
<div v-if="selectedTabKey === 'collaborators'" class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_AGENTS')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_AGENTS_SUB_TEXT')"
@ -141,7 +176,7 @@
v-model="selectedAgents"
:options="agentList"
track-by="id"
label="name"
label="available_name"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
@ -157,11 +192,8 @@
/>
</settings-section>
</div>
<div
v-if="inbox.channel_type === 'Channel::TwilioSms'"
class="settings--content"
>
<div v-if="selectedTabKey === 'configuration'">
<div v-if="isATwilioChannel" class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.TITLE')"
:sub-title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.SUBTITLE')"
@ -169,19 +201,7 @@
<woot-code :script="twilioCallbackURL" lang="html"></woot-code>
</settings-section>
</div>
<div
v-if="inbox.channel_type === 'Channel::FacebookPage'"
class="settings--content"
>
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_HEADING')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_SUB_HEAD')"
>
<woot-code :script="messengerScript"></woot-code>
</settings-section>
</div>
<div v-else-if="inbox.channel_type === 'Channel::WebWidget'">
<div v-else-if="isAWebWidgetInbox">
<div class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_HEADING')"
@ -192,21 +212,23 @@
</div>
</div>
</div>
</div>
</template>
<script>
/* eslint no-console: 0 */
/* global bus */
import { mapGetters } from 'vuex';
import { createMessengerScript } from 'dashboard/helper/scriptGenerator';
import configMixin from 'shared/mixins/configMixin';
import alertMixin from 'shared/mixins/alertMixin';
import SettingsSection from '../../../../components/SettingsSection';
import inboxMixin from 'shared/mixins/inboxMixin';
export default {
components: {
SettingsSection,
},
mixins: [configMixin],
mixins: [alertMixin, configMixin, inboxMixin],
data() {
return {
avatarFile: null,
@ -220,6 +242,7 @@ export default {
channelWebsiteUrl: '',
channelWelcomeTitle: '',
channelWelcomeTagline: '',
selectedFeatureFlags: [],
autoAssignmentOptions: [
{
value: true,
@ -230,6 +253,7 @@ export default {
label: this.$t('INBOX_MGMT.EDIT.AUTO_ASSIGNMENT.DISABLED'),
},
],
selectedTabIndex: 0,
};
},
computed: {
@ -237,17 +261,41 @@ export default {
agentList: 'agents/getAgents',
uiFlags: 'inboxes/getUIFlags',
}),
selectedTabKey() {
return this.tabs[this.selectedTabIndex]?.key;
},
tabs() {
const visibleToAllChannelTabs = [
{
key: 'inbox_settings',
name: this.$t('INBOX_MGMT.TABS.SETTINGS'),
},
{
key: 'collaborators',
name: this.$t('INBOX_MGMT.TABS.COLLABORATORS'),
},
];
if (this.isAWebWidgetInbox || this.isATwilioChannel) {
return [
...visibleToAllChannelTabs,
{
key: 'configuration',
name: this.$t('INBOX_MGMT.TABS.CONFIGURATION'),
},
];
}
return visibleToAllChannelTabs;
},
currentInboxId() {
return this.$route.params.inboxId;
},
inbox() {
return this.$store.getters['inboxes/getInbox'](this.currentInboxId);
},
isAWidgetInbox() {
return this.inbox.channel_type === 'Channel::WebWidget';
},
inboxName() {
if (this.inbox.channel_type === 'Channel::TwilioSms') {
if (this.isATwilioSMSChannel || this.isATwilioWhatsappChannel) {
return `${this.inbox.name} (${this.inbox.phone_number})`;
}
return this.inbox.name;
@ -267,10 +315,25 @@ export default {
this.fetchInboxSettings();
},
methods: {
showAlert(message) {
bus.$emit('newToastMessage', message);
handleFeatureFlag(e) {
console.log(e.target.value);
this.selectedFeatureFlags = this.toggleInput(
this.selectedFeatureFlags,
e.target.value
);
},
toggleInput(selected, current) {
if (selected.includes(current)) {
const newSelectedFlags = selected.filter(flag => flag !== current);
return newSelectedFlags;
}
return [...selected, current];
},
onTabChange(selectedTabIndex) {
this.selectedTabIndex = selectedTabIndex;
},
fetchInboxSettings() {
this.selectedTabIndex = 0;
this.selectedAgents = [];
this.$store.dispatch('agents/get');
this.$store.dispatch('inboxes/get').then(() => {
@ -283,6 +346,7 @@ export default {
this.channelWebsiteUrl = this.inbox.website_url;
this.channelWelcomeTitle = this.inbox.welcome_title;
this.channelWelcomeTagline = this.inbox.welcome_tagline;
this.selectedFeatureFlags = this.inbox.selected_feature_flags || [];
});
},
async fetchAttachedAgents() {
@ -325,6 +389,7 @@ export default {
website_url: this.channelWebsiteUrl,
welcome_title: this.channelWelcomeTitle || '',
welcome_tagline: this.channelWelcomeTagline || '',
selectedFeatureFlags: this.selectedFeatureFlags,
},
};
if (this.avatarFile) {
@ -370,7 +435,16 @@ export default {
.page-top-bar {
@include background-light;
@include border-normal-bottom;
padding: $space-normal $space-larger;
padding: $space-normal $space-large 0;
.tabs {
padding: 0;
margin-bottom: -1px;
}
}
}
.widget--feature-flag {
padding-top: var(--space-small);
}
</style>

View file

@ -11,7 +11,15 @@ const buildInboxData = inboxParams => {
Object.keys(inboxProperties).forEach(key => {
formData.append(key, inboxProperties[key]);
});
Object.keys(channel).forEach(key => {
const { selectedFeatureFlags = [], ...channelParams } = channel;
if (selectedFeatureFlags.length) {
selectedFeatureFlags.forEach(featureFlag => {
formData.append(`channel[selected_feature_flags][]`, featureFlag);
});
} else {
formData.append('channel[selected_feature_flags][]', '');
}
Object.keys(channelParams).forEach(key => {
formData.append(`channel[${key}]`, channel[key]);
});
return formData;

View file

@ -0,0 +1,42 @@
export const INBOX_TYPES = {
WEB: 'Channel::WebWidget',
FB: 'Channel::FacebookPage',
TWITTER: 'Channel::TwitterProfile',
TWILIO: 'Channel::TwilioSms',
API: 'Channel::Api',
EMAIL: 'Channel::Email',
};
export default {
computed: {
channelType() {
return this.inbox.channel_type;
},
isAPIInbox() {
return this.channelType === INBOX_TYPES.API;
},
isATwitterInbox() {
return this.channelType === INBOX_TYPES.TWITTER;
},
isAFacebookInbox() {
return this.channelType === INBOX_TYPES.FB;
},
isAWebWidgetInbox() {
return this.channelType === INBOX_TYPES.WEB;
},
isATwilioChannel() {
return this.channelType === INBOX_TYPES.TWILIO;
},
isAnEmailChannel() {
return this.channelType === INBOX_TYPES.EMAIL;
},
isATwilioSMSChannel() {
const { phone_number: phoneNumber = '' } = this.inbox;
return this.isATwilioChannel && !phoneNumber.startsWith('whatsapp');
},
isATwilioWhatsappChannel() {
const { phone_number: phoneNumber = '' } = this.inbox;
return this.isATwilioChannel && phoneNumber.startsWith('whatsapp');
},
},
};

View file

@ -0,0 +1,114 @@
import { shallowMount } from '@vue/test-utils';
import inboxMixin from '../inboxMixin';
describe('inboxMixin', () => {
it('returns the correct channel type', () => {
const Component = {
render() {},
mixins: [inboxMixin],
data() {
return { inbox: { channel_type: 'Channel::WebWidget' } };
},
};
const wrapper = shallowMount(Component);
expect(wrapper.vm.channelType).toBe('Channel::WebWidget');
});
it('isAPIInbox returns true if channel type is API', () => {
const Component = {
render() {},
mixins: [inboxMixin],
data() {
return { inbox: { channel_type: 'Channel::Api' } };
},
};
const wrapper = shallowMount(Component);
expect(wrapper.vm.isAPIInbox).toBe(true);
});
it('isATwitterInbox returns true if channel type is twitter', () => {
const Component = {
render() {},
mixins: [inboxMixin],
data() {
return { inbox: { channel_type: 'Channel::TwitterProfile' } };
},
};
const wrapper = shallowMount(Component);
expect(wrapper.vm.isATwitterInbox).toBe(true);
});
it('isAFacebookInbox returns true if channel type is Facebook', () => {
const Component = {
render() {},
mixins: [inboxMixin],
data() {
return { inbox: { channel_type: 'Channel::FacebookPage' } };
},
};
const wrapper = shallowMount(Component);
expect(wrapper.vm.isAFacebookInbox).toBe(true);
});
it('isAWebWidgetInbox returns true if channel type is Facebook', () => {
const Component = {
render() {},
mixins: [inboxMixin],
data() {
return { inbox: { channel_type: 'Channel::WebWidget' } };
},
};
const wrapper = shallowMount(Component);
expect(wrapper.vm.isAWebWidgetInbox).toBe(true);
});
it('isATwilioChannel returns true if channel type is Twilio', () => {
const Component = {
render() {},
mixins: [inboxMixin],
data() {
return {
inbox: {
channel_type: 'Channel::TwilioSms',
phone_number: '+91944444444',
},
};
},
};
const wrapper = shallowMount(Component);
expect(wrapper.vm.isATwilioChannel).toBe(true);
expect(wrapper.vm.isATwilioSMSChannel).toBe(true);
});
it('isATwilioWhatsappChannel returns true if channel type is Twilio and phonenumber is a whatsapp number', () => {
const Component = {
render() {},
mixins: [inboxMixin],
data() {
return {
inbox: {
channel_type: 'Channel::TwilioSms',
phone_number: 'whatsapp:+91944444444',
},
};
},
};
const wrapper = shallowMount(Component);
expect(wrapper.vm.isATwilioChannel).toBe(true);
expect(wrapper.vm.isATwilioWhatsappChannel).toBe(true);
});
it('isAnEmailChannel returns true if channel type is email', () => {
const Component = {
render() {},
mixins: [inboxMixin],
data() {
return {
inbox: { channel_type: 'Channel::Email' },
};
},
};
const wrapper = shallowMount(Component);
expect(wrapper.vm.isAnEmailChannel).toBe(true);
});
});

View file

@ -18,6 +18,7 @@
:on-click="emojiOnClick"
/>
<i
v-if="hasEmojiPickerEnabled"
class="emoji-toggle icon ion-happy-outline"
:class="{ active: showEmojiPicker }"
@click="toggleEmojiPicker()"
@ -39,6 +40,7 @@ import ChatSendButton from 'widget/components/ChatSendButton.vue';
import ChatAttachmentButton from 'widget/components/ChatAttachment.vue';
import ResizableTextArea from 'shared/components/ResizableTextArea';
import EmojiInput from 'dashboard/components/widgets/emoji/EmojiInput';
import configMixin from '../mixins/configMixin';
export default {
name: 'ChatInputWrap',
@ -48,7 +50,7 @@ export default {
EmojiInput,
ResizableTextArea,
},
mixins: [clickaway],
mixins: [clickaway, configMixin],
props: {
onSendMessage: {
type: Function,
@ -72,7 +74,7 @@ export default {
widgetColor: 'appConfig/getWidgetColor',
}),
showAttachment() {
return this.userInput.length === 0;
return this.hasAttachmentsEnabled && this.userInput.length === 0;
},
showSendButton() {
return this.userInput.length > 0;

View file

@ -15,5 +15,11 @@ export default {
channelConfig() {
return window.chatwootWebChannel;
},
hasEmojiPickerEnabled() {
return this.channelConfig.enabledFeatures.includes('emoji_picker');
},
hasAttachmentsEnabled() {
return this.channelConfig.enabledFeatures.includes('attachments');
},
},
};

View file

@ -6,6 +6,7 @@ global.chatwootWebChannel = {
hideInputForBotConversations: true,
avatarUrl: 'https://test.url',
hasAConnectedAgentBot: 'AgentBot',
enabledFeatures: ['emoji_picker', 'attachments'],
};
global.chatwootWidgetDefaults = {
@ -22,6 +23,8 @@ describe('configMixin', () => {
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
expect(wrapper.vm.hasEmojiPickerEnabled).toBe(true);
expect(wrapper.vm.hasAttachmentsEnabled).toBe(true);
expect(wrapper.vm.hideInputForBotConversations).toBe(true);
expect(wrapper.vm.hasAConnectedAgentBot).toBe(true);
expect(wrapper.vm.useInboxAvatarForBot).toBe(true);
@ -30,6 +33,7 @@ describe('configMixin', () => {
hideInputForBotConversations: true,
avatarUrl: 'https://test.url',
hasAConnectedAgentBot: 'AgentBot',
enabledFeatures: ['emoji_picker', 'attachments'],
});
});
});

View file

@ -3,6 +3,7 @@
# Table name: channel_web_widgets
#
# id :integer not null, primary key
# feature_flags :integer default(3), not null
# website_token :string
# website_url :string
# welcome_tagline :string
@ -18,6 +19,8 @@
#
class Channel::WebWidget < ApplicationRecord
include FlagShihTzu
self.table_name = 'channel_web_widgets'
validates :website_url, presence: true
@ -26,6 +29,9 @@ class Channel::WebWidget < ApplicationRecord
belongs_to :account
has_one :inbox, as: :channel, dependent: :destroy
has_secure_token :website_token
has_flags 1 => :attachments,
2 => :emoji_picker,
:column => 'feature_flags'
def has_24_hour_messaging_window?
false

View file

@ -46,6 +46,8 @@ class Inbox < ApplicationRecord
after_destroy :delete_round_robin_agents
scope :order_by_id, -> { order(id: :asc) }
def add_member(user_id)
member = inbox_members.new(user_id: user_id)
member.save!

View file

@ -14,3 +14,4 @@ json.enable_auto_assignment resource.enable_auto_assignment
json.web_widget_script resource.channel.try(:web_widget_script)
json.forward_to_address resource.channel.try(:forward_to_address)
json.phone_number resource.channel.try(:phone_number)
json.selected_feature_flags resource.channel.try(:selected_feature_flags)

View file

@ -17,6 +17,7 @@
welcomeTagline: '<%= @web_widget.welcome_tagline %>',
welcomeTitle: '<%= @web_widget.welcome_title %>',
widgetColor: '<%= @web_widget.widget_color %>',
enabledFeatures: <%= @web_widget.selected_feature_flags.to_json.html_safe %>,
enabledLanguages: <%= available_locales_with_name.to_json.html_safe %>,
}
window.chatwootWidgetDefaults = {

View file

@ -0,0 +1,5 @@
class AddFeatureSettingToWebsiteInbox < ActiveRecord::Migration[6.0]
def change
add_column :channel_web_widgets, :feature_flags, :integer, default: 3, null: false
end
end

View file

@ -178,6 +178,7 @@ ActiveRecord::Schema.define(version: 2020_08_02_170002) do
t.string "widget_color", default: "#1f93ff"
t.string "welcome_title"
t.string "welcome_tagline"
t.integer "feature_flags", default: 3, null: false
t.index ["website_token"], name: "index_channel_web_widgets_on_website_token", unique: true
end