this.$refs.messageInput.focus());
@@ -536,10 +569,26 @@ export default {
this.attachedFiles = [];
this.ccEmails = '';
this.bccEmails = '';
+ this.isRecordingAudio = false;
},
toggleEmojiPicker() {
this.showEmojiPicker = !this.showEmojiPicker;
},
+ toggleAudioRecorder() {
+ this.isRecordingAudio = !this.isRecordingAudio;
+ this.isRecorderAudioStopped = !this.isRecordingAudio;
+ if (!this.isRecordingAudio) {
+ this.clearMessage();
+ }
+ },
+ toggleAudioRecorderPlayPause() {
+ if (this.isRecordingAudio && !this.isRecorderAudioStopped) {
+ this.isRecorderAudioStopped = true;
+ this.$refs.audioRecorderInput.stopAudioRecording();
+ } else if (this.isRecordingAudio && this.isRecorderAudioStopped) {
+ this.$refs.audioRecorderInput.playPause();
+ }
+ },
hideEmojiPicker() {
if (this.showEmojiPicker) {
this.toggleEmojiPicker();
@@ -560,6 +609,20 @@ export default {
onFocus() {
this.isFocused = true;
},
+ onStateRecorderTimerChanged(time) {
+ this.recordingAudioDuration = time;
+ },
+ onStateRecorderChanged(state) {
+ this.recordingAudioState = state;
+ if (state.includes('notallowederror')) {
+ this.toggleAudioRecorder();
+ }
+ },
+ onRecorderBlob(file) {
+ if (file) {
+ this.onFileUpload(file);
+ }
+ },
toggleTyping(status) {
const conversationId = this.currentChat.id;
const isPrivate = this.isPrivate;
@@ -701,6 +764,8 @@ export default {
justify-content: space-between;
border: 1px dashed var(--s-100);
border-radius: var(--border-radius-small);
+ max-height: 8vh;
+ overflow: auto;
&:hover {
background: var(--s-25);
diff --git a/app/javascript/dashboard/components/widgets/modal/ConfirmationModal.vue b/app/javascript/dashboard/components/widgets/modal/ConfirmationModal.vue
new file mode 100644
index 000000000..0c1472a84
--- /dev/null
+++ b/app/javascript/dashboard/components/widgets/modal/ConfirmationModal.vue
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+ {{ description }}
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/helper/URLHelper.js b/app/javascript/dashboard/helper/URLHelper.js
index e229ce7e4..583a99c7c 100644
--- a/app/javascript/dashboard/helper/URLHelper.js
+++ b/app/javascript/dashboard/helper/URLHelper.js
@@ -1,10 +1,22 @@
import queryString from 'query-string';
+import { DEFAULT_REDIRECT_URL } from '../constants';
export const frontendURL = (path, params) => {
const stringifiedParams = params ? `?${queryString.stringify(params)}` : '';
return `/app/${path}${stringifiedParams}`;
};
+export const getLoginRedirectURL = (ssoAccountId, user) => {
+ const { accounts = [] } = user || {};
+ const ssoAccount = accounts.find(
+ account => account.id === Number(ssoAccountId)
+ );
+ if (ssoAccount) {
+ return frontendURL(`accounts/${ssoAccountId}/dashboard`);
+ }
+ return DEFAULT_REDIRECT_URL;
+};
+
export const conversationUrl = ({
accountId,
activeInbox,
diff --git a/app/javascript/dashboard/helper/specs/URLHelper.spec.js b/app/javascript/dashboard/helper/specs/URLHelper.spec.js
index ade735642..832d90577 100644
--- a/app/javascript/dashboard/helper/specs/URLHelper.spec.js
+++ b/app/javascript/dashboard/helper/specs/URLHelper.spec.js
@@ -3,6 +3,7 @@ import {
conversationUrl,
accountIdFromPathname,
isValidURL,
+ getLoginRedirectURL,
} from '../URLHelper';
describe('#URL Helpers', () => {
@@ -58,4 +59,24 @@ describe('#URL Helpers', () => {
expect(isValidURL('alert.window')).toBe(false);
});
});
+
+ describe('getLoginRedirectURL', () => {
+ it('should return correct Account URL if account id is present', () => {
+ expect(
+ getLoginRedirectURL('7500', {
+ accounts: [{ id: 7500, name: 'Test Account 7500' }],
+ })
+ ).toBe('/app/accounts/7500/dashboard');
+ });
+
+ it('should return default URL if account id is not present', () => {
+ expect(getLoginRedirectURL('7500', {})).toBe('/app/');
+ expect(
+ getLoginRedirectURL('7500', {
+ accounts: [{ id: '7501', name: 'Test Account 7501' }],
+ })
+ ).toBe('/app/');
+ expect(getLoginRedirectURL('7500', null)).toBe('/app/');
+ });
+ });
});
diff --git a/app/javascript/dashboard/i18n/locale/en/advancedFilters.json b/app/javascript/dashboard/i18n/locale/en/advancedFilters.json
index f00973f29..bdbe2c7cd 100644
--- a/app/javascript/dashboard/i18n/locale/en/advancedFilters.json
+++ b/app/javascript/dashboard/i18n/locale/en/advancedFilters.json
@@ -23,6 +23,10 @@
"is_greater_than": "Is greater than",
"is_lesser_than": "Is lesser than"
},
+ "ATTRIBUTE_LABELS": {
+ "TRUE": "True",
+ "FALSE": "False"
+ },
"ATTRIBUTES": {
"STATUS": "Status",
"ASSIGNEE_NAME": "Assignee Name",
diff --git a/app/javascript/dashboard/i18n/locale/en/automation.json b/app/javascript/dashboard/i18n/locale/en/automation.json
index ce95b861c..8c92467bd 100644
--- a/app/javascript/dashboard/i18n/locale/en/automation.json
+++ b/app/javascript/dashboard/i18n/locale/en/automation.json
@@ -90,6 +90,18 @@
},
"ACTION": {
"DELETE_MESSAGE": "You need to have atleast one action to save"
+ },
+ "TOGGLE": {
+ "ACTIVATION_TITLE": "Activate Automation Rule",
+ "DEACTIVATION_TITLE": "Deactivate Automation Rule",
+ "ACTIVATION_DESCRIPTION": "This action will activate the automation rule '{automationName}'. Are you sure you want to proceed?",
+ "DEACTIVATION_DESCRIPTION": "This action will deactivate the automation rule '{automationName}'. Are you sure you want to proceed?",
+ "ACTIVATION_SUCCESFUL": "Automation Rule Activated Successfully",
+ "DEACTIVATION_SUCCESFUL": "Automation Rule Deactivated Successfully",
+ "ACTIVATION_ERROR": "Could not Activate Automation, Please try again later",
+ "DEACTIVATION_ERROR": "Could not Deactivate Automation, Please try again later",
+ "CONFIRMATION_LABEL": "Yes",
+ "CANCEL_LABEL": "No"
}
}
}
diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json
index 96ba4b429..ac25a5aed 100644
--- a/app/javascript/dashboard/i18n/locale/en/conversation.json
+++ b/app/javascript/dashboard/i18n/locale/en/conversation.json
@@ -74,8 +74,14 @@
"TIP_FORMAT_ICON": "Show rich text editor",
"TIP_EMOJI_ICON": "Show emoji selector",
"TIP_ATTACH_ICON": "Attach files",
+ "TIP_AUDIORECORDER_ICON": "Record audio",
+ "TIP_AUDIORECORDER_PERMISSION": "Allow access to audio",
+ "TIP_AUDIORECORDER_ERROR": "Could not open the audio",
"ENTER_TO_SEND": "Enter to send",
"DRAG_DROP": "Drag and drop here to attach",
+ "START_AUDIO_RECORDING": "Start audio recording",
+ "STOP_AUDIO_RECORDING": "Stop audio recording",
+ "": "",
"EMAIL_HEAD": {
"ADD_BCC": "Add bcc",
"CC": {
diff --git a/app/javascript/dashboard/i18n/locale/en/report.json b/app/javascript/dashboard/i18n/locale/en/report.json
index 229c7b068..053c730e1 100644
--- a/app/javascript/dashboard/i18n/locale/en/report.json
+++ b/app/javascript/dashboard/i18n/locale/en/report.json
@@ -333,6 +333,11 @@
"CSAT_REPORTS": {
"HEADER": "CSAT Reports",
"NO_RECORDS": "There are no CSAT survey responses available.",
+ "FILTERS": {
+ "AGENTS": {
+ "PLACEHOLDER": "Choose Agents"
+ }
+ },
"TABLE": {
"HEADER": {
"CONTACT_NAME": "Contact",
diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json
index d353e6b6e..5727e09d0 100644
--- a/app/javascript/dashboard/i18n/locale/en/settings.json
+++ b/app/javascript/dashboard/i18n/locale/en/settings.json
@@ -178,7 +178,8 @@
"REPORTS_LABEL": "Labels",
"REPORTS_INBOX": "Inbox",
"REPORTS_TEAM": "Team",
- "SET_AVAILABILITY_TITLE": "Set yourself as"
+ "SET_AVAILABILITY_TITLE": "Set yourself as",
+ "BETA": "Beta"
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.",
diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsAdvancedFilters.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsAdvancedFilters.vue
index 04ba42f33..3f5162292 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsAdvancedFilters.vue
+++ b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsAdvancedFilters.vue
@@ -147,6 +147,12 @@ export default {
switch (key) {
case 'date':
return 'date';
+ case 'text':
+ return 'plain_text';
+ case 'list':
+ return 'search_select';
+ case 'checkbox':
+ return 'search_select';
default:
return 'plain_text';
}
@@ -164,6 +170,44 @@ export default {
return type.filterOperators;
},
getDropdownValues(type) {
+ const allCustomAttributes = this.$store.getters[
+ 'attributes/getAttributesByModel'
+ ](this.attributeModel);
+ const isCustomAttributeCheckbox = allCustomAttributes.find(attr => {
+ return (
+ attr.attribute_key === type &&
+ attr.attribute_display_type === 'checkbox'
+ );
+ });
+ if (isCustomAttributeCheckbox) {
+ return [
+ {
+ id: true,
+ name: this.$t('FILTER.ATTRIBUTE_LABELS.TRUE'),
+ },
+ {
+ id: false,
+ name: this.$t('FILTER.ATTRIBUTE_LABELS.FALSE'),
+ },
+ ];
+ }
+
+ const isCustomAttributeList = allCustomAttributes.find(attr => {
+ return (
+ attr.attribute_key === type && attr.attribute_display_type === 'list'
+ );
+ });
+
+ if (isCustomAttributeList) {
+ return allCustomAttributes
+ .find(attr => attr.attribute_key === type)
+ .attribute_values.map(item => {
+ return {
+ id: item,
+ name: item,
+ };
+ });
+ }
switch (type) {
case 'country_code':
return countries;
diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsView.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsView.vue
index 7536c0d24..dbac2f0bc 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsView.vue
+++ b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsView.vue
@@ -253,7 +253,7 @@ export default {
this.$store.dispatch('contacts/get', requestParams);
} else {
this.$store.dispatch('contacts/search', {
- search: value,
+ search: encodeURIComponent(value),
...requestParams,
});
}
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue
index 2b3dae832..11e0fb675 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue
@@ -130,11 +130,13 @@ export default {
facebook: '',
twitter: '',
linkedin: '',
+ github: '',
},
socialProfileKeys: [
{ key: 'facebook', prefixURL: 'https://facebook.com/' },
{ key: 'twitter', prefixURL: 'https://twitter.com/' },
{ key: 'linkedin', prefixURL: 'https://linkedin.com/' },
+ { key: 'github', prefixURL: 'https://github.com/' },
],
};
},
@@ -183,6 +185,7 @@ export default {
twitter: socialProfiles.twitter || twitterScreenName || '',
facebook: socialProfiles.facebook || '',
linkedin: socialProfiles.linkedin || '',
+ github: socialProfiles.github || '',
};
},
getContactObject() {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue
index be79dd4f5..811fc95c6 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue
@@ -34,12 +34,19 @@
{{ automation.name }} |
{{ automation.description }} |
-
-
+
|
{{ readableTime(automation.created_on) }} |
@@ -120,6 +127,11 @@
@saveAutomation="submitAutomation"
/>
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue
index 1f2839048..9d90bb9e2 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue
@@ -10,6 +10,7 @@
@@ -41,6 +41,26 @@
@input="changeFilterSelection"
/>
+
+
+
diff --git a/app/javascript/dashboard/routes/index.js b/app/javascript/dashboard/routes/index.js
index b2d19cf7f..14ef77bc3 100644
--- a/app/javascript/dashboard/routes/index.js
+++ b/app/javascript/dashboard/routes/index.js
@@ -5,6 +5,7 @@ import login from './login/login.routes';
import dashboard from './dashboard/dashboard.routes';
import authRoute from './auth/auth.routes';
import { frontendURL } from '../helper/URLHelper';
+import { clearBrowserSessionCookies } from '../store/utils/api';
const routes = [
...login.routes,
@@ -101,6 +102,13 @@ export const validateAuthenticateRoutePermission = (to, from, next) => {
return nextRoute ? next(frontendURL(nextRoute)) : next();
};
+const validateSSOLoginParams = to => {
+ const isLoginRoute = to.name === 'login';
+ const { email, sso_auth_token: ssoAuthToken } = to.query || {};
+ const hasValidSSOParams = email && ssoAuthToken;
+ return isLoginRoute && hasValidSSOParams;
+};
+
const validateRouteAccess = (to, from, next) => {
if (
window.chatwootConfig.signupEnabled !== 'true' &&
@@ -111,6 +119,11 @@ const validateRouteAccess = (to, from, next) => {
next(frontendURL(`accounts/${user.account_id}/dashboard`));
}
+ if (validateSSOLoginParams(to)) {
+ clearBrowserSessionCookies();
+ return next();
+ }
+
if (authIgnoreRoutes.includes(to.name)) {
return next();
}
diff --git a/app/javascript/dashboard/routes/login/Login.vue b/app/javascript/dashboard/routes/login/Login.vue
index 92fea2e36..428974af7 100644
--- a/app/javascript/dashboard/routes/login/Login.vue
+++ b/app/javascript/dashboard/routes/login/Login.vue
@@ -80,6 +80,7 @@ export default {
mixins: [globalConfigMixin],
props: {
ssoAuthToken: { type: String, default: '' },
+ ssoAccountId: { type: String, default: '' },
redirectUrl: { type: String, default: '' },
config: { type: String, default: '' },
email: { type: String, default: '' },
@@ -138,6 +139,7 @@ export default {
: this.credentials.email,
password: this.credentials.password,
sso_auth_token: this.ssoAuthToken,
+ ssoAccountId: this.ssoAccountId,
};
this.$store
.dispatch('login', credentials)
diff --git a/app/javascript/dashboard/routes/login/login.routes.js b/app/javascript/dashboard/routes/login/login.routes.js
index c53cffa92..f43e632bf 100644
--- a/app/javascript/dashboard/routes/login/login.routes.js
+++ b/app/javascript/dashboard/routes/login/login.routes.js
@@ -12,6 +12,7 @@ export default {
email: route.query.email,
ssoAuthToken: route.query.sso_auth_token,
redirectUrl: route.query.route_url,
+ ssoAccountId: route.query.sso_account_id,
}),
},
],
diff --git a/app/javascript/dashboard/store/modules/auth.js b/app/javascript/dashboard/store/modules/auth.js
index d9ec54564..5b2feef11 100644
--- a/app/javascript/dashboard/store/modules/auth.js
+++ b/app/javascript/dashboard/store/modules/auth.js
@@ -6,7 +6,7 @@ import authAPI from '../../api/auth';
import createAxios from '../../helper/APIHelper';
import actionCable from '../../helper/actionCable';
import { setUser, getHeaderExpiry, clearCookiesOnLogout } from '../utils/api';
-import { DEFAULT_REDIRECT_URL } from '../../constants';
+import { getLoginRedirectURL } from '../../helper/URLHelper';
const state = {
currentUser: {
@@ -88,15 +88,16 @@ export const getters = {
// actions
export const actions = {
- login({ commit }, credentials) {
+ login({ commit }, { ssoAccountId, ...credentials }) {
return new Promise((resolve, reject) => {
authAPI
.login(credentials)
- .then(() => {
+ .then(response => {
commit(types.default.SET_CURRENT_USER);
window.axios = createAxios(axios);
actionCable.init(Vue);
- window.location = DEFAULT_REDIRECT_URL;
+
+ window.location = getLoginRedirectURL(ssoAccountId, response.data);
resolve();
})
.catch(error => {
diff --git a/app/javascript/dashboard/store/modules/csat.js b/app/javascript/dashboard/store/modules/csat.js
index d38b212be..7bc1dad6d 100644
--- a/app/javascript/dashboard/store/modules/csat.js
+++ b/app/javascript/dashboard/store/modules/csat.js
@@ -82,10 +82,13 @@ export const getters = {
};
export const actions = {
- get: async function getResponses({ commit }, { page = 1, from, to } = {}) {
+ get: async function getResponses(
+ { commit },
+ { page = 1, from, to, user_ids } = {}
+ ) {
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetching: true });
try {
- const response = await CSATReports.get({ page, from, to });
+ const response = await CSATReports.get({ page, from, to, user_ids });
commit(types.SET_CSAT_RESPONSE, response.data);
} catch (error) {
// Ignore error
@@ -93,10 +96,10 @@ export const actions = {
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetching: false });
}
},
- getMetrics: async function getMetrics({ commit }, { from, to }) {
+ getMetrics: async function getMetrics({ commit }, { from, to, user_ids }) {
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetchingMetrics: true });
try {
- const response = await CSATReports.getMetrics({ from, to });
+ const response = await CSATReports.getMetrics({ from, to, user_ids });
commit(types.SET_CSAT_RESPONSE_METRICS, response.data);
} catch (error) {
// Ignore error
diff --git a/app/javascript/dashboard/store/utils/api.js b/app/javascript/dashboard/store/utils/api.js
index 651f0cd61..37fd68c7b 100644
--- a/app/javascript/dashboard/store/utils/api.js
+++ b/app/javascript/dashboard/store/utils/api.js
@@ -38,13 +38,15 @@ export const setAuthCredentials = response => {
setUser(response.data.data, expiryDate);
};
+export const clearBrowserSessionCookies = () => {
+ Cookies.remove('auth_data');
+ Cookies.remove('user');
+};
+
export const clearCookiesOnLogout = () => {
window.bus.$emit(CHATWOOT_RESET);
window.bus.$emit(ANALYTICS_RESET);
-
- Cookies.remove('auth_data');
- Cookies.remove('user');
-
+ clearBrowserSessionCookies();
const globalConfig = window.globalConfig || {};
const logoutRedirectLink =
globalConfig.LOGOUT_REDIRECT_LINK || frontendURL('login');
diff --git a/app/javascript/sdk/IFrameHelper.js b/app/javascript/sdk/IFrameHelper.js
index b5cc35d7c..a7f9dc70a 100644
--- a/app/javascript/sdk/IFrameHelper.js
+++ b/app/javascript/sdk/IFrameHelper.js
@@ -152,7 +152,7 @@ export const IFrameHelper = {
if (window.$chatwoot.user) {
IFrameHelper.sendMessage('set-user', window.$chatwoot.user);
}
-
+
dispatchWindowEvent({ eventName: CHATWOOT_READY });
window.playAudioAlert = () => {};
diff --git a/app/javascript/shared/components/FluentIcon/dashboard-icons.json b/app/javascript/shared/components/FluentIcon/dashboard-icons.json
index bbc1979e0..a6467e894 100644
--- a/app/javascript/shared/components/FluentIcon/dashboard-icons.json
+++ b/app/javascript/shared/components/FluentIcon/dashboard-icons.json
@@ -86,6 +86,11 @@
"merge-outline": "M3 6.75A.75.75 0 0 1 3.75 6h4.5a.75.75 0 0 1 .53.22L13.56 11h5.878L15.72 7.28a.75.75 0 1 1 1.06-1.06l4.998 5a.75.75 0 0 1 0 1.06l-4.998 5a.75.75 0 1 1-1.06-1.06l3.718-3.72H13.56l-4.78 4.78a.75.75 0 0 1-.531.22h-4.5a.75.75 0 0 1 0-1.5h4.19l4.25-4.25L7.94 7.5H3.75A.75.75 0 0 1 3 6.75Z",
"more-horizontal-outline": "M7.75 12a1.75 1.75 0 1 1-3.5 0 1.75 1.75 0 0 1 3.5 0ZM13.75 12a1.75 1.75 0 1 1-3.5 0 1.75 1.75 0 0 1 3.5 0ZM18 13.75a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5Z",
"more-vertical-outline": "M12 7.75a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5ZM12 13.75a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5ZM10.25 18a1.75 1.75 0 1 0 3.5 0 1.75 1.75 0 0 0-3.5 0Z",
+ "microphone-outline": "M12,2A3,3 0 0,1 15,5V11A3,3 0 0,1 12,14A3,3 0 0,1 9,11V5A3,3 0 0,1 12,2M19,11C19,14.53 16.39,17.44 13,17.93V21H11V17.93C7.61,17.44 5,14.53 5,11H7A5,5 0 0,0 12,16A5,5 0 0,0 17,11H19Z",
+ "microphone-off-outline": "M19,11C19,12.19 18.66,13.3 18.1,14.28L16.87,13.05C17.14,12.43 17.3,11.74 17.3,11H19M15,11.16L9,5.18V5A3,3 0 0,1 12,2A3,3 0 0,1 15,5V11L15,11.16M4.27,3L21,19.73L19.73,21L15.54,16.81C14.77,17.27 13.91,17.58 13,17.72V21H11V17.72C7.72,17.23 5,14.41 5,11H6.7C6.7,14 9.24,16.1 12,16.1C12.81,16.1 13.6,15.91 14.31,15.58L12.65,13.92L12,14A3,3 0 0,1 9,11V10.28L3,4.27L4.27,3Z",
+ "microphone-stop-outline": "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4M9,9V15H15V9",
+ "microphone-pause-outline": "M13,16V8H15V16H13M9,16V8H11V16H9M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z",
+ "microphone-play-outline": "M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M10,16.5L16,12L10,7.5V16.5Z",
"number-symbol-outline": "M10.987 2.89a.75.75 0 1 0-1.474-.28L8.494 7.999 3.75 8a.75.75 0 1 0 0 1.5l4.46-.002-.946 5-4.514.002a.75.75 0 0 0 0 1.5l4.23-.002-.967 5.116a.75.75 0 1 0 1.474.278l1.02-5.395 5.474-.002-.968 5.119a.75.75 0 1 0 1.474.278l1.021-5.398 4.742-.002a.75.75 0 1 0 0-1.5l-4.458.002.946-5 4.512-.002a.75.75 0 1 0 0-1.5l-4.229.002.966-5.104a.75.75 0 0 0-1.474-.28l-1.018 5.385-5.474.002.966-5.107Zm-1.25 6.608 5.474-.003-.946 5-5.474.002.946-5Z",
"open-outline": "M6.25 4.5A1.75 1.75 0 0 0 4.5 6.25v11.5c0 .966.783 1.75 1.75 1.75h11.5a1.75 1.75 0 0 0 1.75-1.75v-4a.75.75 0 0 1 1.5 0v4A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h4a.75.75 0 0 1 0 1.5h-4ZM13 3.75a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 .75.75v6.5a.75.75 0 0 1-1.5 0V5.56l-5.22 5.22a.75.75 0 0 1-1.06-1.06l5.22-5.22h-4.69a.75.75 0 0 1-.75-.75Z",
"people-outline": "M4 13.999 13 14a2 2 0 0 1 1.995 1.85L15 16v1.5C14.999 21 11.284 22 8.5 22c-2.722 0-6.335-.956-6.495-4.27L2 17.5v-1.501c0-1.054.816-1.918 1.85-1.995L4 14ZM15.22 14H20c1.054 0 1.918.816 1.994 1.85L22 16v1c-.001 3.062-2.858 4-5 4a7.16 7.16 0 0 1-2.14-.322c.336-.386.607-.827.802-1.327A6.19 6.19 0 0 0 17 19.5l.267-.006c.985-.043 3.086-.363 3.226-2.289L20.5 17v-1a.501.501 0 0 0-.41-.492L20 15.5h-4.051a2.957 2.957 0 0 0-.595-1.34L15.22 14H20h-4.78ZM4 15.499l-.1.01a.51.51 0 0 0-.254.136.506.506 0 0 0-.136.253l-.01.101V17.5c0 1.009.45 1.722 1.417 2.242.826.445 2.003.714 3.266.753l.317.005.317-.005c1.263-.039 2.439-.308 3.266-.753.906-.488 1.359-1.145 1.412-2.057l.005-.186V16a.501.501 0 0 0-.41-.492L13 15.5l-9-.001ZM8.5 3a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Zm9 2a3.5 3.5 0 1 1 0 7 3.5 3.5 0 0 1 0-7Zm-9-.5c-1.654 0-3 1.346-3 3s1.346 3 3 3 3-1.346 3-3-1.346-3-3-3Zm9 2c-1.103 0-2 .897-2 2s.897 2 2 2 2-.897 2-2-.897-2-2-2Z",
@@ -120,6 +125,7 @@
"brand-telegram-outline": "M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z",
"brand-twitter-outline": "M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z",
"brand-whatsapp-outline": "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z",
+ "brand-github-outline": "M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385c.6.105.825-.255.825-.57c0-.285-.015-1.23-.015-2.235c-3.015.555-3.795-.735-4.035-1.41c-.135-.345-.72-1.41-1.23-1.695c-.42-.225-1.02-.78-.015-.795c.945-.015 1.62.87 1.845 1.23c1.08 1.815 2.805 1.305 3.495.99c.105-.78.42-1.305.765-1.605c-2.67-.3-5.46-1.335-5.46-5.925c0-1.305.465-2.385 1.23-3.225c-.12-.3-.54-1.53.12-3.18c0 0 1.005-.315 3.3 1.23c.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23c.66 1.65.24 2.88.12 3.18c.765.84 1.23 1.905 1.23 3.225c0 4.605-2.805 5.625-5.475 5.925c.435.375.81 1.095.81 2.22c0 1.605-.015 2.895-.015 3.3c0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12Z",
"add-solid": "M11.883 3.007 12 3a1 1 0 0 1 .993.883L13 4v7h7a1 1 0 0 1 .993.883L21 12a1 1 0 0 1-.883.993L20 13h-7v7a1 1 0 0 1-.883.993L12 21a1 1 0 0 1-.993-.883L11 20v-7H4a1 1 0 0 1-.993-.883L3 12a1 1 0 0 1 .883-.993L4 11h7V4a1 1 0 0 1 .883-.993L12 3l-.117.007Z",
"subtract-solid": "M3.997 13H20a1 1 0 1 0 0-2H3.997a1 1 0 1 0 0 2Z"
}
diff --git a/app/javascript/shared/helpers/FileHelper.js b/app/javascript/shared/helpers/FileHelper.js
index a784b2ba2..d9ca9f943 100644
--- a/app/javascript/shared/helpers/FileHelper.js
+++ b/app/javascript/shared/helpers/FileHelper.js
@@ -19,7 +19,7 @@ export const fileSizeInMegaBytes = bytes => {
};
export const checkFileSizeLimit = (file, maximumUploadLimit) => {
- const fileSize = file?.file?.size;
+ const fileSize = file?.file?.size || file?.size;
const fileSizeInMB = fileSizeInMegaBytes(fileSize);
return fileSizeInMB <= maximumUploadLimit;
};
diff --git a/app/javascript/shared/helpers/MessageFormatter.js b/app/javascript/shared/helpers/MessageFormatter.js
index ba45a02b6..f57bbd1d5 100644
--- a/app/javascript/shared/helpers/MessageFormatter.js
+++ b/app/javascript/shared/helpers/MessageFormatter.js
@@ -1,4 +1,4 @@
-import marked from 'marked';
+import { marked } from 'marked';
import DOMPurify from 'dompurify';
import { escapeHtml } from './HTMLSanitizer';
diff --git a/app/javascript/shared/mixins/inboxMixin.js b/app/javascript/shared/mixins/inboxMixin.js
index aebbeebc1..f0417ae93 100644
--- a/app/javascript/shared/mixins/inboxMixin.js
+++ b/app/javascript/shared/mixins/inboxMixin.js
@@ -44,6 +44,9 @@ export default {
const { medium: medium = '' } = this.inbox;
return this.isATwilioChannel && medium === 'sms';
},
+ isASmsInbox() {
+ return this.channelType === INBOX_TYPES.SMS || this.isATwilioSMSChannel;
+ },
isATwilioWhatsappChannel() {
const { medium: medium = '' } = this.inbox;
return this.isATwilioChannel && medium === 'whatsapp';
diff --git a/app/javascript/shared/mixins/specs/inboxMixin.spec.js b/app/javascript/shared/mixins/specs/inboxMixin.spec.js
index f128a44d8..dc7cc38cc 100644
--- a/app/javascript/shared/mixins/specs/inboxMixin.spec.js
+++ b/app/javascript/shared/mixins/specs/inboxMixin.spec.js
@@ -62,6 +62,18 @@ describe('inboxMixin', () => {
expect(wrapper.vm.isAWebWidgetInbox).toBe(true);
});
+ it('isASmsInbox returns true if channel type is sms', () => {
+ const Component = {
+ render() {},
+ mixins: [inboxMixin],
+ data() {
+ return { inbox: { channel_type: 'Channel::Sms' } };
+ },
+ };
+ const wrapper = shallowMount(Component);
+ expect(wrapper.vm.isASmsInbox).toBe(true);
+ });
+
it('isATwilioChannel returns true if channel type is Twilio', () => {
const Component = {
render() {},
@@ -94,6 +106,7 @@ describe('inboxMixin', () => {
const wrapper = shallowMount(Component);
expect(wrapper.vm.isATwilioChannel).toBe(true);
expect(wrapper.vm.isATwilioSMSChannel).toBe(true);
+ expect(wrapper.vm.isASmsInbox).toBe(true);
});
it('isATwilioWhatsappChannel returns true if channel type is Twilio and medium is whatsapp', () => {
diff --git a/app/javascript/widget/components/ChatFooter.vue b/app/javascript/widget/components/ChatFooter.vue
index 423eb2642..7ed33acbd 100755
--- a/app/javascript/widget/components/ChatFooter.vue
+++ b/app/javascript/widget/components/ChatFooter.vue
@@ -37,12 +37,13 @@ import CustomButton from 'shared/components/Button';
import ChatInputWrap from 'widget/components/ChatInputWrap.vue';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import { sendEmailTranscript } from 'widget/api/conversation';
-
+import routerMixin from 'widget/mixins/routerMixin';
export default {
components: {
ChatInputWrap,
CustomButton,
},
+ mixins: [routerMixin],
props: {
msg: {
type: String,
@@ -53,7 +54,7 @@ export default {
...mapGetters({
conversationAttributes: 'conversationAttributes/getConversationParams',
widgetColor: 'appConfig/getWidgetColor',
- getConversationSize: 'conversation/getConversationSize',
+ conversationSize: 'conversation/getConversationSize',
currentUser: 'contacts/getCurrentUser',
isWidgetStyleFlat: 'appConfig/isWidgetStyleFlat',
}),
@@ -80,12 +81,11 @@ export default {
'clearConversationAttributes',
]),
async handleSendMessage(content) {
- const conversationSize = this.getConversationSize;
await this.sendMessage({
content,
});
// Update conversation attributes on new conversation
- if (conversationSize === 0) {
+ if (this.conversationSize === 0) {
this.getAttributes();
}
},
@@ -95,7 +95,12 @@ export default {
startNewConversation() {
this.clearConversations();
this.clearConversationAttributes();
- window.bus.$emit(BUS_EVENTS.START_NEW_CONVERSATION);
+
+ // To create a new conversation, we are redirecting
+ // the user to pre-chat with contact fields disabled
+ // Pass disableContactFields params to the route
+ // This would disable the contact fields in the pre-chat form
+ this.replaceRoute('prechat-form', { disableContactFields: true });
},
async sendTranscript() {
const { email } = this.currentUser;
diff --git a/app/javascript/widget/components/PreChat/Form.vue b/app/javascript/widget/components/PreChat/Form.vue
index c053a58f6..7fdf33375 100644
--- a/app/javascript/widget/components/PreChat/Form.vue
+++ b/app/javascript/widget/components/PreChat/Form.vue
@@ -10,7 +10,7 @@
{{ headerMessage }}
({}),
},
+ disableContactFields: {
+ type: Boolean,
+ default: false,
+ },
},
validations() {
const identityValidations = {
@@ -99,7 +103,7 @@ export default {
if (this.hasActiveCampaign) {
return identityValidations;
}
- if (this.options.requireEmail) {
+ if (this.areContactFieldsVisible) {
return {
...identityValidations,
...messageValidation,
@@ -135,6 +139,9 @@ export default {
}
return this.options.preChatMessage;
},
+ areContactFieldsVisible() {
+ return this.options.requireEmail && !this.disableContactFields;
+ },
},
methods: {
onSubmit() {
diff --git a/app/javascript/widget/mixins/routerMixin.js b/app/javascript/widget/mixins/routerMixin.js
index f75cc65e1..b3b37f6fd 100644
--- a/app/javascript/widget/mixins/routerMixin.js
+++ b/app/javascript/widget/mixins/routerMixin.js
@@ -1,8 +1,8 @@
export default {
methods: {
- async replaceRoute(name) {
+ async replaceRoute(name, params = {}) {
if (this.$route.name !== name) {
- return this.$router.replace({ name });
+ return this.$router.replace({ name, params });
}
return undefined;
},
diff --git a/app/javascript/widget/views/Home.vue b/app/javascript/widget/views/Home.vue
index 32aae26f1..c15fea236 100755
--- a/app/javascript/widget/views/Home.vue
+++ b/app/javascript/widget/views/Home.vue
@@ -15,7 +15,6 @@
import configMixin from '../mixins/configMixin';
import TeamAvailability from 'widget/components/TeamAvailability';
import { mapGetters } from 'vuex';
-import { BUS_EVENTS } from 'shared/constants/busEvents';
import routerMixin from 'widget/mixins/routerMixin';
export default {
name: 'Home',
@@ -34,10 +33,7 @@ export default {
},
},
data() {
- return {
- isOnCollapsedView: false,
- isOnNewConversation: false,
- };
+ return {};
},
computed: {
...mapGetters({
@@ -46,12 +42,6 @@ export default {
conversationSize: 'conversation/getConversationSize',
}),
},
- mounted() {
- bus.$on(BUS_EVENTS.START_NEW_CONVERSATION, () => {
- this.isOnCollapsedView = true;
- this.isOnNewConversation = true;
- });
- },
methods: {
startConversation() {
if (this.preChatFormEnabled && !this.conversationSize) {
diff --git a/app/javascript/widget/views/PreChatForm.vue b/app/javascript/widget/views/PreChatForm.vue
index ac70e4e56..cb0b4c217 100644
--- a/app/javascript/widget/views/PreChatForm.vue
+++ b/app/javascript/widget/views/PreChatForm.vue
@@ -1,6 +1,10 @@
|