feat: Update the design for the Inbox management console (#10043)
This is the continuation of the design update PR. This changes the design for the inbox pages. --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||
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,
|
||||
},
|
||||
mixins: [globalConfigMixin],
|
||||
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: {
|
||||
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>
|
||||
</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