Merge branch 'develop' into chore-global-config-mixin
This commit is contained in:
@@ -331,10 +331,12 @@ export default {
|
||||
::v-deep {
|
||||
.selector-wrap {
|
||||
@apply m-0 top-1;
|
||||
|
||||
.selector-name {
|
||||
@apply ml-0;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
@apply ml-0;
|
||||
}
|
||||
|
||||
@@ -120,7 +120,6 @@ export default {
|
||||
if (fileUrl) {
|
||||
this.onImageUploadStart(fileUrl);
|
||||
}
|
||||
useAlert(this.$t('HELP_CENTER.ARTICLE_EDITOR.IMAGE_UPLOAD.SUCCESS'));
|
||||
} catch (error) {
|
||||
useAlert(this.$t('HELP_CENTER.ARTICLE_EDITOR.IMAGE_UPLOAD.ERROR'));
|
||||
}
|
||||
@@ -173,6 +172,18 @@ export default {
|
||||
blur: () => {
|
||||
this.onBlur();
|
||||
},
|
||||
paste: (view, event) => {
|
||||
const data = event.clipboardData.files;
|
||||
if (data.length > 0) {
|
||||
data.forEach(file => {
|
||||
// Check if the file is an image
|
||||
if (file.type.includes('image')) {
|
||||
this.uploadImageToStorage(file);
|
||||
}
|
||||
});
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ const FEATURE_HELP_URLS = {
|
||||
custom_attributes: 'https://chwt.app/hc/custom-attributes',
|
||||
dashboard_apps: 'https://chwt.app/hc/dashboard-apps',
|
||||
help_center: 'https://chwt.app/hc/help-center',
|
||||
inboxes: 'https://chwt.app/hc/inboxes',
|
||||
integrations: 'https://chwt.app/hc/integrations',
|
||||
labels: 'https://chwt.app/hc/labels',
|
||||
macros: 'https://chwt.app/hc/macros',
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"HEADER": "Custom Attributes",
|
||||
"HEADER_BTN_TXT": "Add Custom Attribute",
|
||||
"LOADING": "Fetching custom attributes",
|
||||
"SIDEBAR_TXT": "<p><b>Custom Attributes</b> <p>A custom attribute tracks facts about your contacts/conversation — like the subscription plan, or when they ordered the first item etc. <br /><br />For creating a Custom Attribute, just click on the <b>Add Custom Attribute.</b> You can also edit or delete an existing Custom Attribute by clicking on the Edit or Delete button.</p>",
|
||||
"DESCRIPTION": "A custom attribute tracks additional details about your contacts or conversations—such as the subscription plan or the date of their first purchase. You can add different types of custom attributes, such as text, lists, or numbers, to capture the specific information you need.",
|
||||
"LEARN_MORE": "Learn more about custom attributes",
|
||||
"ADD": {
|
||||
"TITLE": "Add Custom Attribute",
|
||||
"SUBMIT": "Create",
|
||||
@@ -91,7 +92,12 @@
|
||||
"CONTACT": "Contact"
|
||||
},
|
||||
"LIST": {
|
||||
"TABLE_HEADER": ["Name", "Description", "Type", "Key"],
|
||||
"TABLE_HEADER": [
|
||||
"Name",
|
||||
"Description",
|
||||
"Type",
|
||||
"Key"
|
||||
],
|
||||
"BUTTONS": {
|
||||
"EDIT": "Edit",
|
||||
"DELETE": "Delete"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"INBOX_MGMT": {
|
||||
"HEADER": "Inboxes",
|
||||
"SIDEBAR_TXT": "<p><b>Inbox</b></p> <p> When you connect a website or a facebook Page to Chatwoot, it is called an <b>Inbox</b>. You can have unlimited inboxes in your Chatwoot account. </p><p> Click on <b>Add Inbox</b> to connect a website or a Facebook Page. </p><p> In the Dashboard, you can see all the conversations from all your inboxes in a single place and respond to them under the `Conversations` tab. </p><p> You can also see conversations specific to an inbox by clicking on the inbox name on the left pane of the dashboard. </p>",
|
||||
"DESCRIPTION": "A channel is the mode of communication your customer chooses to interact with you. An inbox is where you manage interactions for a specific channel. It can include communications from various sources such as email, live chat, and social media.",
|
||||
"LEARN_MORE": "Learn more about inboxes",
|
||||
"RECONNECTION_REQUIRED": "Your inbox is disconnected. You won't receive new messages until you reauthorize it.",
|
||||
"CLICK_TO_RECONNECT": "Click here to reconnect.",
|
||||
"LIST": {
|
||||
@@ -745,6 +746,18 @@
|
||||
"MICROSOFT": "Microsoft",
|
||||
"GOOGLE": "Google",
|
||||
"OTHER_PROVIDERS": "Other Providers"
|
||||
},
|
||||
"CHANNELS": {
|
||||
"MESSENGER": "Messenger",
|
||||
"WEB_WIDGET": "Website",
|
||||
"TWITTER_PROFILE": "Twitter",
|
||||
"TWILIO_SMS": "Twilio SMS",
|
||||
"WHATSAPP": "WhatsApp",
|
||||
"SMS": "SMS",
|
||||
"EMAIL": "Email",
|
||||
"TELEGRAM": "Telegram",
|
||||
"LINE": "Line",
|
||||
"API": "API Channel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,17 +22,21 @@ defineProps({
|
||||
<template>
|
||||
<div class="flex flex-col w-full h-full gap-10 font-inter">
|
||||
<slot name="header" />
|
||||
<slot v-if="isLoading" name="loading">
|
||||
<woot-loading-state :message="loadingMessage" />
|
||||
</slot>
|
||||
<p
|
||||
v-else-if="noRecordsFound"
|
||||
class="flex-1 text-slate-700 dark:text-slate-100 flex items-center justify-center text-base"
|
||||
>
|
||||
{{ noRecordsMessage }}
|
||||
</p>
|
||||
<slot v-else name="body" />
|
||||
<!-- Do not delete the slot below. It is required to render anything that is not defined in the above slots. -->
|
||||
<slot />
|
||||
<!-- Added to render any templates that should be rendered before body -->
|
||||
<div>
|
||||
<slot name="preBody" />
|
||||
<slot v-if="isLoading" name="loading">
|
||||
<woot-loading-state :message="loadingMessage" />
|
||||
</slot>
|
||||
<p
|
||||
v-else-if="noRecordsFound"
|
||||
class="flex-1 text-slate-700 dark:text-slate-100 flex items-center justify-center text-base"
|
||||
>
|
||||
{{ noRecordsMessage }}
|
||||
</p>
|
||||
<slot v-else name="body" />
|
||||
<!-- Do not delete the slot below. It is required to render anything that is not defined in the above slots. -->
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -12,6 +12,12 @@ export default {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
// Passes 0 or 1 based on the selected AttributeModel tab selected in the UI
|
||||
// Needs a better data type, todo: refactor this component later
|
||||
selectedAttributeModelTab: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return { v$: useVuelidate() };
|
||||
@@ -20,7 +26,10 @@ export default {
|
||||
return {
|
||||
displayName: '',
|
||||
description: '',
|
||||
attributeModel: 0,
|
||||
// Using the prop as default. There is no side effect here as the component
|
||||
// is destroyed completely when the modal is closed. The prop doesn't change
|
||||
// dynamically when the modal is active.
|
||||
attributeModel: this.selectedAttributeModelTab || 0,
|
||||
attributeType: 0,
|
||||
attributeKey: '',
|
||||
regexPattern: null,
|
||||
@@ -280,13 +289,16 @@ export default {
|
||||
padding: 0 var(--space-small) var(--space-small) 0;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.multiselect--wrap {
|
||||
margin-bottom: var(--space-normal);
|
||||
|
||||
.error-message {
|
||||
color: var(--r-400);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.invalid {
|
||||
::v-deep {
|
||||
.multiselect__tags {
|
||||
@@ -295,13 +307,16 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.multiselect {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.multiselect__content-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.multiselect--active .multiselect__tags {
|
||||
border-radius: var(--border-radius-normal);
|
||||
}
|
||||
|
||||
+129
-196
@@ -1,195 +1,141 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
<script setup>
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import EditAttribute from './EditAttribute.vue';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
const props = defineProps({
|
||||
attributeModel: {
|
||||
type: String,
|
||||
default: 'conversation_attribute',
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditAttribute,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedTabIndex: 0,
|
||||
showEditPopup: false,
|
||||
showDeletePopup: false,
|
||||
selectedAttribute: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
uiFlags: 'attributes/getUIFlags',
|
||||
}),
|
||||
attributes() {
|
||||
const attributeModel = this.selectedTabIndex
|
||||
? 'contact_attribute'
|
||||
: 'conversation_attribute';
|
||||
const { t } = useI18n();
|
||||
|
||||
return this.$store.getters['attributes/getAttributesByModel'](
|
||||
attributeModel
|
||||
);
|
||||
},
|
||||
tabs() {
|
||||
return [
|
||||
{
|
||||
key: 0,
|
||||
name: this.$t('ATTRIBUTES_MGMT.TABS.CONVERSATION'),
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
name: this.$t('ATTRIBUTES_MGMT.TABS.CONTACT'),
|
||||
},
|
||||
];
|
||||
},
|
||||
deleteConfirmText() {
|
||||
return `${this.$t('ATTRIBUTES_MGMT.DELETE.CONFIRM.YES')} ${
|
||||
this.selectedAttribute.attribute_display_name
|
||||
}`;
|
||||
},
|
||||
deleteRejectText() {
|
||||
return this.$t('ATTRIBUTES_MGMT.DELETE.CONFIRM.NO');
|
||||
},
|
||||
confirmDeleteTitle() {
|
||||
return this.$t('ATTRIBUTES_MGMT.DELETE.CONFIRM.TITLE', {
|
||||
attributeName: this.selectedAttribute.attribute_display_name,
|
||||
});
|
||||
},
|
||||
confirmPlaceHolderText() {
|
||||
return `${this.$t('ATTRIBUTES_MGMT.DELETE.CONFIRM.PLACE_HOLDER', {
|
||||
attributeName: this.selectedAttribute.attribute_display_name,
|
||||
})}`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchAttributes(this.selectedTabIndex);
|
||||
},
|
||||
methods: {
|
||||
onClickTabChange(index) {
|
||||
this.selectedTabIndex = index;
|
||||
this.fetchAttributes(index);
|
||||
},
|
||||
fetchAttributes(index) {
|
||||
this.$store.dispatch('attributes/get', index);
|
||||
},
|
||||
async deleteAttributes({ id }) {
|
||||
try {
|
||||
await this.$store.dispatch('attributes/delete', id);
|
||||
useAlert(this.$t('ATTRIBUTES_MGMT.DELETE.API.SUCCESS_MESSAGE'));
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error?.response?.message ||
|
||||
this.$t('ATTRIBUTES_MGMT.DELETE.API.ERROR_MESSAGE');
|
||||
useAlert(errorMessage);
|
||||
}
|
||||
},
|
||||
openEditPopup(response) {
|
||||
this.showEditPopup = true;
|
||||
this.selectedAttribute = response;
|
||||
},
|
||||
hideEditPopup() {
|
||||
this.showEditPopup = false;
|
||||
},
|
||||
confirmDeletion() {
|
||||
this.deleteAttributes(this.selectedAttribute);
|
||||
this.closeDelete();
|
||||
},
|
||||
openDelete(value) {
|
||||
this.showDeletePopup = true;
|
||||
this.selectedAttribute = value;
|
||||
},
|
||||
closeDelete() {
|
||||
this.showDeletePopup = false;
|
||||
this.selectedAttribute = {};
|
||||
},
|
||||
},
|
||||
const showEditPopup = ref(false);
|
||||
const showDeletePopup = ref(false);
|
||||
const selectedAttribute = ref({});
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const store = useStore();
|
||||
|
||||
const attributes = computed(() =>
|
||||
getters['attributes/getAttributesByModel'].value(props.attributeModel)
|
||||
);
|
||||
const uiFlags = computed(() => getters['attributes/getUIFlags'].value);
|
||||
|
||||
const attributeDisplayName = computed(
|
||||
() => selectedAttribute.value.attribute_display_name
|
||||
);
|
||||
const deleteConfirmText = computed(
|
||||
() =>
|
||||
`${t('ATTRIBUTES_MGMT.DELETE.CONFIRM.YES')} ${attributeDisplayName.value}`
|
||||
);
|
||||
const deleteRejectText = computed(() => t('ATTRIBUTES_MGMT.DELETE.CONFIRM.NO'));
|
||||
const confirmDeleteTitle = computed(() =>
|
||||
t('ATTRIBUTES_MGMT.DELETE.CONFIRM.TITLE', {
|
||||
attributeName: attributeDisplayName.value,
|
||||
})
|
||||
);
|
||||
const confirmPlaceHolderText = computed(
|
||||
() =>
|
||||
`${t('ATTRIBUTES_MGMT.DELETE.CONFIRM.PLACE_HOLDER', {
|
||||
attributeName: attributeDisplayName.value,
|
||||
})}`
|
||||
);
|
||||
|
||||
const deleteAttributes = async ({ id }) => {
|
||||
try {
|
||||
await store.dispatch('attributes/delete', id);
|
||||
useAlert(t('ATTRIBUTES_MGMT.DELETE.API.SUCCESS_MESSAGE'));
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error?.response?.message || t('ATTRIBUTES_MGMT.DELETE.API.ERROR_MESSAGE');
|
||||
useAlert(errorMessage);
|
||||
}
|
||||
};
|
||||
const openEditPopup = response => {
|
||||
showEditPopup.value = true;
|
||||
selectedAttribute.value = response;
|
||||
};
|
||||
const hideEditPopup = () => {
|
||||
showEditPopup.value = false;
|
||||
};
|
||||
|
||||
const closeDelete = () => {
|
||||
showDeletePopup.value = false;
|
||||
selectedAttribute.value = {};
|
||||
};
|
||||
const confirmDeletion = () => {
|
||||
deleteAttributes(selectedAttribute.value);
|
||||
closeDelete();
|
||||
};
|
||||
const openDelete = value => {
|
||||
showDeletePopup.value = true;
|
||||
selectedAttribute.value = value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-row gap-4 p-8">
|
||||
<div class="w-full lg:w-3/5">
|
||||
<woot-tabs :index="selectedTabIndex" @change="onClickTabChange">
|
||||
<woot-tabs-item
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:name="tab.name"
|
||||
:show-badge="false"
|
||||
/>
|
||||
</woot-tabs>
|
||||
|
||||
<div class="w-full">
|
||||
<p
|
||||
v-if="!uiFlags.isFetching && !attributes.length"
|
||||
class="flex items-center justify-center mt-12"
|
||||
<div class="flex flex-col">
|
||||
<table class="min-w-full overflow-x-auto">
|
||||
<thead>
|
||||
<th
|
||||
v-for="tableHeader in $t('ATTRIBUTES_MGMT.LIST.TABLE_HEADER')"
|
||||
:key="tableHeader"
|
||||
class="py-4 ltr:pr-4 rtl:pl-4 text-left font-semibold text-slate-700 dark:text-slate-300"
|
||||
>
|
||||
{{ $t('ATTRIBUTES_MGMT.LIST.EMPTY_RESULT.404') }}
|
||||
</p>
|
||||
<woot-loading-state
|
||||
v-if="uiFlags.isFetching"
|
||||
:message="$t('ATTRIBUTES_MGMT.LOADING')"
|
||||
/>
|
||||
<table
|
||||
v-if="!uiFlags.isFetching && attributes.length"
|
||||
class="w-full mt-2 table-fixed woot-table"
|
||||
>
|
||||
<thead>
|
||||
<th
|
||||
v-for="tableHeader in $t('ATTRIBUTES_MGMT.LIST.TABLE_HEADER')"
|
||||
:key="tableHeader"
|
||||
class="pl-0 max-w-[6.25rem] min-w-[5rem]"
|
||||
>
|
||||
{{ tableHeader }}
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="attribute in attributes" :key="attribute.attribute_key">
|
||||
<td
|
||||
class="pl-0 max-w-[6.25rem] min-w-[5rem] overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
{{ attribute.attribute_display_name }}
|
||||
</td>
|
||||
<td
|
||||
class="pl-0 max-w-[10rem] min-w-[6.25rem] overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
{{ attribute.attribute_description }}
|
||||
</td>
|
||||
<td
|
||||
class="pl-0 max-w-[6.25rem] min-w-[5rem] overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
{{ attribute.attribute_display_type }}
|
||||
</td>
|
||||
<td
|
||||
class="attribute-key pl-0 max-w-[6.25rem] min-w-[5rem] overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
{{ attribute.attribute_key }}
|
||||
</td>
|
||||
<td class="button-wrapper">
|
||||
<woot-button
|
||||
v-tooltip.top="$t('ATTRIBUTES_MGMT.LIST.BUTTONS.EDIT')"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
color-scheme="secondary"
|
||||
class-names="grey-btn"
|
||||
icon="edit"
|
||||
@click="openEditPopup(attribute)"
|
||||
/>
|
||||
<woot-button
|
||||
v-tooltip.top="$t('ATTRIBUTES_MGMT.LIST.BUTTONS.DELETE')"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
size="tiny"
|
||||
icon="dismiss-circle"
|
||||
class-names="grey-btn"
|
||||
@click="openDelete(attribute)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden w-1/3 lg:block">
|
||||
<span v-dompurify-html="$t('ATTRIBUTES_MGMT.SIDEBAR_TXT')" />
|
||||
</div>
|
||||
{{ tableHeader }}
|
||||
</th>
|
||||
</thead>
|
||||
<tbody
|
||||
class="divide-y divide-slate-25 dark:divide-slate-800 flex-1 text-slate-700 dark:text-slate-100"
|
||||
>
|
||||
<tr v-for="attribute in attributes" :key="attribute.attribute_key">
|
||||
<td
|
||||
class="py-4 ltr:pr-4 rtl:pl-4 overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
{{ attribute.attribute_display_name }}
|
||||
</td>
|
||||
<td class="py-4 ltr:pr-4 rtl:pl-4">
|
||||
{{ attribute.attribute_description }}
|
||||
</td>
|
||||
<td
|
||||
class="py-4 ltr:pr-4 rtl:pl-4 overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
{{ attribute.attribute_display_type }}
|
||||
</td>
|
||||
<td
|
||||
class="py-4 ltr:pr-4 rtl:pl-4 attribute-key overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
{{ attribute.attribute_key }}
|
||||
</td>
|
||||
<td class="py-4 min-w-xs">
|
||||
<div class="flex gap-1">
|
||||
<woot-button
|
||||
v-tooltip.top="$t('ATTRIBUTES_MGMT.LIST.BUTTONS.EDIT')"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
color-scheme="secondary"
|
||||
class-names="grey-btn"
|
||||
icon="edit"
|
||||
@click="openEditPopup(attribute)"
|
||||
/>
|
||||
<woot-button
|
||||
v-tooltip.top="$t('ATTRIBUTES_MGMT.LIST.BUTTONS.DELETE')"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
size="tiny"
|
||||
icon="dismiss-circle"
|
||||
class-names="grey-btn"
|
||||
@click="openDelete(attribute)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<woot-modal :show.sync="showEditPopup" :on-close="hideEditPopup">
|
||||
<EditAttribute
|
||||
:selected-attribute="selectedAttribute"
|
||||
@@ -216,17 +162,4 @@ export default {
|
||||
.attribute-key {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.tabs--container {
|
||||
.tabs {
|
||||
@apply p-0;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-title a {
|
||||
font-weight: var(--font-weight-medium);
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,40 +1,112 @@
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
|
||||
import AddAttribute from './AddAttribute.vue';
|
||||
import CustomAttribute from './CustomAttribute.vue';
|
||||
export default {
|
||||
components: {
|
||||
AddAttribute,
|
||||
CustomAttribute,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAddPopup: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openAddPopup() {
|
||||
this.showAddPopup = true;
|
||||
import SettingsLayout from '../SettingsLayout.vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const store = useStore();
|
||||
|
||||
const showAddPopup = ref(false);
|
||||
const selectedTabIndex = ref(0);
|
||||
const uiFlags = computed(() => getters['attributes/getUIFlags'].value);
|
||||
|
||||
const openAddPopup = () => {
|
||||
showAddPopup.value = true;
|
||||
};
|
||||
const hideAddPopup = () => {
|
||||
showAddPopup.value = false;
|
||||
};
|
||||
|
||||
const tabs = computed(() => {
|
||||
return [
|
||||
{
|
||||
key: 0,
|
||||
name: t('ATTRIBUTES_MGMT.TABS.CONVERSATION'),
|
||||
},
|
||||
hideAddPopup() {
|
||||
this.showAddPopup = false;
|
||||
{
|
||||
key: 1,
|
||||
name: t('ATTRIBUTES_MGMT.TABS.CONTACT'),
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('attributes/get');
|
||||
});
|
||||
|
||||
const attributeModel = computed(() =>
|
||||
selectedTabIndex.value ? 'contact_attribute' : 'conversation_attribute'
|
||||
);
|
||||
|
||||
const attributes = computed(() =>
|
||||
getters['attributes/getAttributesByModel'].value(attributeModel.value)
|
||||
);
|
||||
|
||||
const onClickTabChange = index => {
|
||||
selectedTabIndex.value = index;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 overflow-auto">
|
||||
<woot-button
|
||||
color-scheme="success"
|
||||
class-names="button--fixed-top"
|
||||
icon="add-circle"
|
||||
@click="openAddPopup()"
|
||||
<SettingsLayout
|
||||
:is-loading="uiFlags.isFetching"
|
||||
:loading-message="$t('ATTRIBUTES_MGMT.LOADING')"
|
||||
:no-records-found="!attributes.length"
|
||||
:no-records-message="$t('ATTRIBUTES_MGMT.LIST.EMPTY_RESULT.404')"
|
||||
>
|
||||
<template #header>
|
||||
<BaseSettingsHeader
|
||||
:title="$t('ATTRIBUTES_MGMT.HEADER')"
|
||||
:description="$t('ATTRIBUTES_MGMT.DESCRIPTION')"
|
||||
:link-text="$t('ATTRIBUTES_MGMT.LEARN_MORE')"
|
||||
feature-name="custom_attributes"
|
||||
>
|
||||
<template #actions>
|
||||
<woot-button
|
||||
class="button nice rounded-md"
|
||||
icon="add-circle"
|
||||
@click="openAddPopup"
|
||||
>
|
||||
{{ $t('ATTRIBUTES_MGMT.HEADER_BTN_TXT') }}
|
||||
</woot-button>
|
||||
</template>
|
||||
</BaseSettingsHeader>
|
||||
</template>
|
||||
<template #preBody>
|
||||
<woot-tabs
|
||||
class="font-medium [&_.tabs]:p-0 mb-4"
|
||||
:index="selectedTabIndex"
|
||||
@change="onClickTabChange"
|
||||
>
|
||||
<woot-tabs-item
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:name="tab.name"
|
||||
:show-badge="false"
|
||||
/>
|
||||
</woot-tabs>
|
||||
</template>
|
||||
<template #body>
|
||||
<CustomAttribute
|
||||
:key="attributeModel"
|
||||
:attribute-model="attributeModel"
|
||||
/>
|
||||
</template>
|
||||
<woot-modal
|
||||
v-if="showAddPopup"
|
||||
:show.sync="showAddPopup"
|
||||
:on-close="hideAddPopup"
|
||||
>
|
||||
{{ $t('ATTRIBUTES_MGMT.HEADER_BTN_TXT') }}
|
||||
</woot-button>
|
||||
<CustomAttribute />
|
||||
<woot-modal :show.sync="showAddPopup" :on-close="hideAddPopup">
|
||||
<AddAttribute :on-close="hideAddPopup" />
|
||||
<AddAttribute
|
||||
:on-close="hideAddPopup"
|
||||
:selected-attribute-model-tab="selectedTabIndex"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</SettingsLayout>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const AttributesHome = () => import('./Index.vue');
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/settings/custom-attributes'),
|
||||
component: SettingsContent,
|
||||
props: {
|
||||
headerTitle: 'ATTRIBUTES_MGMT.HEADER',
|
||||
icon: 'code',
|
||||
showNewButton: false,
|
||||
},
|
||||
component: SettingsWrapper,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
|
||||
@@ -1,219 +1,177 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
<script setup>
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
import Settings from './Settings.vue';
|
||||
import { useInstallationName } from 'shared/helpers/installationNameHelper';
|
||||
import SettingsLayout from '../SettingsLayout.vue';
|
||||
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import ChannelName from './components/ChannelName.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Settings,
|
||||
},
|
||||
setup() {
|
||||
const { isAdmin } = useAdmin();
|
||||
const { accountScopedUrl } = useAccount();
|
||||
return {
|
||||
isAdmin,
|
||||
accountScopedUrl,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: {},
|
||||
showSettings: false,
|
||||
showDeletePopup: false,
|
||||
selectedInbox: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
inboxesList: 'inboxes/getInboxes',
|
||||
globalConfig: 'globalConfig/get',
|
||||
}),
|
||||
// Delete Modal
|
||||
deleteConfirmText() {
|
||||
return `${this.$t('INBOX_MGMT.DELETE.CONFIRM.YES')} ${
|
||||
this.selectedInbox.name
|
||||
}`;
|
||||
},
|
||||
deleteRejectText() {
|
||||
return `${this.$t('INBOX_MGMT.DELETE.CONFIRM.NO')} ${
|
||||
this.selectedInbox.name
|
||||
}`;
|
||||
},
|
||||
confirmDeleteMessage() {
|
||||
return `${this.$t('INBOX_MGMT.DELETE.CONFIRM.MESSAGE')} ${
|
||||
this.selectedInbox.name
|
||||
}?`;
|
||||
},
|
||||
confirmPlaceHolderText() {
|
||||
return `${this.$t('INBOX_MGMT.DELETE.CONFIRM.PLACE_HOLDER', {
|
||||
inboxName: this.selectedInbox.name,
|
||||
})}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
useInstallationName,
|
||||
twilioChannelName(item) {
|
||||
const { medium = '' } = item;
|
||||
if (medium === 'whatsapp') return 'WhatsApp';
|
||||
return 'Twilio SMS';
|
||||
},
|
||||
openSettings(inbox) {
|
||||
this.showSettings = true;
|
||||
this.selectedInbox = inbox;
|
||||
},
|
||||
closeSettings() {
|
||||
this.showSettings = false;
|
||||
this.selectedInbox = {};
|
||||
},
|
||||
async deleteInbox({ id }) {
|
||||
try {
|
||||
await this.$store.dispatch('inboxes/delete', id);
|
||||
useAlert(this.$t('INBOX_MGMT.DELETE.API.SUCCESS_MESSAGE'));
|
||||
} catch (error) {
|
||||
useAlert(this.$t('INBOX_MGMT.DELETE.API.ERROR_MESSAGE'));
|
||||
}
|
||||
},
|
||||
const getters = useStoreGetters();
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
const { isAdmin } = useAdmin();
|
||||
|
||||
confirmDeletion() {
|
||||
this.deleteInbox(this.selectedInbox);
|
||||
this.closeDelete();
|
||||
},
|
||||
openDelete(inbox) {
|
||||
this.showDeletePopup = true;
|
||||
this.selectedInbox = inbox;
|
||||
},
|
||||
closeDelete() {
|
||||
this.showDeletePopup = false;
|
||||
this.selectedInbox = {};
|
||||
},
|
||||
},
|
||||
const showDeletePopup = ref(false);
|
||||
const selectedInbox = ref({});
|
||||
|
||||
const inboxesList = computed(() => getters['inboxes/getInboxes'].value);
|
||||
const uiFlags = computed(() => getters['inboxes/getUIFlags'].value);
|
||||
|
||||
const deleteConfirmText = computed(
|
||||
() => `${t('INBOX_MGMT.DELETE.CONFIRM.YES')} ${selectedInbox.value.name}`
|
||||
);
|
||||
|
||||
const deleteRejectText = computed(
|
||||
() => `${t('INBOX_MGMT.DELETE.CONFIRM.NO')} ${selectedInbox.value.name}`
|
||||
);
|
||||
|
||||
const confirmDeleteMessage = computed(
|
||||
() => `${t('INBOX_MGMT.DELETE.CONFIRM.MESSAGE')} ${selectedInbox.value.name}?`
|
||||
);
|
||||
const confirmPlaceHolderText = computed(
|
||||
() =>
|
||||
`${t('INBOX_MGMT.DELETE.CONFIRM.PLACE_HOLDER', {
|
||||
inboxName: selectedInbox.value.name,
|
||||
})}`
|
||||
);
|
||||
|
||||
const deleteInbox = async ({ id }) => {
|
||||
try {
|
||||
await store.dispatch('inboxes/delete', id);
|
||||
useAlert(t('INBOX_MGMT.DELETE.API.SUCCESS_MESSAGE'));
|
||||
} catch (error) {
|
||||
useAlert(t('INBOX_MGMT.DELETE.API.ERROR_MESSAGE'));
|
||||
}
|
||||
};
|
||||
const closeDelete = () => {
|
||||
showDeletePopup.value = false;
|
||||
selectedInbox.value = {};
|
||||
};
|
||||
|
||||
const confirmDeletion = () => {
|
||||
deleteInbox(selectedInbox.value);
|
||||
closeDelete();
|
||||
};
|
||||
const openDelete = inbox => {
|
||||
showDeletePopup.value = true;
|
||||
selectedInbox.value = inbox;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 overflow-auto">
|
||||
<div class="flex flex-row gap-4 p-8">
|
||||
<div class="w-full lg:w-3/5">
|
||||
<p
|
||||
v-if="!inboxesList.length"
|
||||
class="flex flex-col items-center justify-center h-full"
|
||||
>
|
||||
{{ $t('INBOX_MGMT.LIST.404') }}
|
||||
<SettingsLayout
|
||||
:no-records-found="!inboxesList.length"
|
||||
:no-records-message="$t('INBOX_MGMT.LIST.404')"
|
||||
:is-loading="uiFlags.isFetching"
|
||||
>
|
||||
<template #header>
|
||||
<BaseSettingsHeader
|
||||
:title="$t('INBOX_MGMT.HEADER')"
|
||||
:description="$t('INBOX_MGMT.DESCRIPTION')"
|
||||
:link-text="$t('INBOX_MGMT.LEARN_MORE')"
|
||||
feature-name="inboxes"
|
||||
>
|
||||
<template #actions>
|
||||
<router-link
|
||||
v-if="isAdmin"
|
||||
:to="accountScopedUrl('settings/inboxes/new')"
|
||||
class="button nice rounded-md"
|
||||
:to="{ name: 'settings_inbox_new' }"
|
||||
>
|
||||
<fluent-icon icon="add-circle" />
|
||||
{{ $t('SETTINGS.INBOXES.NEW_INBOX') }}
|
||||
</router-link>
|
||||
</p>
|
||||
|
||||
<table v-if="inboxesList.length" class="woot-table">
|
||||
<tbody>
|
||||
<tr v-for="item in inboxesList" :key="item.id">
|
||||
<td>
|
||||
<img
|
||||
v-if="item.avatar_url"
|
||||
class="woot-thumbnail"
|
||||
:src="item.avatar_url"
|
||||
alt="No Page Image"
|
||||
</template>
|
||||
</BaseSettingsHeader>
|
||||
</template>
|
||||
<template #body>
|
||||
<table
|
||||
class="min-w-full overflow-x-auto divide-y divide-slate-75 dark:divide-slate-700"
|
||||
>
|
||||
<tbody
|
||||
class="divide-y divide-slate-25 dark:divide-slate-800 flex-1 text-slate-700 dark:text-slate-100"
|
||||
>
|
||||
<tr v-for="inbox in inboxesList" :key="inbox.id">
|
||||
<td class="py-4 ltr:pr-4 rtl:pl-4">
|
||||
<div class="flex items-center flex-row gap-4">
|
||||
<Thumbnail
|
||||
v-if="inbox.avatar_url"
|
||||
class="bg-black-50 dark:bg-black-800 rounded-full p-2 ring ring-opacity-20 dark:ring-opacity-80 ring-black-100 dark:ring-black-900 border border-slate-100 dark:border-slate-700/50 shadow-sm"
|
||||
:src="inbox.avatar_url"
|
||||
:username="inbox.name"
|
||||
size="48px"
|
||||
/>
|
||||
<img
|
||||
<div
|
||||
v-else
|
||||
class="woot-thumbnail"
|
||||
src="~dashboard/assets/images/flag.svg"
|
||||
alt="No Page Image"
|
||||
/>
|
||||
</td>
|
||||
<!-- Short Code -->
|
||||
<td>
|
||||
<span class="agent-name">{{ item.name }}</span>
|
||||
<span v-if="item.channel_type === 'Channel::FacebookPage'">
|
||||
{{ 'Facebook' }}
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::WebWidget'">
|
||||
{{ 'Website' }}
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::TwitterProfile'">
|
||||
{{ 'Twitter' }}
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::TwilioSms'">
|
||||
{{ twilioChannelName(item) }}
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::Whatsapp'">
|
||||
{{ 'Whatsapp' }}
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::Sms'">
|
||||
{{ 'Sms' }}
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::Email'">
|
||||
{{ 'Email' }}
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::Telegram'">
|
||||
{{ 'Telegram' }}
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::Line'">
|
||||
{{ 'Line' }}
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::Api'">
|
||||
{{ globalConfig.apiChannelName || 'API' }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<td>
|
||||
<div class="button-wrapper">
|
||||
<router-link
|
||||
:to="accountScopedUrl(`settings/inboxes/${item.id}`)"
|
||||
class="w-12 h-12 bg-black-50 dark:bg-black-800 rounded-full p-2 ring ring-opacity-20 dark:ring-opacity-80 ring-black-100 dark:ring-black-900 border border-slate-100 dark:border-slate-700/50 shadow-sm block"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 24 24"
|
||||
class="opacity-80 p-1"
|
||||
>
|
||||
<woot-button
|
||||
v-if="isAdmin"
|
||||
v-tooltip.top="$t('INBOX_MGMT.SETTINGS')"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
icon="settings"
|
||||
color-scheme="secondary"
|
||||
class-names="grey-btn"
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M1 12c0-5.185 0-7.778 1.61-9.39C4.223 1 6.816 1 12 1s7.778 0 9.39 1.61C23 4.223 23 6.816 23 12s0 7.778-1.61 9.39C19.777 23 17.184 23 12 23s-7.778 0-9.39-1.61C1 19.777 1 17.184 1 12"
|
||||
opacity=".35"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
<woot-button
|
||||
v-if="isAdmin"
|
||||
v-tooltip.top="$t('INBOX_MGMT.DELETE.BUTTON_TEXT')"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
size="tiny"
|
||||
class-names="grey-btn"
|
||||
:is-loading="loading[item.id]"
|
||||
icon="dismiss-circle"
|
||||
@click="openDelete(item)"
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M2.61 21.389c1.612 1.61 4.205 1.61 9.39 1.61s7.778 0 9.39-1.61c1.492-1.493 1.601-3.829 1.61-8.29h-3.476c-.996 0-1.494 0-1.931.202c-.438.201-.762.58-1.41 1.335l-.666.777c-.648.756-.972 1.134-1.41 1.335s-.935.202-1.93.202h-.353c-.996 0-1.494 0-1.931-.202c-.438-.2-.762-.579-1.41-1.335l-.666-.777c-.648-.756-.972-1.134-1.41-1.335s-.935-.201-1.93-.201H1c.008 4.46.118 6.796 1.61 8.289"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block font-medium capitalize">
|
||||
{{ inbox.name }}
|
||||
</span>
|
||||
<ChannelName
|
||||
:channel-type="inbox.channel_type"
|
||||
:medium="inbox.medium"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<div class="hidden w-1/3 lg:block">
|
||||
<span
|
||||
v-dompurify-html="
|
||||
useInstallationName(
|
||||
$t('INBOX_MGMT.SIDEBAR_TXT'),
|
||||
globalConfig.installationName
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Settings
|
||||
v-if="showSettings"
|
||||
:show.sync="showSettings"
|
||||
:on-close="closeSettings"
|
||||
:inbox="selectedInbox"
|
||||
/>
|
||||
<td class="py-4">
|
||||
<div class="flex gap-1 justify-end">
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'settings_inbox_show',
|
||||
params: { inboxId: inbox.id },
|
||||
}"
|
||||
>
|
||||
<woot-button
|
||||
v-if="isAdmin"
|
||||
v-tooltip.top="$t('INBOX_MGMT.SETTINGS')"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
icon="settings"
|
||||
color-scheme="secondary"
|
||||
class-names="grey-btn"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
<woot-button
|
||||
v-if="isAdmin"
|
||||
v-tooltip.top="$t('INBOX_MGMT.DELETE.BUTTON_TEXT')"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
size="tiny"
|
||||
class-names="grey-btn"
|
||||
icon="dismiss-circle"
|
||||
@click="openDelete(inbox)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<woot-confirm-delete-modal
|
||||
v-if="showDeletePopup"
|
||||
@@ -227,5 +185,5 @@ export default {
|
||||
@onConfirm="confirmDeletion"
|
||||
@onClose="closeDelete"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</SettingsLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup>
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useStoreGetters } from 'dashboard/composables/store';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
channelType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
medium: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const getters = useStoreGetters();
|
||||
const { t } = useI18n();
|
||||
const globalConfig = computed(() => getters['globalConfig/get'].value);
|
||||
|
||||
const i18nMap = {
|
||||
'Channel::FacebookPage': 'MESSENGER',
|
||||
'Channel::WebWidget': 'WEB_WIDGET',
|
||||
'Channel::TwitterProfile': 'TWITTER_PROFILE',
|
||||
'Channel::TwilioSms': 'TWILIO_SMS',
|
||||
'Channel::Whatsapp': 'WHATSAPP',
|
||||
'Channel::Sms': 'SMS',
|
||||
'Channel::Email': 'EMAIL',
|
||||
'Channel::Telegram': 'TELEGRAM',
|
||||
'Channel::Line': 'LINE',
|
||||
'Channel::Api': 'API',
|
||||
};
|
||||
|
||||
const twilioChannelName = () => {
|
||||
if (props.medium === 'whatsapp') {
|
||||
return t(`INBOX_MGMT.CHANNELS.WHATSAPP`);
|
||||
}
|
||||
return t(`INBOX_MGMT.CHANNELS.TWILIO_SMS`);
|
||||
};
|
||||
|
||||
const readableChannelName = computed(() => {
|
||||
if (props.channelType === 'Channel::Api') {
|
||||
return globalConfig.value.apiChannelName || t('INBOX_MGMT.CHANNELS.API');
|
||||
}
|
||||
if (props.channelType === 'Channel::TwilioSms') {
|
||||
return twilioChannelName();
|
||||
}
|
||||
return t(`INBOX_MGMT.CHANNELS.${i18nMap[props.channelType]}`);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
{{ readableChannelName }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint arrow-body-style: 0 */
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
import channelFactory from './channel-factory';
|
||||
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const SettingWrapper = () => import('../SettingsWrapper.vue');
|
||||
const InboxHome = () => import('./Index.vue');
|
||||
const Settings = () => import('./Settings.vue');
|
||||
const InboxChannel = () => import('./InboxChannels.vue');
|
||||
@@ -12,6 +12,24 @@ const FinishSetup = () => import('./FinishSetup.vue');
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/settings/inboxes'),
|
||||
component: SettingWrapper,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: 'list',
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
name: 'settings_inbox_list',
|
||||
component: InboxHome,
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/settings/inboxes'),
|
||||
component: SettingsContent,
|
||||
@@ -26,18 +44,6 @@ export default {
|
||||
};
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: 'list',
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
name: 'settings_inbox_list',
|
||||
component: InboxHome,
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'new',
|
||||
component: InboxChannel,
|
||||
|
||||
Reference in New Issue
Block a user