Merge branch 'develop' into design/conv-card
This commit is contained in:
commit
3d136a79fa
35 changed files with 273 additions and 68 deletions
|
@ -398,7 +398,7 @@ GEM
|
|||
llhttp-ffi (0.4.0)
|
||||
ffi-compiler (~> 1.0)
|
||||
rake (~> 13.0)
|
||||
loofah (2.18.0)
|
||||
loofah (2.19.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
|
@ -488,8 +488,8 @@ GEM
|
|||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.4.3)
|
||||
loofah (~> 2.3)
|
||||
rails-html-sanitizer (1.4.4)
|
||||
loofah (~> 2.19, >= 2.19.1)
|
||||
railties (6.1.6.1)
|
||||
actionpack (= 6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
class Platform::Api::V1::AccountsController < PlatformController
|
||||
def create
|
||||
@resource = Account.new(account_params)
|
||||
@resource = Account.create!(account_params)
|
||||
update_resource_features
|
||||
@resource.save!
|
||||
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
||||
end
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ const primaryMenuItems = accountId => [
|
|||
icon: 'megaphone',
|
||||
key: 'campaigns',
|
||||
label: 'CAMPAIGNS',
|
||||
featureFlag: 'campaigns',
|
||||
toState: frontendURL(`accounts/${accountId}/campaigns`),
|
||||
toStateName: 'settings_account_campaigns',
|
||||
roles: ['administrator'],
|
||||
|
|
|
@ -67,6 +67,9 @@ export default {
|
|||
if (Object.keys(this.enabledFeatures).length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (key === 'website') {
|
||||
return this.enabledFeatures.channel_website;
|
||||
}
|
||||
if (key === 'facebook') {
|
||||
return this.enabledFeatures.channel_facebook;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ export default {
|
|||
}
|
||||
|
||||
.colorpicker--selected {
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: $space-smaller;
|
||||
cursor: pointer;
|
||||
height: $space-large;
|
||||
|
|
|
@ -17,13 +17,22 @@ const formatArray = params => {
|
|||
return params;
|
||||
};
|
||||
|
||||
const generatePayloadForObject = item => {
|
||||
if (item.action_params.id) {
|
||||
item.action_params = [item.action_params.id];
|
||||
} else {
|
||||
item.action_params = [item.action_params];
|
||||
}
|
||||
return item.action_params;
|
||||
};
|
||||
|
||||
const generatePayload = data => {
|
||||
const actions = JSON.parse(JSON.stringify(data));
|
||||
let payload = actions.map(item => {
|
||||
if (Array.isArray(item.action_params)) {
|
||||
item.action_params = formatArray(item.action_params);
|
||||
} else if (typeof item.action_params === 'object') {
|
||||
item.action_params = [item.action_params.id];
|
||||
item.action_params = generatePayloadForObject(item);
|
||||
} else if (!item.action_params) {
|
||||
item.action_params = [];
|
||||
} else {
|
||||
|
|
|
@ -134,7 +134,7 @@
|
|||
"PHONE_NUMBER": {
|
||||
"LABEL": "Phone number",
|
||||
"PLACEHOLDER": "Please enter the phone number from which message will be sent.",
|
||||
"ERROR": "Please enter a valid value. Phone number should start with `+` sign."
|
||||
"ERROR": "Please provide a valid phone number that starts with a `+` sign and does not contain any spaces."
|
||||
},
|
||||
"API_CALLBACK": {
|
||||
"TITLE": "Callback URL",
|
||||
|
@ -185,7 +185,7 @@
|
|||
"PHONE_NUMBER": {
|
||||
"LABEL": "Phone number",
|
||||
"PLACEHOLDER": "Please enter the phone number from which message will be sent.",
|
||||
"ERROR": "Please enter a valid value. Phone number should start with `+` sign."
|
||||
"ERROR": "Please provide a valid phone number that starts with a `+` sign and does not contain any spaces."
|
||||
},
|
||||
"SUBMIT_BUTTON": "Create Bandwidth Channel",
|
||||
"API": {
|
||||
|
@ -214,7 +214,7 @@
|
|||
"PHONE_NUMBER": {
|
||||
"LABEL": "Phone number",
|
||||
"PLACEHOLDER": "Please enter the phone number from which message will be sent.",
|
||||
"ERROR": "Please enter a valid value. Phone number should start with `+` sign."
|
||||
"ERROR": "Please provide a valid phone number that starts with a `+` sign and does not contain any spaces."
|
||||
},
|
||||
"PHONE_NUMBER_ID": {
|
||||
"LABEL": "Phone number ID",
|
||||
|
|
|
@ -69,11 +69,11 @@ export default {
|
|||
}
|
||||
|
||||
.center--img {
|
||||
left: 5%;
|
||||
max-height: 86%;
|
||||
max-width: 90%;
|
||||
height: 96%;
|
||||
left: 8%;
|
||||
position: absolute;
|
||||
top: 2%;
|
||||
top: 8%;
|
||||
width: 86%;
|
||||
}
|
||||
|
||||
.center-container {
|
||||
|
@ -101,7 +101,7 @@ export default {
|
|||
align-items: flex-start;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: var(--space-larger);
|
||||
padding: var(--space-large);
|
||||
}
|
||||
|
||||
.testimonial-left--card {
|
||||
|
|
|
@ -171,11 +171,12 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { required, url, minLength } from 'vuelidate/lib/validators';
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
||||
import campaignMixin from 'shared/mixins/campaignMixin';
|
||||
import WootDateTimePicker from 'dashboard/components/ui/DateTimePicker.vue';
|
||||
import { URLPattern } from 'urlpattern-polyfill';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -221,8 +222,23 @@ export default {
|
|||
},
|
||||
endPoint: {
|
||||
required,
|
||||
minLength: minLength(7),
|
||||
url,
|
||||
shouldBeAValidURLPattern(value) {
|
||||
try {
|
||||
// eslint-disable-next-line
|
||||
new URLPattern(value);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
shouldStartWithHTTP(value) {
|
||||
if (value) {
|
||||
return (
|
||||
value.startsWith('https://') || value.startsWith('http://')
|
||||
);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
timeOnPage: {
|
||||
required,
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<label :class="{ error: $v.selectedInbox.$error }">
|
||||
{{ $t('CAMPAIGN.ADD.FORM.INBOX.LABEL') }}
|
||||
<select v-model="selectedInbox" @change="onChangeInbox($event)">
|
||||
<option v-for="item in inboxes" :key="item.name" :value="item.id">
|
||||
<option v-for="item in inboxes" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
</option>
|
||||
</select>
|
||||
|
@ -111,10 +111,12 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { required, url, minLength } from 'vuelidate/lib/validators';
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import campaignMixin from 'shared/mixins/campaignMixin';
|
||||
import { URLPattern } from 'urlpattern-polyfill';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootMessageEditor,
|
||||
|
@ -152,8 +154,21 @@ export default {
|
|||
},
|
||||
endPoint: {
|
||||
required,
|
||||
minLength: minLength(7),
|
||||
url,
|
||||
shouldBeAValidURLPattern(value) {
|
||||
try {
|
||||
// eslint-disable-next-line
|
||||
new URLPattern(value);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
shouldStartWithHTTP(value) {
|
||||
if (value) {
|
||||
return value.startsWith('https://') || value.startsWith('http://');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
timeOnPage: {
|
||||
required,
|
||||
|
|
|
@ -62,7 +62,7 @@ import alertMixin from 'shared/mixins/alertMixin';
|
|||
import { required } from 'vuelidate/lib/validators';
|
||||
import router from '../../../../index';
|
||||
|
||||
const shouldStartWithPlusSign = (value = '') => value.startsWith('+');
|
||||
import { isPhoneE164OrEmpty } from 'shared/helpers/Validators';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin],
|
||||
|
@ -78,7 +78,7 @@ export default {
|
|||
},
|
||||
validations: {
|
||||
inboxName: { required },
|
||||
phoneNumber: { required, shouldStartWithPlusSign },
|
||||
phoneNumber: { required, isPhoneE164OrEmpty },
|
||||
apiKey: { required },
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -99,8 +99,7 @@ import { mapGetters } from 'vuex';
|
|||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
import router from '../../../../index';
|
||||
|
||||
const shouldStartWithPlusSign = (value = '') => value.startsWith('+');
|
||||
import { isPhoneE164OrEmpty, isNumber } from 'shared/helpers/Validators';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin],
|
||||
|
@ -118,10 +117,10 @@ export default {
|
|||
},
|
||||
validations: {
|
||||
inboxName: { required },
|
||||
phoneNumber: { required, shouldStartWithPlusSign },
|
||||
phoneNumber: { required, isPhoneE164OrEmpty },
|
||||
apiKey: { required },
|
||||
phoneNumberId: { required },
|
||||
businessAccountId: { required },
|
||||
phoneNumberId: { required, isNumber },
|
||||
businessAccountId: { required, isNumber },
|
||||
},
|
||||
methods: {
|
||||
async createChannel() {
|
||||
|
|
|
@ -110,8 +110,7 @@ import { mapGetters } from 'vuex';
|
|||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
import router from '../../../../index';
|
||||
|
||||
const shouldStartWithPlusSign = (value = '') => value.startsWith('+');
|
||||
import { isPhoneE164OrEmpty } from 'shared/helpers/Validators';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin],
|
||||
|
@ -142,7 +141,7 @@ export default {
|
|||
return {
|
||||
channelName: { required },
|
||||
messagingServiceSID: {},
|
||||
phoneNumber: { shouldStartWithPlusSign },
|
||||
phoneNumber: { required, isPhoneE164OrEmpty },
|
||||
authToken: { required },
|
||||
accountSID: { required },
|
||||
medium: { required },
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
widgetHolder,
|
||||
createBubbleHolder,
|
||||
createBubbleIcon,
|
||||
bubbleImg,
|
||||
bubbleSVG,
|
||||
chatBubble,
|
||||
closeBubble,
|
||||
bubbleHolder,
|
||||
|
@ -21,6 +21,7 @@ import {
|
|||
addUnreadClass,
|
||||
removeUnreadClass,
|
||||
} from './bubbleHelpers';
|
||||
import { isWidgetColorLighter } from 'shared/helpers/colorHelper';
|
||||
import { dispatchWindowEvent } from 'shared/helpers/CustomEventHelper';
|
||||
import { CHATWOOT_ERROR, CHATWOOT_READY } from '../widget/constants/sdkEvents';
|
||||
import { SET_USER_ERROR } from '../widget/constants/errorTypes';
|
||||
|
@ -277,9 +278,14 @@ export const IFrameHelper = {
|
|||
closeBtnClassName += ' woot-widget-bubble--flat';
|
||||
}
|
||||
|
||||
if (isWidgetColorLighter(widgetColor)) {
|
||||
className += ' woot-widget-bubble-color--lighter';
|
||||
closeBtnClassName += ' woot-widget-bubble-color--lighter';
|
||||
}
|
||||
|
||||
const chatIcon = createBubbleIcon({
|
||||
className,
|
||||
src: bubbleImg,
|
||||
path: bubbleSVG,
|
||||
target: chatBubble,
|
||||
});
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ import { addClasses, removeClasses, toggleClass } from './DOMHelpers';
|
|||
import { IFrameHelper } from './IFrameHelper';
|
||||
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==';
|
||||
export const bubbleSVG =
|
||||
'M240.808 240.808H122.123C56.6994 240.808 3.45695 187.562 3.45695 122.122C3.45695 56.7031 56.6994 3.45697 122.124 3.45697C187.566 3.45697 240.808 56.7031 240.808 122.122V240.808Z';
|
||||
|
||||
export const body = document.getElementsByTagName('body')[0];
|
||||
export const widgetHolder = document.createElement('div');
|
||||
|
@ -20,11 +20,27 @@ export const setBubbleText = bubbleText => {
|
|||
}
|
||||
};
|
||||
|
||||
export const createBubbleIcon = ({ className, src, target }) => {
|
||||
export const createBubbleIcon = ({ className, path, target }) => {
|
||||
let bubbleClassName = `${className} woot-elements--${window.$chatwoot.position}`;
|
||||
const bubbleIcon = document.createElement('img');
|
||||
bubbleIcon.src = src;
|
||||
bubbleIcon.alt = 'bubble-icon';
|
||||
const bubbleIcon = document.createElementNS(
|
||||
'http://www.w3.org/2000/svg',
|
||||
'svg'
|
||||
);
|
||||
bubbleIcon.setAttributeNS(null, 'id', 'woot-widget-bubble-icon');
|
||||
bubbleIcon.setAttributeNS(null, 'width', '24');
|
||||
bubbleIcon.setAttributeNS(null, 'height', '24');
|
||||
bubbleIcon.setAttributeNS(null, 'viewBox', '0 0 240 240');
|
||||
bubbleIcon.setAttributeNS(null, 'fill', 'none');
|
||||
bubbleIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
||||
|
||||
const bubblePath = document.createElementNS(
|
||||
'http://www.w3.org/2000/svg',
|
||||
'path'
|
||||
);
|
||||
bubblePath.setAttributeNS(null, 'd', path);
|
||||
bubblePath.setAttributeNS(null, 'fill', '#FFFFFF');
|
||||
|
||||
bubbleIcon.appendChild(bubblePath);
|
||||
target.appendChild(bubbleIcon);
|
||||
|
||||
if (isExpandedView(window.$chatwoot.type)) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export const SDK_CSS = `
|
||||
:root {
|
||||
--b-100: #F2F3F7;
|
||||
--s-700: #37546D;
|
||||
}
|
||||
|
||||
.woot-widget-holder {
|
||||
|
@ -64,7 +65,7 @@ export const SDK_CSS = `
|
|||
width: 56px;
|
||||
}
|
||||
|
||||
.woot-widget-bubble.woot-widget-bubble--flat img {
|
||||
.woot-widget-bubble.woot-widget-bubble--flat svg {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
|
@ -107,7 +108,11 @@ export const SDK_CSS = `
|
|||
width: auto !important;
|
||||
}
|
||||
|
||||
.woot-widget-bubble.woot-widget--expanded img {
|
||||
.woot-widget-bubble.woot-widget--expanded.woot-widget-bubble-color--lighter div{
|
||||
color: var(--s-700);
|
||||
}
|
||||
|
||||
.woot-widget-bubble.woot-widget--expanded svg {
|
||||
height: 20px;
|
||||
margin: 14px 8px 14px 16px;
|
||||
width: 20px;
|
||||
|
@ -126,13 +131,17 @@ export const SDK_CSS = `
|
|||
box-shadow: 0 8px 32px rgba(0, 0, 0, .4) !important;
|
||||
}
|
||||
|
||||
.woot-widget-bubble img {
|
||||
.woot-widget-bubble svg {
|
||||
all: revert;
|
||||
height: 24px;
|
||||
margin: 20px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.woot-widget-bubble.woot-widget-bubble-color--lighter path{
|
||||
fill: var(--s-700);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 667px) {
|
||||
.woot-widget-holder.woot-elements--left {
|
||||
left: 20px;
|
||||
|
@ -157,6 +166,10 @@ export const SDK_CSS = `
|
|||
width: 2px;
|
||||
}
|
||||
|
||||
.woot-widget-bubble-color--lighter.woot--close::before, .woot-widget-bubble-color--lighter.woot--close::after {
|
||||
background-color: var(--s-700);
|
||||
}
|
||||
|
||||
.woot--close::before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ export default {
|
|||
margin-left: -$space-one;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.7);
|
||||
border-top-color: lighten($color-woot, 10%);
|
||||
border-top-color: rgba(255, 255, 255, 0.2);
|
||||
animation: spinner 0.9s linear infinite;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,3 +16,4 @@ export const isValidPassword = value => {
|
|||
containsSpecialCharacter
|
||||
);
|
||||
};
|
||||
export const isNumber = value => /^\d+$/.test(value);
|
||||
|
|
8
app/javascript/shared/helpers/colorHelper.js
Normal file
8
app/javascript/shared/helpers/colorHelper.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export const isWidgetColorLighter = color => {
|
||||
const colorToCheck = color.replace('#', '');
|
||||
const c_r = parseInt(colorToCheck.substr(0, 2), 16);
|
||||
const c_g = parseInt(colorToCheck.substr(2, 2), 16);
|
||||
const c_b = parseInt(colorToCheck.substr(4, 2), 16);
|
||||
const brightness = (c_r * 299 + c_g * 587 + c_b * 114) / 1000;
|
||||
return brightness > 225;
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
import { shouldBeUrl } from '../Validators';
|
||||
import { isValidPassword } from '../Validators';
|
||||
import { isNumber } from '../Validators';
|
||||
|
||||
describe('#shouldBeUrl', () => {
|
||||
it('should return correct url', () => {
|
||||
|
@ -22,3 +23,15 @@ describe('#isValidPassword', () => {
|
|||
expect(isValidPassword('testPass!')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isNumber', () => {
|
||||
it('should return correct number', () => {
|
||||
expect(isNumber('123')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return wrong number', () => {
|
||||
expect(isNumber('123-')).toEqual(false);
|
||||
expect(isNumber('123./')).toEqual(false);
|
||||
expect(isNumber('string')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
|
10
app/javascript/shared/helpers/specs/colorHelper.spec.js
Normal file
10
app/javascript/shared/helpers/specs/colorHelper.spec.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { isWidgetColorLighter } from 'shared/helpers/colorHelper';
|
||||
|
||||
describe('#isWidgetColorLighter', () => {
|
||||
it('returns true if color is lighter', () => {
|
||||
expect(isWidgetColorLighter('#ffffff')).toEqual(true);
|
||||
});
|
||||
it('returns false if color is darker', () => {
|
||||
expect(isWidgetColorLighter('#000000')).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div class="file flex flex-row items-center p-3 cursor-pointer">
|
||||
<div class="icon-wrap">
|
||||
<div class="icon-wrap" :style="{ color: textColor }">
|
||||
<fluent-icon icon="document" size="28" />
|
||||
</div>
|
||||
<div class="meta">
|
||||
<div class="title">
|
||||
<div class="title" :style="{ color: textColor }">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="link-wrap mb-1">
|
||||
|
@ -12,6 +12,7 @@
|
|||
class="download"
|
||||
rel="noreferrer noopener nofollow"
|
||||
target="_blank"
|
||||
:style="{ color: textColor }"
|
||||
:href="url"
|
||||
>
|
||||
{{ $t('COMPONENTS.FILE_BUBBLE.DOWNLOAD') }}
|
||||
|
@ -23,6 +24,7 @@
|
|||
|
||||
<script>
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -51,6 +53,9 @@ export default {
|
|||
fileName() {
|
||||
return this.url.substring(this.url.lastIndexOf('/') + 1);
|
||||
},
|
||||
textColor() {
|
||||
return getContrastingTextColor(this.widgetColor);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openLink() {
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
<div v-if="error" class="text-red-400 mt-2 text-xs font-medium">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!error && helpText"
|
||||
class="text-red-400 mt-2 text-xs font-medium"
|
||||
>
|
||||
{{ helpText }}
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
<script>
|
||||
|
@ -41,6 +47,10 @@ export default {
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
helpText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
labelClass() {
|
||||
|
|
|
@ -27,7 +27,15 @@
|
|||
class="button clear-button"
|
||||
@click="openConversationView"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<span
|
||||
class="flex items-center"
|
||||
:class="{
|
||||
'is-background-light': isBackgroundLighter,
|
||||
}"
|
||||
:style="{
|
||||
color: widgetColor,
|
||||
}"
|
||||
>
|
||||
<fluent-icon class="mr-2" size="16" icon="arrow-right" />
|
||||
{{ $t('UNREAD_VIEW.VIEW_MESSAGES_BUTTON') }}
|
||||
</span>
|
||||
|
@ -43,6 +51,7 @@ import configMixin from '../mixins/configMixin';
|
|||
import { ON_UNREAD_MESSAGE_CLICK } from '../constants/widgetBusEvents';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import UnreadMessage from 'widget/components/UnreadMessage.vue';
|
||||
import { isWidgetColorLighter } from 'shared/helpers/colorHelper';
|
||||
|
||||
export default {
|
||||
name: 'Unread',
|
||||
|
@ -58,11 +67,17 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ unreadMessageCount: 'conversation/getUnreadMessageCount' }),
|
||||
...mapGetters({
|
||||
unreadMessageCount: 'conversation/getUnreadMessageCount',
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
}),
|
||||
sender() {
|
||||
const [firstMessage] = this.messages;
|
||||
return firstMessage.sender || {};
|
||||
},
|
||||
isBackgroundLighter() {
|
||||
return isWidgetColorLighter(this.widgetColor);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openConversationView() {
|
||||
|
@ -134,5 +149,8 @@ export default {
|
|||
color: $color-body;
|
||||
}
|
||||
}
|
||||
.is-background-light {
|
||||
color: $color-body !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
v-else
|
||||
:url="attachment.data_url"
|
||||
:is-in-progress="isInProgress"
|
||||
:widget-color="widgetColor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,19 @@
|
|||
export const stripTrailingSlash = ({ URL }) => {
|
||||
return URL.replace(/\/$/, '');
|
||||
import { URLPattern } from 'urlpattern-polyfill';
|
||||
|
||||
export const isPatternMatchingWithURL = (urlPattern, url) => {
|
||||
let updatedUrlPattern = urlPattern;
|
||||
const locationObj = new URL(url);
|
||||
|
||||
if (updatedUrlPattern.endsWith('/')) {
|
||||
updatedUrlPattern = updatedUrlPattern.slice(0, -1) + '*\\?*\\#*';
|
||||
}
|
||||
|
||||
if (locationObj.pathname.endsWith('/')) {
|
||||
locationObj.pathname = locationObj.pathname.slice(0, -1);
|
||||
}
|
||||
|
||||
const pattern = new URLPattern(updatedUrlPattern);
|
||||
return pattern.test(locationObj.toString());
|
||||
};
|
||||
|
||||
// Format all campaigns
|
||||
|
@ -22,10 +36,7 @@ export const filterCampaigns = ({
|
|||
isInBusinessHours,
|
||||
}) => {
|
||||
return campaigns.filter(campaign => {
|
||||
const hasMatchingURL =
|
||||
stripTrailingSlash({ URL: campaign.url }) ===
|
||||
stripTrailingSlash({ URL: currentURL });
|
||||
if (!hasMatchingURL) {
|
||||
if (!isPatternMatchingWithURL(campaign.url, currentURL)) {
|
||||
return false;
|
||||
}
|
||||
if (campaign.triggerOnlyDuringBusinessHours) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
stripTrailingSlash,
|
||||
formatCampaigns,
|
||||
filterCampaigns,
|
||||
isPatternMatchingWithURL,
|
||||
} from '../campaignHelper';
|
||||
import campaigns from './campaignFixtures';
|
||||
|
||||
|
@ -9,11 +9,35 @@ global.chatwootWebChannel = {
|
|||
workingHoursEnabled: false,
|
||||
};
|
||||
describe('#Campaigns Helper', () => {
|
||||
describe('stripTrailingSlash', () => {
|
||||
it('should return striped trailing slash if url with trailing slash is passed', () => {
|
||||
describe('#isPatternMatchingWithURL', () => {
|
||||
it('returns correct value if a valid URL is passed', () => {
|
||||
expect(
|
||||
stripTrailingSlash({ URL: 'https://www.chatwoot.com/pricing/' })
|
||||
).toBe('https://www.chatwoot.com/pricing');
|
||||
isPatternMatchingWithURL(
|
||||
'https://chatwoot.com/pricing*',
|
||||
'https://chatwoot.com/pricing/'
|
||||
)
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isPatternMatchingWithURL(
|
||||
'https://*.chatwoot.com/pricing/',
|
||||
'https://app.chatwoot.com/pricing/'
|
||||
)
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isPatternMatchingWithURL(
|
||||
'https://{*.}?chatwoot.com/pricing?test=true',
|
||||
'https://app.chatwoot.com/pricing/?test=true'
|
||||
)
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isPatternMatchingWithURL(
|
||||
'https://{*.}?chatwoot.com/pricing*\\?*',
|
||||
'https://chatwoot.com/pricing/?test=true'
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -86,7 +86,8 @@ class Campaign < ApplicationRecord
|
|||
def validate_url
|
||||
return unless trigger_rules['url']
|
||||
|
||||
errors.add(:url, 'invalid') if inbox.inbox_type == 'Website' && !url_valid?(trigger_rules['url'])
|
||||
use_http_protocol = trigger_rules['url'].starts_with?('http://') || trigger_rules['url'].starts_with?('https://')
|
||||
errors.add(:url, 'invalid') if inbox.inbox_type == 'Website' && !use_http_protocol
|
||||
end
|
||||
|
||||
def prevent_completed_campaign_from_update
|
||||
|
|
|
@ -16,7 +16,7 @@ module Featurable
|
|||
include FlagShihTzu
|
||||
has_flags FEATURES.merge(column: 'feature_flags').merge(QUERY_MODE)
|
||||
|
||||
after_initialize :enable_default_features
|
||||
before_create :enable_default_features
|
||||
end
|
||||
|
||||
def enable_features(*names)
|
||||
|
|
|
@ -3,7 +3,7 @@ json.name resource.name
|
|||
json.locale resource.locale
|
||||
json.domain resource.domain
|
||||
json.support_email resource.support_email
|
||||
json.enabled_features resource.enabled_features
|
||||
json.features resource.enabled_features
|
||||
json.custom_attributes resource.custom_attributes
|
||||
json.limits resource.limits
|
||||
json.status resource.status
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# DO NOT change the order of features EVER
|
||||
- name: inbound_emails
|
||||
enabled: true
|
||||
- name: channel_website
|
||||
enabled: true
|
||||
- name: channel_email
|
||||
enabled: true
|
||||
- name: channel_facebook
|
||||
|
@ -13,6 +15,8 @@
|
|||
enabled: false
|
||||
- name: email_continuity_on_api_channel
|
||||
enabled: false
|
||||
- name: campaigns
|
||||
enabled: true
|
||||
- name: help_center
|
||||
enabled: true
|
||||
- name: agent_bots
|
||||
|
|
13
db/migrate/20221029202300_add_two_features_to_accounts.rb
Normal file
13
db/migrate/20221029202300_add_two_features_to_accounts.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
class AddTwoFeaturesToAccounts < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
Account.find_in_batches do |account_batch|
|
||||
account_batch.each do |account|
|
||||
account.enable_features(
|
||||
'campaigns',
|
||||
'channel_website'
|
||||
)
|
||||
account.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -55,6 +55,7 @@
|
|||
"tailwindcss": "^1.9.6",
|
||||
"turbolinks": "^5.2.0",
|
||||
"url-loader": "^2.0.0",
|
||||
"urlpattern-polyfill": "^6.0.2",
|
||||
"v-tooltip": "~2.1.3",
|
||||
"videojs-record": "^4.5.0",
|
||||
"vue": "2.6.12",
|
||||
|
|
|
@ -42,28 +42,29 @@ RSpec.describe 'Platform Accounts API', type: :request do
|
|||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['name']).to eq('Test Account')
|
||||
expect(json_response['locale']).to eq('es')
|
||||
expect(json_response['enabled_features']['agent_management']).to be(true)
|
||||
expect(json_response['features']['agent_management']).to be(true)
|
||||
end
|
||||
|
||||
it 'creates an account with feature flags' do
|
||||
InstallationConfig.where(name: 'ACCOUNT_LEVEL_FEATURE_DEFAULTS').first_or_create!(value: [{ 'name' => 'inbox_management',
|
||||
'enabled' => true },
|
||||
{ 'name' => 'disable_branding',
|
||||
'enabled' => true }])
|
||||
'enabled' => true },
|
||||
{ 'name' => 'help_center',
|
||||
'enabled' => false }])
|
||||
|
||||
post '/platform/api/v1/accounts', params: { name: 'Test Account', features: {
|
||||
ip_lookup: true,
|
||||
help_center: true,
|
||||
disable_branding: false
|
||||
|
||||
} }, headers: { api_access_token: platform_app.access_token.token }, as: :json
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['name']).to include('Test Account')
|
||||
expect(json_response['enabled_features']['inbox_management']).to be(true)
|
||||
expect(json_response['enabled_features']['ip_lookup']).to be(true)
|
||||
expect(json_response['enabled_features']['help_center']).to be(true)
|
||||
expect(json_response['enabled_features']['disable_branding']).to be_nil
|
||||
expect(json_response['features']['inbox_management']).to be(true)
|
||||
expect(json_response['features']['ip_lookup']).to be(true)
|
||||
expect(json_response['features']['help_center']).to be(true)
|
||||
expect(json_response['features']['disable_branding']).to be_nil
|
||||
end
|
||||
|
||||
it 'creates an account with limits settings' do
|
||||
|
|
|
@ -16381,6 +16381,13 @@ url@^0.11.0:
|
|||
punycode "1.3.2"
|
||||
querystring "0.2.0"
|
||||
|
||||
urlpattern-polyfill@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-6.0.2.tgz#a193fe773459865a2a5c93b246bb794b13d07256"
|
||||
integrity sha512-5vZjFlH9ofROmuWmXM9yj2wljYKgWstGwe8YTyiqM7hVum/g9LyCizPZtb3UqsuppVwety9QJmfc42VggLpTgg==
|
||||
dependencies:
|
||||
braces "^3.0.2"
|
||||
|
||||
use@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||
|
|
Loading…
Reference in a new issue