feat: Macros listing and Editor (#5606)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
parent
1fb1be3ddc
commit
22d5703b92
23 changed files with 1287 additions and 31 deletions
|
@ -54,3 +54,5 @@ exclude_patterns:
|
|||
- 'app/javascript/widget/i18n/index.js'
|
||||
- 'app/javascript/survey/i18n/index.js'
|
||||
- 'app/javascript/shared/constants/locales.js'
|
||||
- 'app/javascript/dashboard/helper/specs/macrosFixtures.js'
|
||||
- 'app/javascript/dashboard/routes/dashboard/settings/macros/constants.js'
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<div
|
||||
class="filter"
|
||||
:class="{ error: v.action_params.$dirty && v.action_params.$error }"
|
||||
>
|
||||
<div class="filter" :class="actionInputStyles">
|
||||
<div class="filter-inputs">
|
||||
<select
|
||||
v-model="action_name"
|
||||
|
@ -60,6 +57,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<woot-button
|
||||
v-if="!isMacro"
|
||||
icon="dismiss"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
|
@ -120,6 +118,10 @@ export default {
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isMacro: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
action_name: {
|
||||
|
@ -146,6 +148,12 @@ export default {
|
|||
return this.actionTypes.find(action => action.key === this.action_name)
|
||||
.inputType;
|
||||
},
|
||||
actionInputStyles() {
|
||||
return {
|
||||
error: this.v.action_params.$dirty && this.v.action_params.$error,
|
||||
'is-a-macro': this.isMacro,
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeAction() {
|
||||
|
@ -165,6 +173,18 @@ export default {
|
|||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-medium);
|
||||
margin-bottom: var(--space-small);
|
||||
|
||||
&.is-a-macro {
|
||||
margin-bottom: 0;
|
||||
background: var(--white);
|
||||
padding: var(--space-zero);
|
||||
border: unset;
|
||||
border-radius: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.no-margin-bottom {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.filter.error {
|
||||
|
|
71
app/javascript/dashboard/helper/specs/macrosFixtures.js
Normal file
71
app/javascript/dashboard/helper/specs/macrosFixtures.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
export const teams = [
|
||||
{
|
||||
id: 1,
|
||||
name: '⚙️ sales team',
|
||||
description: 'This is our internal sales team',
|
||||
allow_auto_assign: true,
|
||||
account_id: 1,
|
||||
is_member: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '🤷♂️ fayaz',
|
||||
description: 'Test',
|
||||
allow_auto_assign: true,
|
||||
account_id: 1,
|
||||
is_member: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '🇮🇳 apac sales',
|
||||
description: 'Sales team for France Territory',
|
||||
allow_auto_assign: true,
|
||||
account_id: 1,
|
||||
is_member: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const labels = [
|
||||
{
|
||||
id: 6,
|
||||
title: 'sales',
|
||||
description: 'sales team',
|
||||
color: '#8EA20F',
|
||||
show_on_sidebar: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'billing',
|
||||
description: 'billing',
|
||||
color: '#4077DA',
|
||||
show_on_sidebar: true,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'snoozed',
|
||||
description: 'Items marked for later',
|
||||
color: '#D12F42',
|
||||
show_on_sidebar: true,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: 'mobile-app',
|
||||
description: 'tech team',
|
||||
color: '#2DB1CC',
|
||||
show_on_sidebar: true,
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
title: 'human-resources-department-with-long-title',
|
||||
description: 'Test',
|
||||
color: '#FF6E09',
|
||||
show_on_sidebar: true,
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
title: 'priority',
|
||||
description: 'For important sales leads',
|
||||
color: '#7E7CED',
|
||||
show_on_sidebar: true,
|
||||
},
|
||||
];
|
17
app/javascript/dashboard/helper/specs/macrosHelper.spec.js
Normal file
17
app/javascript/dashboard/helper/specs/macrosHelper.spec.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { emptyMacro } from '../../routes/dashboard/settings/macros/macroHelper';
|
||||
|
||||
describe('#emptyMacro', () => {
|
||||
const defaultMacro = {
|
||||
name: '',
|
||||
actions: [
|
||||
{
|
||||
action_name: 'assign_team',
|
||||
action_params: [],
|
||||
},
|
||||
],
|
||||
visibility: 'global',
|
||||
};
|
||||
it('returns the default macro', () => {
|
||||
expect(emptyMacro).toEqual(defaultMacro);
|
||||
});
|
||||
});
|
|
@ -1,5 +1,73 @@
|
|||
{
|
||||
"MACROS": {
|
||||
"HEADER": "Macros"
|
||||
"HEADER": "Macros",
|
||||
"HEADER_BTN_TXT": "Add a new macro",
|
||||
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||
"LOADING": "Fetching macros",
|
||||
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||
"ERROR": "Something went wrong. Please try again",
|
||||
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||
"ADD": {
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Macro name",
|
||||
"PLACEHOLDER": "Enter a name for your macro",
|
||||
"ERROR": "Name is required for creating a macro"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"LABEL": "Actions"
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||
}
|
||||
},
|
||||
"LIST": {
|
||||
"TABLE_HEADER": [
|
||||
"Name",
|
||||
"Created by",
|
||||
"Last updated by",
|
||||
"Visibility"
|
||||
],
|
||||
"404": "No macros found"
|
||||
},
|
||||
"DELETE": {
|
||||
"TOOLTIP": "Delete macro",
|
||||
"CONFIRM": {
|
||||
"MESSAGE": "Are you sure to delete ",
|
||||
"YES": "Yes, Delete",
|
||||
"NO": "No"
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TOOLTIP": "Edit macro",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||
}
|
||||
},
|
||||
"EDITOR": {
|
||||
"START_FLOW": "Start Flow",
|
||||
"END_FLOW": "End Flow",
|
||||
"LOADING": "Fetching macro",
|
||||
"ADD_BTN_TOOLTIP": "Add new action",
|
||||
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||
"VISIBILITY": {
|
||||
"LABEL": "Macro Visibility",
|
||||
"GLOBAL": {
|
||||
"LABEL": "Public",
|
||||
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||
},
|
||||
"PERSONAL": {
|
||||
"LABEL": "Private",
|
||||
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
20
app/javascript/dashboard/mixins/macrosMixin.js
Normal file
20
app/javascript/dashboard/mixins/macrosMixin.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
export default {
|
||||
methods: {
|
||||
getDropdownValues(type) {
|
||||
switch (type) {
|
||||
case 'assign_team':
|
||||
case 'send_email_to_team':
|
||||
return this.teams;
|
||||
case 'add_label':
|
||||
return this.labels.map(i => {
|
||||
return {
|
||||
id: i.title,
|
||||
name: i.title,
|
||||
};
|
||||
});
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
41
app/javascript/dashboard/mixins/specs/macros.spec.js
Normal file
41
app/javascript/dashboard/mixins/specs/macros.spec.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { createWrapper } from '@vue/test-utils';
|
||||
import macrosMixin from '../macrosMixin';
|
||||
import Vue from 'vue';
|
||||
import { teams, labels } from '../../helper/specs/macrosFixtures';
|
||||
describe('webhookMixin', () => {
|
||||
describe('#getEventLabel', () => {
|
||||
it('returns correct i18n translation:', () => {
|
||||
const Component = {
|
||||
render() {},
|
||||
title: 'MyComponent',
|
||||
mixins: [macrosMixin],
|
||||
data: () => {
|
||||
return {
|
||||
teams,
|
||||
labels,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
$t(text) {
|
||||
return text;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const resolvedLabels = labels.map(i => {
|
||||
return {
|
||||
id: i.title,
|
||||
name: i.title,
|
||||
};
|
||||
});
|
||||
|
||||
const Constructor = Vue.extend(Component);
|
||||
const vm = new Constructor().$mount();
|
||||
const wrapper = createWrapper(vm);
|
||||
expect(wrapper.vm.getDropdownValues('assign_team')).toEqual(teams);
|
||||
expect(wrapper.vm.getDropdownValues('send_email_to_team')).toEqual(teams);
|
||||
expect(wrapper.vm.getDropdownValues('add_label')).toEqual(resolvedLabels);
|
||||
expect(wrapper.vm.getDropdownValues()).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -225,7 +225,7 @@ export default {
|
|||
mode === 'EDIT'
|
||||
? this.$t('AUTOMATION.EDIT.API.SUCCESS_MESSAGE')
|
||||
: this.$t('AUTOMATION.ADD.API.SUCCESS_MESSAGE');
|
||||
await await this.$store.dispatch(action, payload);
|
||||
await this.$store.dispatch(action, payload);
|
||||
this.showAlert(this.$t(successMessage));
|
||||
this.hideAddPopup();
|
||||
this.hideEditPopup();
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<div>
|
||||
<button
|
||||
v-tooltip="tooltip"
|
||||
class="macros__action-button"
|
||||
:class="type"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<fluent-icon :icon="icon" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'add',
|
||||
},
|
||||
tooltip: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.macros__action-button {
|
||||
height: var(--space-three);
|
||||
width: var(--space-three);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-default);
|
||||
border-radius: var(--border-radius-rounded);
|
||||
position: relative;
|
||||
margin-left: var(--space-one);
|
||||
|
||||
&.add {
|
||||
background-color: var(--g-100);
|
||||
color: var(--g-600);
|
||||
}
|
||||
&.delete {
|
||||
position: absolute;
|
||||
top: calc(var(--space-three) / -2);
|
||||
right: calc(var(--space-three) / -2);
|
||||
background-color: var(--r-100);
|
||||
color: var(--r-600);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,11 +1,121 @@
|
|||
<template>
|
||||
<div>
|
||||
Macros
|
||||
<div class="column content-box">
|
||||
<router-link
|
||||
:to="addAccountScoping('settings/macros/new')"
|
||||
class="button success button--fixed-right-top"
|
||||
>
|
||||
<fluent-icon icon="add-circle" />
|
||||
<span class="button__content">
|
||||
{{ $t('MACROS.HEADER_BTN_TXT') }}
|
||||
</span>
|
||||
</router-link>
|
||||
<div class="row">
|
||||
<div class="small-8 columns with-right-space">
|
||||
<div
|
||||
v-if="!uiFlags.isFetching && !records.length"
|
||||
class="macros__empty-state"
|
||||
>
|
||||
<p class="no-items-error-message">
|
||||
{{ $t('MACROS.LIST.404') }}
|
||||
</p>
|
||||
</div>
|
||||
<woot-loading-state
|
||||
v-if="uiFlags.isFetching"
|
||||
:message="$t('MACROS.LOADING')"
|
||||
/>
|
||||
<table v-if="!uiFlags.isFetching && records.length" class="woot-table">
|
||||
<thead>
|
||||
<th
|
||||
v-for="thHeader in $t('MACROS.LIST.TABLE_HEADER')"
|
||||
:key="thHeader"
|
||||
>
|
||||
{{ thHeader }}
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<macros-table-row
|
||||
v-for="(macro, index) in records"
|
||||
:key="index"
|
||||
:macro="macro"
|
||||
@delete="openDeletePopup(macro, index)"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="small-4 columns">
|
||||
<span v-dompurify-html="$t('MACROS.SIDEBAR_TXT')" />
|
||||
</div>
|
||||
</div>
|
||||
<woot-delete-modal
|
||||
:show.sync="showDeleteConfirmationPopup"
|
||||
:on-close="closeDeletePopup"
|
||||
:on-confirm="confirmDeletion"
|
||||
:title="$t('LABEL_MGMT.DELETE.CONFIRM.TITLE')"
|
||||
:message="$t('MACROS.DELETE.CONFIRM.MESSAGE')"
|
||||
:message-value="deleteMessage"
|
||||
:confirm-text="$t('MACROS.DELETE.CONFIRM.YES')"
|
||||
:reject-text="$t('MACROS.DELETE.CONFIRM.NO')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
import { mapGetters } from 'vuex';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import accountMixin from 'dashboard/mixins/account.js';
|
||||
import MacrosTableRow from './MacrosTableRow';
|
||||
export default {
|
||||
components: {
|
||||
MacrosTableRow,
|
||||
},
|
||||
mixins: [alertMixin, accountMixin],
|
||||
data() {
|
||||
return {
|
||||
showDeleteConfirmationPopup: false,
|
||||
selectedResponse: {},
|
||||
loading: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
records: ['macros/getMacros'],
|
||||
uiFlags: 'macros/getUIFlags',
|
||||
}),
|
||||
deleteMessage() {
|
||||
return ` ${this.selectedResponse.name}?`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('macros/get');
|
||||
},
|
||||
methods: {
|
||||
openDeletePopup(response) {
|
||||
this.showDeleteConfirmationPopup = true;
|
||||
this.selectedResponse = response;
|
||||
},
|
||||
closeDeletePopup() {
|
||||
this.showDeleteConfirmationPopup = false;
|
||||
},
|
||||
confirmDeletion() {
|
||||
this.loading[this.selectedResponse.id] = true;
|
||||
this.closeDeletePopup();
|
||||
this.deleteMacro(this.selectedResponse.id);
|
||||
},
|
||||
async deleteMacro(id) {
|
||||
try {
|
||||
await this.$store.dispatch('macros/delete', id);
|
||||
this.showAlert(this.$t('MACROS.DELETE.API.SUCCESS_MESSAGE'));
|
||||
this.loading[this.selectedResponse.id] = false;
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('MACROS.DELETE.API.ERROR_MESSAGE'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style scoped>
|
||||
.macros__empty-state {
|
||||
padding: var(--space-slab);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,137 @@
|
|||
<template>
|
||||
<div>MacrosEditor</div>
|
||||
<div class="column content-box">
|
||||
<woot-loading-state
|
||||
v-if="uiFlags.isFetchingItem"
|
||||
:message="$t('MACROS.EDITOR.LOADING')"
|
||||
/>
|
||||
<macro-form
|
||||
v-if="macro && !uiFlags.isFetchingItem"
|
||||
:macro-data.sync="macro"
|
||||
@submit="saveMacro"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
import MacroForm from './MacroForm';
|
||||
import { MACRO_ACTION_TYPES } from './constants';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { emptyMacro } from './macroHelper';
|
||||
import actionQueryGenerator from 'dashboard/helper/actionQueryGenerator.js';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import macrosMixin from 'dashboard/mixins/macrosMixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MacroForm,
|
||||
},
|
||||
mixins: [alertMixin, macrosMixin],
|
||||
provide() {
|
||||
return {
|
||||
macroActionTypes: this.macroActionTypes,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
macro: null,
|
||||
mode: 'CREATE',
|
||||
macroActionTypes: MACRO_ACTION_TYPES,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
uiFlags: 'macros/getUIFlags',
|
||||
labels: 'labels/getLabels',
|
||||
teams: 'teams/getTeams',
|
||||
}),
|
||||
macroId() {
|
||||
return this.$route.params.macroId;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler() {
|
||||
if (this.$route.params.macroId) {
|
||||
this.fetchMacro();
|
||||
} else {
|
||||
this.initNewMacro();
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchMacro() {
|
||||
this.mode = 'EDIT';
|
||||
this.$store.dispatch('agents/get');
|
||||
this.$store.dispatch('teams/get');
|
||||
this.$store.dispatch('labels/get');
|
||||
this.manifestMacro();
|
||||
},
|
||||
async manifestMacro() {
|
||||
await this.$store.dispatch('macros/getSingleMacro', this.macroId);
|
||||
const singleMacro = this.$store.getters['macros/getMacro'](this.macroId);
|
||||
this.macro = this.formatMacro(singleMacro);
|
||||
},
|
||||
formatMacro(macro) {
|
||||
const formattedActions = macro.actions.map(action => {
|
||||
let actionParams = [];
|
||||
if (action.action_params.length) {
|
||||
const inputType = this.macroActionTypes.find(
|
||||
item => item.key === action.action_name
|
||||
).inputType;
|
||||
if (inputType === 'multi_select') {
|
||||
actionParams = [
|
||||
...this.getDropdownValues(action.action_name, this.$store),
|
||||
].filter(item => [...action.action_params].includes(item.id));
|
||||
} else if (inputType === 'team_message') {
|
||||
actionParams = {
|
||||
team_ids: [
|
||||
...this.getDropdownValues(action.action_name, this.$store),
|
||||
].filter(item =>
|
||||
[...action.action_params[0].team_ids].includes(item.id)
|
||||
),
|
||||
message: action.action_params[0].message,
|
||||
};
|
||||
} else actionParams = [...action.action_params];
|
||||
}
|
||||
return {
|
||||
...action,
|
||||
action_params: actionParams,
|
||||
};
|
||||
});
|
||||
return {
|
||||
...macro,
|
||||
actions: formattedActions,
|
||||
};
|
||||
},
|
||||
initNewMacro() {
|
||||
this.mode = 'CREATE';
|
||||
this.macro = emptyMacro;
|
||||
},
|
||||
async saveMacro(macro) {
|
||||
try {
|
||||
const action = this.mode === 'EDIT' ? 'macros/update' : 'macros/create';
|
||||
let successMessage =
|
||||
this.mode === 'EDIT'
|
||||
? this.$t('MACROS.EDIT.API.SUCCESS_MESSAGE')
|
||||
: this.$t('MACROS.ADD.API.SUCCESS_MESSAGE');
|
||||
let serializedMacro = JSON.parse(JSON.stringify(macro));
|
||||
serializedMacro.actions = actionQueryGenerator(serializedMacro.actions);
|
||||
await this.$store.dispatch(action, serializedMacro);
|
||||
this.showAlert(successMessage);
|
||||
this.$router.push({ name: 'macros_wrapper' });
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('MACROS.ERROR'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style scoped>
|
||||
.content-box {
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div class="small-8 columns with-right-space macros-canvas">
|
||||
<macro-nodes
|
||||
v-model="macro.actions"
|
||||
@addNewNode="appendNode"
|
||||
@deleteNode="deleteNode"
|
||||
@resetAction="resetNode"
|
||||
/>
|
||||
</div>
|
||||
<div class="small-4 columns">
|
||||
<macro-properties
|
||||
:macro-name="macro.name"
|
||||
:macro-visibility="macro.visibility"
|
||||
@update:name="updateName"
|
||||
@update:visibility="updateVisibility"
|
||||
@submit="submit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MacroNodes from './MacroNodes';
|
||||
import MacroProperties from './MacroProperties';
|
||||
import { required, requiredIf } from 'vuelidate/lib/validators';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MacroNodes,
|
||||
MacroProperties,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
$v: this.$v,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
macroData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
macro: this.macroData,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
macroData: {
|
||||
handler() {
|
||||
this.macro = this.macroData;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
macro: {
|
||||
name: {
|
||||
required,
|
||||
},
|
||||
visibility: {
|
||||
required,
|
||||
},
|
||||
actions: {
|
||||
required,
|
||||
$each: {
|
||||
action_params: {
|
||||
required: requiredIf(prop => {
|
||||
if (prop.action_name === 'send_email_to_team') return true;
|
||||
return !(
|
||||
prop.action_name === 'mute_conversation' ||
|
||||
prop.action_name === 'snooze_conversation' ||
|
||||
prop.action_name === 'resolve_conversation'
|
||||
);
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$v.$reset();
|
||||
},
|
||||
methods: {
|
||||
updateName(value) {
|
||||
this.macro.name = value;
|
||||
},
|
||||
updateVisibility(value) {
|
||||
this.macro.visibility = value;
|
||||
},
|
||||
appendNode() {
|
||||
this.macro.actions.push({
|
||||
action_name: 'assign_team',
|
||||
action_params: [],
|
||||
});
|
||||
},
|
||||
deleteNode(index) {
|
||||
this.macro.actions.splice(index, 1);
|
||||
},
|
||||
submit() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) return;
|
||||
this.$emit('submit', this.macro);
|
||||
},
|
||||
resetNode(index) {
|
||||
this.macro.actions[index].action_params = [];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.row {
|
||||
height: 100%;
|
||||
}
|
||||
.macros-canvas {
|
||||
background-image: radial-gradient(var(--s-100) 1.2px, transparent 0);
|
||||
background-size: var(--space-normal) var(--space-normal);
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
padding: var(--space-normal) var(--space-three);
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,149 @@
|
|||
<template>
|
||||
<div class="macro__node-action-container">
|
||||
<fluent-icon
|
||||
v-if="!singleNode"
|
||||
size="20"
|
||||
icon="navigation"
|
||||
class="macros__node-drag-handle"
|
||||
/>
|
||||
<div
|
||||
class="macro__node-action-item"
|
||||
:class="{
|
||||
'has-error': hasError($v.macro.actions.$each[index]),
|
||||
}"
|
||||
>
|
||||
<action-input
|
||||
v-model="actionData"
|
||||
:action-types="macroActionTypes"
|
||||
:dropdown-values="dropdownValues()"
|
||||
:show-action-input="showActionInput"
|
||||
:show-remove-button="false"
|
||||
:is-macro="true"
|
||||
:v="$v.macro.actions.$each[index]"
|
||||
@resetAction="$emit('resetAction')"
|
||||
/>
|
||||
<macro-action-button
|
||||
v-if="!singleNode"
|
||||
icon="dismiss-circle"
|
||||
class="macro__node macro__node-action-button-delete"
|
||||
type="delete"
|
||||
:tooltip="$t('MACROS.EDITOR.DELETE_BTN_TOOLTIP')"
|
||||
@click="$emit('deleteNode')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionInput from 'dashboard/components/widgets/AutomationActionInput';
|
||||
import MacroActionButton from './ActionButton.vue';
|
||||
import macrosMixin from 'dashboard/mixins/macrosMixin';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActionInput,
|
||||
MacroActionButton,
|
||||
},
|
||||
mixins: [macrosMixin],
|
||||
inject: ['macroActionTypes', '$v'],
|
||||
props: {
|
||||
singleNode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
labels: 'labels/getLabels',
|
||||
teams: 'teams/getTeams',
|
||||
}),
|
||||
actionData: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('input', value);
|
||||
},
|
||||
},
|
||||
showActionInput() {
|
||||
if (
|
||||
this.actionData.action_name === 'send_email_to_team' ||
|
||||
this.actionData.action_name === 'send_message'
|
||||
)
|
||||
return false;
|
||||
const type = this.macroActionTypes.find(
|
||||
action => action.key === this.actionData.action_name
|
||||
).inputType;
|
||||
return !!type;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dropdownValues() {
|
||||
return this.getDropdownValues(this.value.action_name, this.$store);
|
||||
},
|
||||
hasError(v) {
|
||||
return !!(v.action_params.$dirty && v.action_params.$error);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.macro__node-action-container {
|
||||
position: relative;
|
||||
.macros__node-drag-handle {
|
||||
position: absolute;
|
||||
left: var(--space-minus-medium);
|
||||
top: var(--space-smaller);
|
||||
cursor: move;
|
||||
color: var(--s-400);
|
||||
}
|
||||
.macro__node-action-item {
|
||||
background-color: var(--white);
|
||||
padding: var(--space-slab);
|
||||
border-radius: var(--border-radius-medium);
|
||||
box-shadow: rgb(0 0 0 / 3%) 0px 6px 24px 0px,
|
||||
rgb(0 0 0 / 6%) 0px 0px 0px 1px;
|
||||
|
||||
.macro__node-action-button-delete {
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
.macro__node-action-button-delete {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
&.has-error {
|
||||
animation: shake 0.3s ease-in-out 0s 2;
|
||||
background-color: var(--r-50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(0.375rem);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(-0.375rem);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(0.375rem);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div class="macros__nodes">
|
||||
<macros-pill :label="$t('MACROS.EDITOR.START_FLOW')" class="macro__node" />
|
||||
<draggable
|
||||
:list="actionData"
|
||||
animation="200"
|
||||
ghost-class="ghost"
|
||||
tag="div"
|
||||
class="macros__nodes-draggable"
|
||||
handle=".macros__node-drag-handle"
|
||||
@start="dragging = true"
|
||||
@end="dragging = false"
|
||||
>
|
||||
<div v-for="(action, i) in actionData" :key="i" class="macro__node">
|
||||
<macro-node
|
||||
v-model="actionData[i]"
|
||||
class="macros__node-action"
|
||||
type="add"
|
||||
:index="i"
|
||||
:single-node="actionData.length === 1"
|
||||
@resetAction="$emit('resetAction', i)"
|
||||
@deleteNode="$emit('deleteNode', i)"
|
||||
/>
|
||||
</div>
|
||||
</draggable>
|
||||
<macro-action-button
|
||||
icon="add-circle"
|
||||
class="macro__node"
|
||||
:tooltip="$t('MACROS.EDITOR.ADD_BTN_TOOLTIP')"
|
||||
type="add"
|
||||
@click="$emit('addNewNode')"
|
||||
/>
|
||||
<macros-pill :label="$t('MACROS.EDITOR.END_FLOW')" class="macro__node" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MacrosPill from './Pill.vue';
|
||||
import Draggable from 'vuedraggable';
|
||||
import MacroNode from './MacroNode.vue';
|
||||
import MacroActionButton from './ActionButton.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Draggable,
|
||||
MacrosPill,
|
||||
MacroNode,
|
||||
MacroActionButton,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
actionData: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('input', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.macros__nodes {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.macro__node:not(:last-child) {
|
||||
position: relative;
|
||||
padding-bottom: var(--space-three);
|
||||
}
|
||||
|
||||
.macro__node:not(:last-child):not(.sortable-chosen):after,
|
||||
.macros__nodes-draggable:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: var(--space-three);
|
||||
width: var(--space-smaller);
|
||||
margin-left: var(--space-medium);
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='4' height='30' viewBox='0 0 4 30' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cline x1='1.50098' y1='0.579529' x2='1.50098' y2='30.5795' stroke='%2393afc8' stroke-width='2' stroke-dasharray='5 5'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
|
||||
.macros__nodes-draggable {
|
||||
position: relative;
|
||||
padding-bottom: var(--space-three);
|
||||
}
|
||||
|
||||
.macros__node-action-container {
|
||||
position: relative;
|
||||
.drag-handle {
|
||||
position: absolute;
|
||||
left: var(--space-minus-medium);
|
||||
top: var(--space-smaller);
|
||||
cursor: move;
|
||||
color: var(--s-400);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,174 @@
|
|||
<template>
|
||||
<div class="macros__properties-panel">
|
||||
<div>
|
||||
<woot-input
|
||||
:value="macroName"
|
||||
:label="$t('MACROS.ADD.FORM.NAME.LABEL')"
|
||||
:placeholder="$t('MACROS.ADD.FORM.NAME.PLACEHOLDER')"
|
||||
:error="$v.macro.name.$error ? $t('MACROS.ADD.FORM.NAME.ERROR') : null"
|
||||
:class="{ error: $v.macro.name.$error }"
|
||||
@input="onUpdateName($event)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ $t('MACROS.EDITOR.VISIBILITY.LABEL') }}</p>
|
||||
<div class="macros__form-visibility">
|
||||
<button
|
||||
class="card"
|
||||
:class="isActive('global')"
|
||||
@click="onUpdateVisibility('global')"
|
||||
>
|
||||
<fluent-icon
|
||||
v-if="macroVisibility === 'global'"
|
||||
icon="checkmark-circle"
|
||||
type="solid"
|
||||
class="visibility-check"
|
||||
/>
|
||||
<p class="title">
|
||||
{{ $t('MACROS.EDITOR.VISIBILITY.GLOBAL.LABEL') }}
|
||||
</p>
|
||||
<p class="subtitle">
|
||||
{{ $t('MACROS.EDITOR.VISIBILITY.GLOBAL.DESCRIPTION') }}
|
||||
</p>
|
||||
</button>
|
||||
<button
|
||||
class="card"
|
||||
:class="isActive('personal')"
|
||||
@click="onUpdateVisibility('personal')"
|
||||
>
|
||||
<fluent-icon
|
||||
v-if="macroVisibility === 'personal'"
|
||||
icon="checkmark-circle"
|
||||
type="solid"
|
||||
class="visibility-check"
|
||||
/>
|
||||
<p class="title">
|
||||
{{ $t('MACROS.EDITOR.VISIBILITY.PERSONAL.LABEL') }}
|
||||
</p>
|
||||
<p class="subtitle">
|
||||
{{ $t('MACROS.EDITOR.VISIBILITY.PERSONAL.DESCRIPTION') }}
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
<div class="macros__info-panel">
|
||||
<fluent-icon icon="info" size="20" />
|
||||
<p>
|
||||
{{ $t('MACROS.ORDER_INFO') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="macros__submit-button">
|
||||
<woot-button
|
||||
size="expanded"
|
||||
color-scheme="success"
|
||||
@click="$emit('submit')"
|
||||
>
|
||||
{{ $t('MACROS.HEADER_BTN_TXT_SAVE') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['$v'],
|
||||
props: {
|
||||
macroName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
macroVisibility: {
|
||||
type: String,
|
||||
default: 'global',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isActive(key) {
|
||||
return { active: this.macroVisibility === key };
|
||||
},
|
||||
onUpdateName(value) {
|
||||
this.$emit('update:name', value);
|
||||
},
|
||||
onUpdateVisibility(value) {
|
||||
this.$emit('update:visibility', value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.macros__properties-panel {
|
||||
padding: var(--space-slab);
|
||||
background-color: var(--white);
|
||||
// full screen height subtracted by the height of the header
|
||||
height: calc(100vh - 5.6rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-left: 1px solid var(--s-50);
|
||||
}
|
||||
|
||||
.macros__submit-button {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.macros__form-visibility {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: var(--space-slab);
|
||||
|
||||
.card {
|
||||
padding: var(--space-small);
|
||||
border-radius: var(--border-radius-normal);
|
||||
border: 1px solid var(--s-200);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
background-color: var(--w-25);
|
||||
border: 1px solid var(--w-300);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: var(--font-size-mini);
|
||||
color: var(--s-500);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
margin: 0;
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1.8;
|
||||
color: var(--color-body);
|
||||
}
|
||||
|
||||
.visibility-check {
|
||||
position: absolute;
|
||||
color: var(--w-500);
|
||||
top: var(--space-small);
|
||||
right: var(--space-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.macros__info-panel {
|
||||
margin-top: var(--space-small);
|
||||
display: flex;
|
||||
background-color: var(--s-50);
|
||||
padding: var(--space-small);
|
||||
border-radius: var(--border-radius-normal);
|
||||
align-items: flex-start;
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
p {
|
||||
margin-left: var(--space-small);
|
||||
color: var(--s-600);
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep input[type='text'] {
|
||||
margin-bottom: var(--space-small);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<tr>
|
||||
<td>{{ macro.name }}</td>
|
||||
<td>
|
||||
<div class="avatar-container">
|
||||
<thumbnail :username="macro.created_by.name" size="24px" />
|
||||
<span class="ml-2">{{ macro.created_by.name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="avatar-container">
|
||||
<thumbnail :username="macro.updated_by.name" size="24px" />
|
||||
<span class="ml-2">{{ macro.updated_by.name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ visibilityLabel }}</td>
|
||||
<td class="button-wrapper">
|
||||
<router-link :to="addAccountScoping(`settings/macros/${macro.id}/edit`)">
|
||||
<woot-button
|
||||
v-tooltip.top="$t('MACROS.EDIT.TOOLTIP')"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
color-scheme="secondary"
|
||||
class-names="grey-btn"
|
||||
icon="edit"
|
||||
/>
|
||||
</router-link>
|
||||
<woot-button
|
||||
v-tooltip.top="$t('MACROS.DELETE.TOOLTIP')"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
size="tiny"
|
||||
icon="dismiss-circle"
|
||||
class-names="grey-btn"
|
||||
@click="$emit('delete')"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||
import accountMixin from 'dashboard/mixins/account.js';
|
||||
export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
},
|
||||
mixins: [accountMixin],
|
||||
props: {
|
||||
macro: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
visibilityLabel() {
|
||||
return this.macro.visibility === 'global'
|
||||
? this.$t('MACROS.EDITOR.VISIBILITY.GLOBAL.LABEL')
|
||||
: this.$t('MACROS.EDITOR.VISIBILITY.PERSONAL.LABEL');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.avatar-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
margin-left: var(--space-one);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="macros-item macros-pill">
|
||||
<span>{{ label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.macros-pill {
|
||||
padding: var(--space-slab);
|
||||
background-color: var(--w-500);
|
||||
max-width: max-content;
|
||||
color: var(--white);
|
||||
font-size: var(--font-size-small);
|
||||
border-radius: var(--border-radius-full);
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,42 @@
|
|||
export const MACRO_ACTION_TYPES = [
|
||||
{
|
||||
key: 'assign_team',
|
||||
label: 'Assign a team',
|
||||
inputType: 'multi_select',
|
||||
},
|
||||
{
|
||||
key: 'add_label',
|
||||
label: 'Add a label',
|
||||
inputType: 'multi_select',
|
||||
},
|
||||
{
|
||||
key: 'send_email_transcript',
|
||||
label: 'Send an email transcript',
|
||||
inputType: 'email',
|
||||
},
|
||||
{
|
||||
key: 'mute_conversation',
|
||||
label: 'Mute conversation',
|
||||
inputType: null,
|
||||
},
|
||||
{
|
||||
key: 'snooze_conversation',
|
||||
label: 'Snooze conversation',
|
||||
inputType: null,
|
||||
},
|
||||
{
|
||||
key: 'resolve_conversation',
|
||||
label: 'Resolve conversation',
|
||||
inputType: null,
|
||||
},
|
||||
{
|
||||
key: 'send_attachment',
|
||||
label: 'Send Attachment',
|
||||
inputType: 'attachment',
|
||||
},
|
||||
{
|
||||
key: 'send_message',
|
||||
label: 'Send a message',
|
||||
inputType: 'textarea',
|
||||
},
|
||||
];
|
|
@ -0,0 +1,10 @@
|
|||
export const emptyMacro = {
|
||||
name: '',
|
||||
actions: [
|
||||
{
|
||||
action_name: 'assign_team',
|
||||
action_params: [],
|
||||
},
|
||||
],
|
||||
visibility: 'global',
|
||||
};
|
|
@ -8,10 +8,14 @@ export default {
|
|||
{
|
||||
path: frontendURL('accounts/:accountId/settings/macros'),
|
||||
component: SettingsContent,
|
||||
props: {
|
||||
headerTitle: 'MACROS.HEADER',
|
||||
icon: 'flash-settings',
|
||||
showNewButton: false,
|
||||
props: params => {
|
||||
const showBackButton = params.name !== 'macros_wrapper';
|
||||
return {
|
||||
headerTitle: 'MACROS.HEADER',
|
||||
headerButtonText: 'MACROS.HEADER_BTN_TXT',
|
||||
icon: 'flash-settings',
|
||||
showBackButton,
|
||||
};
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
|
|
@ -2,14 +2,16 @@ import Vue from 'vue';
|
|||
import Vuex from 'vuex';
|
||||
|
||||
import accounts from './modules/accounts';
|
||||
import agents from './modules/agents';
|
||||
import agentBots from './modules/agentBots';
|
||||
import agents from './modules/agents';
|
||||
import articles from './modules/helpCenterArticles';
|
||||
import attributes from './modules/attributes';
|
||||
import auth from './modules/auth';
|
||||
import automations from './modules/automations';
|
||||
import bulkActions from './modules/bulkActions';
|
||||
import campaigns from './modules/campaigns';
|
||||
import cannedResponse from './modules/cannedResponse';
|
||||
import categories from './modules/helpCenterCategories';
|
||||
import contactConversations from './modules/contactConversations';
|
||||
import contactLabels from './modules/contactLabels';
|
||||
import contactNotes from './modules/contactNotes';
|
||||
|
@ -30,28 +32,29 @@ import inboxes from './modules/inboxes';
|
|||
import inboxMembers from './modules/inboxMembers';
|
||||
import integrations from './modules/integrations';
|
||||
import labels from './modules/labels';
|
||||
import macros from './modules/macros';
|
||||
import notifications from './modules/notifications';
|
||||
import portals from './modules/helpCenterPortals';
|
||||
import reports from './modules/reports';
|
||||
import teamMembers from './modules/teamMembers';
|
||||
import teams from './modules/teams';
|
||||
import userNotificationSettings from './modules/userNotificationSettings';
|
||||
import webhooks from './modules/webhooks';
|
||||
import articles from './modules/helpCenterArticles';
|
||||
import portals from './modules/helpCenterPortals';
|
||||
import categories from './modules/helpCenterCategories';
|
||||
|
||||
Vue.use(Vuex);
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
accounts,
|
||||
agents,
|
||||
agentBots,
|
||||
agents,
|
||||
articles,
|
||||
attributes,
|
||||
auth,
|
||||
automations,
|
||||
bulkActions,
|
||||
campaigns,
|
||||
cannedResponse,
|
||||
categories,
|
||||
contactConversations,
|
||||
contactLabels,
|
||||
contactNotes,
|
||||
|
@ -72,14 +75,13 @@ export default new Vuex.Store({
|
|||
inboxMembers,
|
||||
integrations,
|
||||
labels,
|
||||
macros,
|
||||
notifications,
|
||||
portals,
|
||||
reports,
|
||||
teamMembers,
|
||||
teams,
|
||||
userNotificationSettings,
|
||||
webhooks,
|
||||
articles,
|
||||
portals,
|
||||
categories,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
:root {
|
||||
// border-radius
|
||||
--border-radius-small: 0.3rem;
|
||||
--border-radius-normal: 0.5rem;
|
||||
--border-radius-medium: 0.7rem;
|
||||
--border-radius-large: 0.9rem;
|
||||
--border-radius-rounded: 50%;
|
||||
--border-radius-small: 0.3rem;
|
||||
--border-radius-normal: 0.5rem;
|
||||
--border-radius-medium: 0.7rem;
|
||||
--border-radius-large: 0.9rem;
|
||||
--border-radius-full: 10rem;
|
||||
--border-radius-rounded: 50%;
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@
|
|||
"microphone-stop-outline": "M18,18H6V6H18V18Z",
|
||||
"microphone-pause-outline": "M14,19H18V5H14M6,19H10V5H6V19Z",
|
||||
"microphone-play-outline": "M8,5.14V19.14L19,12.14L8,5.14Z",
|
||||
"navigation-outline": "M3 17h18a1 1 0 0 1 .117 1.993L21 19H3a1 1 0 0 1-.117-1.993L3 17h18H3Zm0-6l18-.002a1 1 0 0 1 .117 1.993l-.117.007L3 13a1 1 0 0 1-.117-1.993L3 11l18-.002L3 11Zm0-6h18a1 1 0 0 1 .117 1.993L21 7H3a1 1 0 0 1-.117-1.993L3 5h18H3Z",
|
||||
"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",
|
||||
"panel-sidebar-outline": "M4.75 4A2.75 2.75 0 0 0 2 6.75v10.5A2.75 2.75 0 0 0 4.75 20h14.5A2.75 2.75 0 0 0 22 17.25V6.75A2.75 2.75 0 0 0 19.25 4H4.75ZM9 18.5v-13h10.25c.69 0 1.25.56 1.25 1.25v10.5c0 .69-.56 1.25-1.25 1.25H9ZM5.5 3.5h4M5.5 5.5h4M5.5 7.5h4M5.5 9.5h4",
|
||||
|
|
Loading…
Reference in a new issue