Merge branch 'develop' into deploy-apr-15

This commit is contained in:
Pranav
2024-07-08 19:47:10 -07:00
264 changed files with 6600 additions and 1471 deletions
@@ -110,11 +110,11 @@ export default {
mounted() {
this.handleResize();
window.addEventListener('resize', this.handleResize);
bus.$on(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
this.$emitter.on(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
bus.$off(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
this.$emitter.off(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
},
methods: {
@@ -11,6 +11,7 @@ import {
ICON_REOPEN_CONVERSATION,
ICON_RESOLVE_CONVERSATION,
} from './CommandBarIcons';
import { emitter } from 'shared/helpers/mitt';
import { createSnoozeHandlers } from './commandBarActions';
@@ -37,7 +38,7 @@ export const RESOLVED_CONVERSATION_BULK_ACTIONS = [
title: 'COMMAND_BAR.COMMANDS.REOPEN_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_REOPEN_CONVERSATION,
handler: () => bus.$emit(CMD_BULK_ACTION_REOPEN_CONVERSATION),
handler: () => emitter.emit(CMD_BULK_ACTION_REOPEN_CONVERSATION),
},
];
@@ -47,7 +48,7 @@ export const OPEN_CONVERSATION_BULK_ACTIONS = [
title: 'COMMAND_BAR.COMMANDS.RESOLVE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_RESOLVE_CONVERSATION,
handler: () => bus.$emit(CMD_BULK_ACTION_RESOLVE_CONVERSATION),
handler: () => emitter.emit(CMD_BULK_ACTION_RESOLVE_CONVERSATION),
},
];
@@ -1,4 +1,5 @@
import wootConstants from 'dashboard/constants/globals';
import { emitter } from 'shared/helpers/mitt';
import {
CMD_MUTE_CONVERSATION,
@@ -26,7 +27,7 @@ export const OPEN_CONVERSATION_ACTIONS = [
title: 'COMMAND_BAR.COMMANDS.RESOLVE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.CONVERSATION',
icon: ICON_RESOLVE_CONVERSATION,
handler: () => bus.$emit(CMD_RESOLVE_CONVERSATION),
handler: () => emitter.emit(CMD_RESOLVE_CONVERSATION),
},
];
@@ -37,7 +38,7 @@ export const createSnoozeHandlers = (busEventName, parentId, section) => {
parent: parentId,
section: section,
icon: ICON_SNOOZE_CONVERSATION,
handler: () => bus.$emit(busEventName, option),
handler: () => emitter.emit(busEventName, option),
}));
};
@@ -61,7 +62,7 @@ export const RESOLVED_CONVERSATION_ACTIONS = [
title: 'COMMAND_BAR.COMMANDS.REOPEN_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.CONVERSATION',
icon: ICON_REOPEN_CONVERSATION,
handler: () => bus.$emit(CMD_REOPEN_CONVERSATION),
handler: () => emitter.emit(CMD_REOPEN_CONVERSATION),
},
];
@@ -70,7 +71,7 @@ export const SEND_TRANSCRIPT_ACTION = {
title: 'COMMAND_BAR.COMMANDS.SEND_TRANSCRIPT',
section: 'COMMAND_BAR.SECTIONS.CONVERSATION',
icon: ICON_SEND_TRANSCRIPT,
handler: () => bus.$emit(CMD_SEND_TRANSCRIPT),
handler: () => emitter.emit(CMD_SEND_TRANSCRIPT),
};
export const UNMUTE_ACTION = {
@@ -78,7 +79,7 @@ export const UNMUTE_ACTION = {
title: 'COMMAND_BAR.COMMANDS.UNMUTE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.CONVERSATION',
icon: ICON_UNMUTE_CONVERSATION,
handler: () => bus.$emit(CMD_UNMUTE_CONVERSATION),
handler: () => emitter.emit(CMD_UNMUTE_CONVERSATION),
};
export const MUTE_ACTION = {
@@ -86,5 +87,5 @@ export const MUTE_ACTION = {
title: 'COMMAND_BAR.COMMANDS.MUTE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.CONVERSATION',
icon: ICON_MUTE_CONVERSATION,
handler: () => bus.$emit(CMD_MUTE_CONVERSATION),
handler: () => emitter.emit(CMD_MUTE_CONVERSATION),
};
@@ -1,5 +1,6 @@
import { mapGetters } from 'vuex';
import wootConstants from 'dashboard/constants/globals';
import { emitter } from 'shared/helpers/mitt';
import { CMD_AI_ASSIST } from './commandBarBusEvents';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
@@ -318,7 +319,7 @@ export default {
section: this.$t('COMMAND_BAR.SECTIONS.AI_ASSIST'),
priority: item,
icon: item.icon,
handler: () => bus.$emit(CMD_AI_ASSIST, item.key),
handler: () => emitter.emit(CMD_AI_ASSIST, item.key),
}));
return [
{
@@ -2,6 +2,7 @@ import wootConstants from 'dashboard/constants/globals';
import { CMD_SNOOZE_NOTIFICATION } from './commandBarBusEvents';
import { ICON_SNOOZE_NOTIFICATION } from './CommandBarIcons';
import { emitter } from 'shared/helpers/mitt';
import { isAInboxViewRoute } from 'dashboard/helper/routeHelpers';
@@ -21,7 +22,7 @@ const INBOX_SNOOZE_EVENTS = [
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
bus.$emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.AN_HOUR_FROM_NOW),
emitter.emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.AN_HOUR_FROM_NOW),
},
{
id: SNOOZE_OPTIONS.UNTIL_TOMORROW,
@@ -30,7 +31,7 @@ const INBOX_SNOOZE_EVENTS = [
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
bus.$emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_TOMORROW),
emitter.emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_TOMORROW),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_WEEK,
@@ -39,7 +40,7 @@ const INBOX_SNOOZE_EVENTS = [
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
bus.$emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_NEXT_WEEK),
emitter.emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_NEXT_WEEK),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_MONTH,
@@ -48,7 +49,7 @@ const INBOX_SNOOZE_EVENTS = [
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
bus.$emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_NEXT_MONTH),
emitter.emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_NEXT_MONTH),
},
{
id: SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME,
@@ -57,7 +58,7 @@ const INBOX_SNOOZE_EVENTS = [
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
bus.$emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME),
emitter.emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME),
},
];
export default {
@@ -7,13 +7,17 @@ export const routes = [
{
path: frontendURL('accounts/:accountId/contacts'),
name: 'contacts_dashboard',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ContactsView,
},
{
path: frontendURL('accounts/:accountId/contacts/custom_view/:id'),
name: 'contacts_segments_dashboard',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ContactsView,
props: route => {
return { segmentsId: route.params.id };
@@ -22,7 +26,9 @@ export const routes = [
{
path: frontendURL('accounts/:accountId/labels/:label/contacts'),
name: 'contacts_labels_dashboard',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ContactsView,
props: route => {
return { label: route.params.label };
@@ -31,7 +37,9 @@ export const routes = [
{
path: frontendURL('accounts/:accountId/contacts/:contactId'),
name: 'contact_profile_dashboard',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ContactManageView,
props: route => {
return { contactId: route.params.contactId };
@@ -145,7 +145,6 @@ import CustomAttributes from './customAttributes/CustomAttributes.vue';
import draggable from 'vuedraggable';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import MacrosList from './Macros/List.vue';
export default {
components: {
AccordionItem,
@@ -19,12 +19,12 @@ const initiatedAt = computed(
() => props.conversationAttributes.initiated_at?.timestamp
);
const browserInfo = props.conversationAttributes.browser;
const browserInfo = computed(() => props.conversationAttributes.browser);
const browserName = computed(() => {
if (!browserInfo) return '';
if (!browserInfo.value) return '';
const { browser_name: name = '', browser_version: version = '' } =
browserInfo;
browserInfo.value;
return `${name} ${version}`;
});
@@ -33,9 +33,9 @@ const browserLanguage = computed(() =>
);
const platformName = computed(() => {
if (!browserInfo) return '';
if (!browserInfo.value) return '';
const { platform_name: name = '', platform_version: version = '' } =
browserInfo;
browserInfo.value;
return `${name} ${version}`;
});
@@ -22,25 +22,40 @@
:is-on-expanded-layout="isOnExpandedLayout"
@contact-panel-toggle="onToggleContactPanel"
/>
<woot-modal
:show.sync="showCustomSnoozeModal"
:on-close="hideCustomSnoozeModal"
>
<custom-snooze-modal
@close="hideCustomSnoozeModal"
@choose-time="chooseSnoozeTime"
/>
</woot-modal>
</section>
</template>
<script>
import { mapGetters } from 'vuex';
import { getUnixTime } from 'date-fns';
import ChatList from '../../../components/ChatList.vue';
import ConversationBox from '../../../components/widgets/conversation/ConversationBox.vue';
import PopOverSearch from './search/PopOverSearch.vue';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import alertMixin from 'shared/mixins/alertMixin';
import wootConstants from 'dashboard/constants/globals';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import { CMD_SNOOZE_CONVERSATION } from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
export default {
components: {
ChatList,
ConversationBox,
PopOverSearch,
CustomSnoozeModal,
},
mixins: [uiSettingsMixin],
mixins: [uiSettingsMixin, alertMixin],
props: {
inboxId: {
type: [String, Number],
@@ -70,12 +85,14 @@ export default {
data() {
return {
showSearchModal: false,
showCustomSnoozeModal: false,
};
},
computed: {
...mapGetters({
chatList: 'getAllConversations',
currentChat: 'getSelectedChat',
contextMenuChatId: 'getContextMenuChatId',
}),
showConversationList() {
return this.isOnExpandedLayout ? !this.conversationId : true;
@@ -112,6 +129,10 @@ export default {
this.$watch('chatList.length', () => {
this.setActiveChat();
});
this.$emitter.on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
},
beforeDestroy() {
this.$emitter.off(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
},
methods: {
@@ -169,7 +190,7 @@ export default {
after: messageId,
})
.then(() => {
bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE, { messageId });
this.$emitter.emit(BUS_EVENTS.SCROLL_TO_MESSAGE, { messageId });
});
} else {
this.$store.dispatch('clearSelectedState');
@@ -186,6 +207,43 @@ export default {
closeSearch() {
this.showSearchModal = false;
},
onCmdSnoozeConversation(snoozeType) {
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
this.showCustomSnoozeModal = true;
} else {
this.toggleStatus(
wootConstants.STATUS_TYPE.SNOOZED,
findSnoozeTime(snoozeType) || null
);
}
},
chooseSnoozeTime(customSnoozeTime) {
this.showCustomSnoozeModal = false;
if (customSnoozeTime) {
this.toggleStatus(
wootConstants.STATUS_TYPE.SNOOZED,
getUnixTime(customSnoozeTime)
);
}
},
toggleStatus(status, snoozedUntil) {
this.$store
.dispatch('toggleStatus', {
conversationId: this.currentChat?.id || this.contextMenuChatId,
status,
snoozedUntil,
})
.then(() => {
this.$store.dispatch('setContextMenuChatId', null);
this.showAlert(this.$t('CONVERSATION.CHANGE_STATUS'));
});
},
hideCustomSnoozeModal() {
// if we select custom snooze and the custom snooze modal is open
// Then if the custom snooze modal is closed then set the context menu chat id to null
this.$store.dispatch('setContextMenuChatId', null);
this.showCustomSnoozeModal = false;
},
},
};
</script>
@@ -271,7 +271,10 @@ export default {
},
toggleConversationModal() {
this.showConversationModal = !this.showConversationModal;
bus.$emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, this.showConversationModal);
this.$emitter.emit(
BUS_EVENTS.NEW_CONVERSATION_MODAL,
this.showConversationModal
);
},
toggleDeleteModal() {
this.showDeleteModal = !this.showDeleteModal;
@@ -7,7 +7,9 @@ export default {
{
path: frontendURL('accounts/:accountId/dashboard'),
name: 'home',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: () => {
return { inboxId: 0 };
@@ -16,7 +18,9 @@ export default {
{
path: frontendURL('accounts/:accountId/conversations/:conversation_id'),
name: 'inbox_conversation',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => {
return { inboxId: 0, conversationId: route.params.conversation_id };
@@ -25,7 +29,9 @@ export default {
{
path: frontendURL('accounts/:accountId/inbox/:inbox_id'),
name: 'inbox_dashboard',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => {
return { inboxId: route.params.inbox_id };
@@ -36,7 +42,9 @@ export default {
'accounts/:accountId/inbox/:inbox_id/conversations/:conversation_id'
),
name: 'conversation_through_inbox',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => {
return {
@@ -48,7 +56,9 @@ export default {
{
path: frontendURL('accounts/:accountId/label/:label'),
name: 'label_conversations',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({ label: route.params.label }),
},
@@ -57,7 +67,9 @@ export default {
'accounts/:accountId/label/:label/conversations/:conversation_id'
),
name: 'conversations_through_label',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversation_id,
@@ -67,7 +79,9 @@ export default {
{
path: frontendURL('accounts/:accountId/team/:teamId'),
name: 'team_conversations',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({ teamId: route.params.teamId }),
},
@@ -76,7 +90,9 @@ export default {
'accounts/:accountId/team/:teamId/conversations/:conversationId'
),
name: 'conversations_through_team',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversationId,
@@ -86,7 +102,9 @@ export default {
{
path: frontendURL('accounts/:accountId/custom_view/:id'),
name: 'folder_conversations',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({ foldersId: route.params.id }),
},
@@ -95,7 +113,9 @@ export default {
'accounts/:accountId/custom_view/:id/conversations/:conversation_id'
),
name: 'conversations_through_folders',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversation_id,
@@ -105,7 +125,9 @@ export default {
{
path: frontendURL('accounts/:accountId/mentions/conversations'),
name: 'conversation_mentions',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: () => ({ conversationType: 'mention' }),
},
@@ -114,7 +136,9 @@ export default {
'accounts/:accountId/mentions/conversations/:conversationId'
),
name: 'conversation_through_mentions',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversationId,
@@ -124,7 +148,9 @@ export default {
{
path: frontendURL('accounts/:accountId/unattended/conversations'),
name: 'conversation_unattended',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: () => ({ conversationType: 'unattended' }),
},
@@ -133,7 +159,9 @@ export default {
'accounts/:accountId/unattended/conversations/:conversationId'
),
name: 'conversation_through_unattended',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversationId,
@@ -143,7 +171,9 @@ export default {
{
path: frontendURL('accounts/:accountId/participating/conversations'),
name: 'conversation_participating',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: () => ({ conversationType: 'participating' }),
},
@@ -152,7 +182,9 @@ export default {
'accounts/:accountId/participating/conversations/:conversationId'
),
name: 'conversation_through_participating',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversationId,
@@ -28,7 +28,9 @@ export default {
{
path: frontendURL('accounts/:accountId/suspended'),
name: 'account_suspended',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: Suspended,
},
],
@@ -265,7 +265,7 @@ export default {
},
mounted() {
bus.$on(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
this.$emitter.on(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
const slug = this.$route.params.portalSlug;
if (slug) this.lastActivePortalSlug = slug;
@@ -273,7 +273,7 @@ export default {
this.fetchPortalAndItsCategories();
},
beforeDestroy() {
bus.$off(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
this.$emitter.off(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
},
updated() {
const slug = this.$route.params.portalSlug;
@@ -30,13 +30,17 @@ const portalRoutes = [
{
path: getPortalRoute(''),
name: 'default_portal_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator'],
},
component: DefaultPortalArticles,
},
{
path: getPortalRoute('all'),
name: 'list_all_portals',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllPortals,
},
{
@@ -47,55 +51,73 @@ const portalRoutes = [
path: '',
name: 'new_portal_information',
component: PortalDetails,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':portalSlug/customization',
name: 'portal_customization',
component: PortalCustomization,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':portalSlug/finish',
name: 'portal_finish',
component: PortalSettingsFinish,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},
{
path: getPortalRoute(':portalSlug'),
name: 'portalSlug',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ShowPortal,
},
{
path: getPortalRoute(':portalSlug/edit'),
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: EditPortal,
children: [
{
path: '',
name: 'edit_portal_information',
component: EditPortalBasic,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'customizations',
name: 'edit_portal_customization',
component: EditPortalCustomization,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'locales',
name: 'edit_portal_locales',
component: EditPortalLocales,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'categories',
name: 'list_all_locale_categories',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllCategories,
},
],
@@ -106,39 +128,51 @@ const articleRoutes = [
{
path: getPortalRoute(':portalSlug/:locale/articles'),
name: 'list_all_locale_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllArticles,
},
{
path: getPortalRoute(':portalSlug/:locale/articles/new'),
name: 'new_article',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: NewArticle,
},
{
path: getPortalRoute(':portalSlug/:locale/articles/mine'),
name: 'list_mine_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllArticles,
},
{
path: getPortalRoute(':portalSlug/:locale/articles/archived'),
name: 'list_archived_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllArticles,
},
{
path: getPortalRoute(':portalSlug/:locale/articles/draft'),
name: 'list_draft_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllArticles,
},
{
path: getPortalRoute(':portalSlug/:locale/articles/:articleSlug'),
name: 'edit_article',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: EditArticle,
},
];
@@ -147,19 +181,25 @@ const categoryRoutes = [
{
path: getPortalRoute(':portalSlug/:locale/categories'),
name: 'all_locale_categories',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllCategories,
},
{
path: getPortalRoute(':portalSlug/:locale/categories/new'),
name: 'new_category_in_locale',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: NewCategory,
},
{
path: getPortalRoute(':portalSlug/:locale/categories/:categorySlug'),
name: 'show_category',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllArticles,
},
{
@@ -167,13 +207,17 @@ const categoryRoutes = [
':portalSlug/:locale/categories/:categorySlug/articles'
),
name: 'show_category_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListCategoryArticles,
},
{
path: getPortalRoute(':portalSlug/:locale/categories/:categorySlug'),
name: 'edit_category',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: EditCategory,
},
];
@@ -35,7 +35,7 @@ const locales = computed(() => {
});
const allowedLocales = computed(() => {
return Object.keys(locales.value).map(key => {
return this.locales.value[key].code;
return locales.value[key].code;
});
});
@@ -72,7 +72,7 @@ async function updatePortalLocales({
function changeDefaultLocale({ localeCode }) {
updatePortalLocales({
allowedLocales: allowedLocales.value,
newAllowedLocales: allowedLocales.value,
defaultLocale: localeCode,
messageKey: 'CHANGE_DEFAULT_LOCALE',
});
@@ -90,7 +90,7 @@ function deletePortalLocale({ localeCode }) {
const defaultLocale = currentPortal.value?.meta.default_locale;
updatePortalLocales({
allowedLocales: updatedLocales,
newAllowedLocales: updatedLocales,
defaultLocale,
messageKey: 'DELETE_LOCALE',
});
@@ -47,6 +47,7 @@
<script>
import { mapGetters } from 'vuex';
import wootConstants from 'dashboard/constants/globals';
import InboxCard from './components/InboxCard.vue';
import InboxListHeader from './components/InboxListHeader.vue';
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
@@ -103,6 +104,13 @@ export default {
return !this.uiFlags.isFetching && !this.notifications.length;
},
},
watch: {
inboxFilters(newVal, oldVal) {
if (newVal !== oldVal) {
this.$store.dispatch('notifications/updateNotificationFilters', newVal);
}
},
},
mounted() {
this.setSavedFilter();
this.fetchNotifications();
@@ -190,6 +198,10 @@ export default {
this.status = status;
this.type = type;
this.sortOrder = sortBy || wootConstants.INBOX_SORT_BY.NEWEST;
this.$store.dispatch(
'notifications/setNotificationFilters',
this.inboxFilters
);
},
},
};
@@ -140,7 +140,7 @@ export default {
this.$store
.dispatch('setActiveChat', { data: selectedConversation })
.then(() => {
bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
this.$emitter.emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
});
},
findConversation() {
@@ -92,10 +92,10 @@ export default {
...mapGetters({ meta: 'notifications/getMeta' }),
},
mounted() {
bus.$on(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
this.$emitter.on(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
},
destroyed() {
bus.$off(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
this.$emitter.off(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
},
methods: {
openSnoozeNotificationModal() {
@@ -12,13 +12,17 @@ export const routes = [
path: '',
name: 'inbox_view',
component: InboxEmptyStateView,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
{
path: ':notification_id',
name: 'inbox_view_conversation',
component: InboxDetailView,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
],
},
@@ -18,7 +18,9 @@ export const routes = [
path: '',
name: 'notifications_index',
component: NotificationsView,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
],
},
@@ -200,7 +200,7 @@ export default {
},
featureInboundEmailEnabled() {
return !!this.features.inbound_emails;
return !!this.features?.inbound_emails;
},
featureCustomReplyDomainEnabled() {
@@ -6,7 +6,9 @@ export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/general'),
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: SettingsContent,
props: {
headerTitle: 'GENERAL_SETTINGS.TITLE',
@@ -18,7 +20,9 @@ export default {
path: '',
name: 'general_settings_index',
component: Index,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},
@@ -8,7 +8,9 @@ export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/agent-bots'),
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: SettingsContent,
props: {
headerTitle: 'AGENT_BOTS.HEADER',
@@ -20,19 +22,25 @@ export default {
path: '',
name: 'agent_bots',
component: Bot,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'csml/new',
name: 'agent_bots_csml_new',
component: CsmlNewBot,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'csml/:botId',
name: 'agent_bots_csml_edit',
component: CsmlEditBot,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},
@@ -69,8 +69,10 @@
<script>
import { required, minLength, email } from 'vuelidate/lib/validators';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
export default {
mixins: [alertMixin],
props: {
onClose: {
type: Function,
@@ -117,9 +119,6 @@ export default {
},
methods: {
showAlert(message) {
bus.$emit('newToastMessage', message);
},
async addAgent() {
try {
await this.$store.dispatch('agents/create', {
@@ -83,6 +83,7 @@ import WootSubmitButton from '../../../../components/buttons/FormSubmitButton.vu
import Modal from '../../../../components/Modal.vue';
import Auth from '../../../../api/auth';
import wootConstants from 'dashboard/constants/globals';
import alertMixin from 'shared/mixins/alertMixin';
const { AVAILABILITY_STATUS_KEYS } = wootConstants;
@@ -91,6 +92,7 @@ export default {
WootSubmitButton,
Modal,
},
mixins: [alertMixin],
props: {
id: {
type: Number,
@@ -169,9 +171,6 @@ export default {
},
},
methods: {
showAlert(message) {
bus.$emit('newToastMessage', message);
},
async editAgent() {
try {
await this.$store.dispatch('agents/update', {
@@ -130,6 +130,7 @@ import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import Thumbnail from '../../../../components/widgets/Thumbnail.vue';
import AddAgent from './AddAgent.vue';
import EditAgent from './EditAgent.vue';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
@@ -137,7 +138,7 @@ export default {
EditAgent,
Thumbnail,
},
mixins: [globalConfigMixin],
mixins: [globalConfigMixin, alertMixin],
data() {
return {
loading: {},
@@ -230,19 +231,19 @@ export default {
async deleteAgent(id) {
try {
await this.$store.dispatch('agents/delete', id);
this.showAlert(this.$t('AGENT_MGMT.DELETE.API.SUCCESS_MESSAGE'));
this.showAlertMessage(this.$t('AGENT_MGMT.DELETE.API.SUCCESS_MESSAGE'));
} catch (error) {
this.showAlert(this.$t('AGENT_MGMT.DELETE.API.ERROR_MESSAGE'));
this.showAlertMessage(this.$t('AGENT_MGMT.DELETE.API.ERROR_MESSAGE'));
}
},
// Show SnackBar
showAlert(message) {
showAlertMessage(message) {
// Reset loading, current selected agent
this.loading[this.currentAgent.id] = false;
this.currentAgent = {};
// Show message
this.agentAPI.message = message;
bus.$emit('newToastMessage', message);
this.showAlert(message);
},
},
};
@@ -15,14 +15,15 @@ export default {
children: [
{
path: '',
name: 'agents_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'agent_list',
component: AgentHome,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},
@@ -15,14 +15,15 @@ export default {
children: [
{
path: '',
name: 'attributes_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'attributes_list',
component: AttributesHome,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},
@@ -16,13 +16,14 @@ export default {
children: [
{
path: '',
name: 'auditlogs_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'auditlogs_list',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: AuditLogsHome,
},
],
@@ -15,14 +15,15 @@ export default {
children: [
{
path: '',
name: 'automation_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'automation_list',
component: Automation,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},
@@ -6,7 +6,9 @@ export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/billing'),
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: SettingsContent,
props: {
headerTitle: 'BILLING_SETTINGS.TITLE',
@@ -18,7 +20,9 @@ export default {
path: '',
name: 'billing_settings_index',
component: Index,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},
@@ -19,7 +19,9 @@ export default {
{
path: 'ongoing',
name: 'ongoing_campaigns',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: Index,
},
],
@@ -35,7 +37,9 @@ export default {
{
path: 'one_off',
name: 'one_off',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: Index,
},
],
@@ -132,12 +132,14 @@
import { mapGetters } from 'vuex';
import AddCanned from './AddCanned.vue';
import EditCanned from './EditCanned.vue';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
AddCanned,
EditCanned,
},
mixins: [alertMixin],
data() {
return {
loading: {},
@@ -187,13 +189,13 @@ export default {
});
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
},
showAlert(message) {
showAlertMessage(message) {
// Reset loading, current selected agent
this.loading[this.selectedResponse.id] = false;
this.selectedResponse = {};
// Show message
this.cannedResponseAPI.message = message;
bus.$emit('newToastMessage', message);
this.showAlert(message);
},
// Edit Function
openAddPopup() {
@@ -230,12 +232,14 @@ export default {
this.$store
.dispatch('deleteCannedResponse', id)
.then(() => {
this.showAlert(this.$t('CANNED_MGMT.DELETE.API.SUCCESS_MESSAGE'));
this.showAlertMessage(
this.$t('CANNED_MGMT.DELETE.API.SUCCESS_MESSAGE')
);
})
.catch(error => {
const errorMessage =
error?.message || this.$t('CANNED_MGMT.DELETE.API.ERROR_MESSAGE');
this.showAlert(errorMessage);
this.showAlertMessage(errorMessage);
});
},
},
@@ -16,13 +16,14 @@ export default {
children: [
{
path: '',
name: 'canned_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'canned_list',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: CannedHome,
},
],
@@ -51,11 +51,13 @@ import { mapGetters } from 'vuex';
import InboxMembersAPI from '../../../../api/inboxMembers';
import router from '../../../index';
import PageHeader from '../SettingsSubPageHeader.vue';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
PageHeader,
},
mixins: [alertMixin],
validations: {
selectedAgents: {
@@ -98,7 +100,7 @@ export default {
},
});
} catch (error) {
bus.$emit('newToastMessage', error.message);
this.showAlert(error.message);
}
this.isCreating = false;
},
@@ -136,12 +136,13 @@ import Settings from './Settings.vue';
import adminMixin from '../../../../mixins/isAdmin';
import accountMixin from '../../../../mixins/account';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
Settings,
},
mixins: [adminMixin, accountMixin, globalConfigMixin],
mixins: [adminMixin, accountMixin, globalConfigMixin, alertMixin],
data() {
return {
loading: {},
@@ -194,15 +195,9 @@ export default {
async deleteInbox({ id }) {
try {
await this.$store.dispatch('inboxes/delete', id);
bus.$emit(
'newToastMessage',
this.$t('INBOX_MGMT.DELETE.API.SUCCESS_MESSAGE')
);
this.showAlert(this.$t('INBOX_MGMT.DELETE.API.SUCCESS_MESSAGE'));
} catch (error) {
bus.$emit(
'newToastMessage',
this.$t('INBOX_MGMT.DELETE.API.ERROR_MESSAGE')
);
this.showAlert(this.$t('INBOX_MGMT.DELETE.API.ERROR_MESSAGE'));
}
},
@@ -21,10 +21,8 @@
</woot-tabs>
</setting-intro-banner>
<inbox-reconnection-required
v-if="isReconnectionRequired"
class="mx-8 mt-5"
/>
<microsoft-reauthorize v-if="microsoftUnauthorized" :inbox="inbox" />
<facebook-reauthorize v-if="facebookUnauthorized" :inbox="inbox" />
<div v-if="selectedTabKey === 'inbox_settings'" class="mx-8">
<settings-section
@@ -402,7 +400,6 @@
@click="updateInbox"
/>
</settings-section>
<facebook-reauthorize v-if="isAFacebookInbox" :inbox-id="inbox.id" />
</div>
<div v-if="selectedTabKey === 'collaborators'" class="mx-8">
@@ -440,7 +437,7 @@ import WeeklyAvailability from './components/WeeklyAvailability.vue';
import GreetingsEditor from 'shared/components/GreetingsEditor.vue';
import ConfigurationPage from './settingsPage/ConfigurationPage.vue';
import CollaboratorsPage from './settingsPage/CollaboratorsPage.vue';
import InboxReconnectionRequired from './components/InboxReconnectionRequired';
import MicrosoftReauthorize from './channels/microsoft/Reauthorize.vue';
import WidgetBuilder from './WidgetBuilder.vue';
import BotConfiguration from './components/BotConfiguration.vue';
import { FEATURE_FLAGS } from '../../../../featureFlags';
@@ -459,7 +456,7 @@ export default {
WeeklyAvailability,
WidgetBuilder,
SenderNameExamplePreview,
InboxReconnectionRequired,
MicrosoftReauthorize,
},
mixins: [alertMixin, configMixin, inboxMixin],
data() {
@@ -544,7 +541,8 @@ export default {
this.isALineChannel ||
this.isAPIInbox ||
(this.isAnEmailChannel && !this.inbox.provider) ||
(this.isAnEmailChannel && this.inbox.provider === 'microsoft') ||
this.isAMicrosoftInbox ||
this.isAGoogleInbox ||
this.isAWhatsAppChannel ||
this.isAWebWidgetInbox
) {
@@ -621,8 +619,11 @@ export default {
return true;
return false;
},
isReconnectionRequired() {
return false;
microsoftUnauthorized() {
return this.isAMicrosoftInbox && this.inbox.reauthorization_required;
},
facebookUnauthorized() {
return this.isAFacebookInbox && this.inbox.reauthorization_required;
},
},
watch: {
@@ -8,7 +8,7 @@
:header-title="$t('INBOX_MGMT.ADD.EMAIL_PROVIDER.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.EMAIL_PROVIDER.DESCRIPTION')"
/>
<div class="grid grid-cols-4 max-w-3xl mx-0 mt-6">
<div class="grid max-w-3xl grid-cols-4 mx-0 mt-6">
<channel-selector
v-for="emailProvider in emailProviderList"
:key="emailProvider.key"
@@ -20,58 +20,59 @@
</div>
</div>
<microsoft v-else-if="provider === 'microsoft'" />
<google v-else-if="provider === 'google'" />
<forward-to-option v-else-if="provider === 'other_provider'" />
</template>
<script>
<script setup>
import { ref, computed } from 'vue';
import ForwardToOption from './emailChannels/ForwardToOption.vue';
import Microsoft from './emailChannels/Microsoft.vue';
import { mapGetters } from 'vuex';
import Google from './emailChannels/Google.vue';
import ChannelSelector from 'dashboard/components/ChannelSelector.vue';
import PageHeader from '../../SettingsSubPageHeader.vue';
export default {
components: {
ChannelSelector,
ForwardToOption,
Microsoft,
PageHeader,
},
data() {
return { provider: '' };
},
computed: {
...mapGetters({
globalConfig: 'globalConfig/get',
isAChatwootInstance: 'globalConfig/isAChatwootInstance',
}),
emailProviderList() {
return [
{
title: this.$t('INBOX_MGMT.EMAIL_PROVIDERS.MICROSOFT'),
isEnabled: !!this.globalConfig.azureAppId,
key: 'microsoft',
src: '/assets/images/dashboard/channels/microsoft.png',
},
{
title: this.$t('INBOX_MGMT.EMAIL_PROVIDERS.OTHER_PROVIDERS'),
isEnabled: true,
key: 'other_provider',
src: '/assets/images/dashboard/channels/email.png',
},
].filter(provider => {
if (this.isAChatwootInstance) {
return true;
}
return provider.isEnabled;
});
import { useStoreGetters } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
const provider = ref('');
const getters = useStoreGetters();
const { t } = useI18n();
const globalConfig = getters['globalConfig/get'];
const isAChatwootInstance = getters['globalConfig/isAChatwootInstance'];
const emailProviderList = computed(() => {
return [
{
title: t('INBOX_MGMT.EMAIL_PROVIDERS.MICROSOFT'),
isEnabled: !!globalConfig.value.azureAppId,
key: 'microsoft',
src: '/assets/images/dashboard/channels/microsoft.png',
},
},
methods: {
onClick(emailProvider) {
if (emailProvider.isEnabled) {
this.provider = emailProvider.key;
}
{
title: t('INBOX_MGMT.EMAIL_PROVIDERS.GOOGLE'),
isEnabled: !!window.chatwootConfig.googleOAuthClientId,
key: 'google',
src: '/assets/images/dashboard/channels/google.png',
},
},
};
{
title: t('INBOX_MGMT.EMAIL_PROVIDERS.OTHER_PROVIDERS'),
isEnabled: true,
key: 'other_provider',
src: '/assets/images/dashboard/channels/email.png',
},
].filter(providerConfig => {
if (isAChatwootInstance.value) {
return true;
}
return providerConfig.isEnabled;
});
});
function onClick(emailProvider) {
if (emailProvider.isEnabled) {
this.provider = emailProvider.key;
}
}
</script>
@@ -0,0 +1,18 @@
<script setup>
import OAuthChannel from './OAuthChannel.vue';
import { defineComponent } from 'vue';
defineComponent({
name: 'GoogleOAuthChannel',
});
</script>
<template>
<OAuthChannel
provider="google"
:title="$t('INBOX_MGMT.ADD.GOOGLE.TITLE')"
:description="$t('INBOX_MGMT.ADD.GOOGLE.DESCRIPTION')"
:input-placeholder="$t('INBOX_MGMT.ADD.GOOGLE.EMAIL_PLACEHOLDER')"
:submit-button-text="$t('INBOX_MGMT.ADD.GOOGLE.SIGN_IN')"
:error-message="$t('INBOX_MGMT.ADD.GOOGLE.ERROR_MESSAGE')"
/>
</template>
@@ -1,71 +1,19 @@
<template>
<div
class="border border-slate-25 dark:border-slate-800/60 bg-white dark:bg-slate-900 h-full p-6 w-full max-w-full md:w-3/4 md:max-w-[75%] flex-shrink-0 flex-grow-0"
>
<settings-sub-page-header
:header-title="$t('INBOX_MGMT.ADD.MICROSOFT.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.MICROSOFT.DESCRIPTION')"
/>
<form
class="microsoft--sign-in-form"
@submit.prevent="requestAuthorization"
>
<woot-input
v-model.trim="email"
type="text"
:placeholder="$t('INBOX_MGMT.ADD.MICROSOFT.EMAIL_PLACEHOLDER')"
@blur="$v.email.$touch"
/>
<woot-submit-button
icon="brand-twitter"
button-text="Sign in with Microsoft"
type="submit"
:loading="isRequestingAuthorization"
/>
</form>
</div>
</template>
<script>
import alertMixin from 'shared/mixins/alertMixin';
import microsoftClient from 'dashboard/api/channel/microsoftClient';
import SettingsSubPageHeader from '../../../SettingsSubPageHeader.vue';
import { required, email } from 'vuelidate/lib/validators';
<script setup>
import OAuthChannel from './OAuthChannel.vue';
import { defineComponent } from 'vue';
export default {
components: { SettingsSubPageHeader },
mixins: [alertMixin],
data() {
return { email: '', isRequestingAuthorization: false };
},
validations: {
email: { required, email },
},
methods: {
async requestAuthorization() {
try {
this.$v.$touch();
if (this.$v.$invalid) return;
this.isRequestingAuthorization = true;
const response = await microsoftClient.generateAuthorization({
email: this.email,
});
const {
data: { url },
} = response;
window.location.href = url;
} catch (error) {
this.showAlert(this.$t('INBOX_MGMT.ADD.MICROSOFT.ERROR_MESSAGE'));
} finally {
this.isRequestingAuthorization = false;
}
},
},
};
defineComponent({
name: 'MicrosoftOAuthChannel',
});
</script>
<style scoped>
.microsoft--sign-in-form {
@apply mt-6;
}
</style>
<template>
<OAuthChannel
provider="microsoft"
:title="$t('INBOX_MGMT.ADD.MICROSOFT.TITLE')"
:description="$t('INBOX_MGMT.ADD.MICROSOFT.DESCRIPTION')"
:input-placeholder="$t('INBOX_MGMT.ADD.MICROSOFT.EMAIL_PLACEHOLDER')"
:submit-button-text="$t('INBOX_MGMT.ADD.MICROSOFT.SIGN_IN')"
:error-message="$t('INBOX_MGMT.ADD.MICROSOFT.ERROR_MESSAGE')"
/>
</template>
@@ -0,0 +1,89 @@
<script setup>
import { ref, computed } from 'vue';
import microsoftClient from 'dashboard/api/channel/microsoftClient';
import googleClient from 'dashboard/api/channel/googleClient';
import SettingsSubPageHeader from '../../../SettingsSubPageHeader.vue';
import { useAlert } from 'dashboard/composables';
const props = defineProps({
provider: {
type: String,
required: true,
validate: value => ['microsoft', 'google'].includes(value),
},
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
submitButtonText: {
type: String,
required: true,
},
errorMessage: {
type: String,
required: true,
},
inputPlaceholder: {
type: String,
required: true,
},
});
const isRequestingAuthorization = ref(false);
const email = ref('');
const client = computed(() => {
if (props.provider === 'microsoft') {
return microsoftClient;
}
return googleClient;
});
async function requestAuthorization() {
try {
isRequestingAuthorization.value = true;
const response = await client.value.generateAuthorization({
email: email.value,
});
const {
data: { url },
} = response;
window.location.href = url;
} catch (error) {
useAlert(props.errorMessage);
} finally {
isRequestingAuthorization.value = false;
}
}
</script>
<template>
<div
class="border border-slate-25 dark:border-slate-800/60 bg-white dark:bg-slate-900 h-full p-6 w-full max-w-full md:w-3/4 md:max-w-[75%] flex-shrink-0 flex-grow-0"
>
<settings-sub-page-header
:header-title="title"
:header-content="description"
/>
<form class="mt-6" @submit.prevent="requestAuthorization">
<woot-input
v-model="email"
type="email"
:placeholder="inputPlaceholder"
/>
<woot-submit-button
icon="brand-twitter"
type="submit"
:button-text="submitButtonText"
:loading="isRequestingAuthorization"
/>
</form>
</div>
</template>
@@ -1,63 +1,44 @@
<template>
<div class="mx-8">
<settings-section
:title="$t('INBOX_MGMT.MICROSOFT.TITLE')"
:sub-title="$t('INBOX_MGMT.MICROSOFT.SUBTITLE')"
>
<div class="mb-6">
<form @submit.prevent="requestAuthorization">
<woot-submit-button
icon="brand-twitter"
button-text="Sign in with Microsoft"
type="submit"
:loading="isRequestingAuthorization"
/>
</form>
</div>
</settings-section>
</div>
</template>
<script>
import alertMixin from 'shared/mixins/alertMixin';
import microsoftClient from '../../../../../../api/channel/microsoftClient';
import SettingsSection from '../../../../../../components/SettingsSection.vue';
<script setup>
import { ref } from 'vue';
import InboxReconnectionRequired from '../../components/InboxReconnectionRequired';
import microsoftClient from 'dashboard/api/channel/microsoftClient';
export default {
components: {
SettingsSection,
import { useI18n } from 'dashboard/composables/useI18n';
import { useAlert } from 'dashboard/composables';
const { t } = useI18n();
const props = defineProps({
inbox: {
type: Object,
default: () => ({}),
},
mixins: [alertMixin],
props: {
inbox: {
type: Object,
default: () => ({}),
},
},
data() {
return { isRequestingAuthorization: false };
},
methods: {
async requestAuthorization() {
try {
this.isRequestingAuthorization = true;
const response = await microsoftClient.generateAuthorization({
email: this.inbox.email,
});
const {
data: { url },
} = response;
window.location.href = url;
} catch (error) {
this.showAlert(this.$t('INBOX_MGMT.ADD.MICROSOFT.ERROR_MESSAGE'));
} finally {
this.isRequestingAuthorization = false;
}
},
},
};
</script>
<style lang="scss" scoped>
.smtp-details-wrap {
margin-bottom: var(--space-medium);
});
const isRequestingAuthorization = ref(false);
async function requestAuthorization() {
try {
isRequestingAuthorization.value = true;
const response = await microsoftClient.generateAuthorization({
email: props.inbox.email,
});
const {
data: { url },
} = response;
window.location.href = url;
} catch (error) {
useAlert(t('INBOX_MGMT.ADD.MICROSOFT.ERROR_MESSAGE'));
} finally {
isRequestingAuthorization.value = false;
}
}
</style>
</script>
<template>
<inbox-reconnection-required
class="mx-8 mt-5"
@reauthorize="requestAuthorization"
/>
</template>
@@ -1,19 +1,14 @@
<script setup>
defineProps({
actionUrl: {
type: String,
required: true,
},
});
import Banner from 'dashboard/components/ui/Banner.vue';
</script>
<template>
<div
class="flex items-center gap-2 px-4 py-3 text-sm text-white bg-red-500 rounded-md dark:bg-red-800/30 dark:text-red-50 min-h-10"
>
<fluent-icon icon="error-circle" class="text-white dark:text-red-50" />
<slot>
<span v-html="$t('INBOX_MGMT.RECONNECTION_REQUIRED', { actionUrl })" />
</slot>
</div>
<banner
color-scheme="alert"
class="justify-start rounded-md"
:banner-message="$t('INBOX_MGMT.RECONNECTION_REQUIRED')"
:action-button-label="$t('INBOX_MGMT.CLICK_TO_RECONNECT')"
has-action-button
@click="$emit('reauthorize')"
/>
</template>
@@ -1,38 +1,32 @@
<template>
<settings-section
:title="$t('INBOX_MGMT.FACEBOOK_REAUTHORIZE.TITLE')"
:sub-title="$t('INBOX_MGMT.FACEBOOK_REAUTHORIZE.SUBTITLE')"
>
<a class="fb--login" href="#" @click="tryFBlogin">
<img
src="~dashboard/assets/images/channels/facebook_login.png"
alt="Facebook-logo"
/>
</a>
</settings-section>
<inbox-reconnection-required class="mx-8 mt-5" @reauthorize="tryFBlogin" />
</template>
<script>
/* global FB */
import SettingsSection from '../../../../../components/SettingsSection.vue';
import InboxReconnectionRequired from '../components/InboxReconnectionRequired';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
SettingsSection,
InboxReconnectionRequired,
},
mixins: [alertMixin],
props: {
inboxId: {
type: Number,
inbox: {
type: Object,
required: true,
},
},
computed: {
inboxId() {
return this.inbox.id;
},
},
mounted() {
this.initFB();
this.loadFBsdk();
},
methods: {
initFB() {
if (window.fbSDKLoaded === undefined) {
@@ -28,14 +28,15 @@ export default {
children: [
{
path: '',
name: 'settings_inbox',
redirect: 'list',
},
{
path: 'list',
name: 'settings_inbox_list',
component: InboxHome,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'new',
@@ -45,19 +46,25 @@ export default {
path: '',
name: 'settings_inbox_new',
component: ChannelList,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':inbox_id/finish',
name: 'settings_inbox_finish',
component: FinishSetup,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':sub_page',
name: 'settings_inboxes_page_channel',
component: channelFactory.create(),
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
props: route => {
return { channel_name: route.params.sub_page };
},
@@ -65,7 +72,9 @@ export default {
{
path: ':inbox_id/agents',
name: 'settings_inboxes_add_agents',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: AddAgents,
},
],
@@ -74,7 +83,9 @@ export default {
path: ':inboxId',
name: 'settings_inbox_show',
component: Settings,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},
@@ -104,10 +104,6 @@
</div>
<imap-settings :inbox="inbox" />
<smtp-settings v-if="inbox.imap_enabled" :inbox="inbox" />
<microsoft-reauthorize
v-if="inbox.microsoft_reauthorization"
:inbox="inbox"
/>
</div>
<div v-else-if="isAWhatsAppChannel && !isATwilioChannel">
<div v-if="inbox.provider_config" class="mx-8">
@@ -130,7 +126,7 @@
"
>
<div
class="whatsapp-settings--content items-center flex flex-1 justify-between mt-2"
class="flex items-center justify-between flex-1 mt-2 whatsapp-settings--content"
>
<woot-input
v-model.trim="whatsAppInboxAPIKey"
@@ -160,7 +156,6 @@ import inboxMixin from 'shared/mixins/inboxMixin';
import SettingsSection from '../../../../../components/SettingsSection.vue';
import ImapSettings from '../ImapSettings.vue';
import SmtpSettings from '../SmtpSettings.vue';
import MicrosoftReauthorize from '../channels/microsoft/Reauthorize.vue';
import { required } from 'vuelidate/lib/validators';
export default {
@@ -168,7 +163,6 @@ export default {
SettingsSection,
ImapSettings,
SmtpSettings,
MicrosoftReauthorize,
},
mixins: [inboxMixin, alertMixin],
props: {
@@ -26,13 +26,17 @@ export default {
path: '',
name: 'settings_applications',
component: Index,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':integration_id',
name: 'settings_applications_integration',
component: IntegrationHooks,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
props: route => ({
integrationId: route.params.integration_id,
}),
@@ -30,32 +30,42 @@ export default {
path: '',
name: 'settings_integrations',
component: Index,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'webhook',
component: Webhook,
name: 'settings_integrations_webhook',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'dashboard-apps',
component: DashboardApps,
name: 'settings_integrations_dashboard_apps',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'slack',
name: 'settings_integrations_slack',
component: Slack,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
props: route => ({ code: route.query.code }),
},
{
path: ':integration_id',
name: 'settings_integrations_integration',
component: ShowIntegration,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
props: route => {
return {
integrationId: route.params.integration_id,
@@ -17,13 +17,17 @@ export default {
{
path: '',
name: 'labels_wrapper',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
redirect: 'list',
},
{
path: 'list',
name: 'labels_list',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: Index,
},
],
@@ -23,19 +23,25 @@ export default {
path: '',
name: 'macros_wrapper',
component: Macros,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
{
path: 'new',
name: 'macros_new',
component: MacroEditor,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
{
path: ':macroId/edit',
name: 'macros_edit',
component: MacroEditor,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
],
},
@@ -8,14 +8,18 @@ export default {
{
path: frontendURL('accounts/:accountId/profile'),
name: 'profile_settings',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: SettingsContent,
children: [
{
path: 'settings',
name: 'profile_settings_index',
component: Index,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
],
},
@@ -144,7 +144,7 @@ export default {
this.$store.dispatch('teams/get');
this.fetchAllData();
bus.$on('fetch_overview_reports', () => {
this.$emitter.on('fetch_overview_reports', () => {
this.fetchAllData();
});
},
@@ -125,6 +125,9 @@ export default {
{}
);
},
onPageNumberChange(pageIndex) {
this.$emit('page-change', pageIndex);
},
},
};
</script>
@@ -29,7 +29,9 @@ export default {
{
path: 'overview',
name: 'account_overview_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: LiveReports,
},
],
@@ -46,7 +48,9 @@ export default {
{
path: 'conversation',
name: 'conversation_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: Index,
},
],
@@ -63,7 +67,9 @@ export default {
{
path: 'csat',
name: 'csat_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: CsatResponses,
},
],
@@ -80,7 +86,9 @@ export default {
{
path: 'bot',
name: 'bot_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: BotReports,
},
],
@@ -97,7 +105,9 @@ export default {
{
path: 'agent',
name: 'agent_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: AgentReports,
},
],
@@ -114,7 +124,9 @@ export default {
{
path: 'label',
name: 'label_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: LabelReports,
},
],
@@ -131,7 +143,9 @@ export default {
{
path: 'inboxes',
name: 'inbox_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: InboxReports,
},
],
@@ -147,7 +161,9 @@ export default {
{
path: 'teams',
name: 'team_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: TeamReports,
},
],
@@ -164,7 +180,9 @@ export default {
{
path: 'sla',
name: 'sla_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: SLAReports,
},
],
@@ -24,7 +24,9 @@ export default {
{
path: frontendURL('accounts/:accountId/settings'),
name: 'settings_home',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
redirect: () => {
if (store.getters.getCurrentRole === 'administrator') {
return frontendURL('accounts/:accountId/settings/general');
@@ -13,13 +13,17 @@ export default {
{
path: '',
name: 'sla_wrapper',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
redirect: 'list',
},
{
path: 'list',
name: 'sla_list',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: Index,
},
],
@@ -29,14 +29,15 @@ export default {
children: [
{
path: '',
name: 'settings_teams',
redirect: 'list',
},
{
path: 'list',
name: 'settings_teams_list',
component: TeamsHome,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'new',
@@ -46,18 +47,24 @@ export default {
path: '',
name: 'settings_teams_new',
component: CreateTeam,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':teamId/finish',
name: 'settings_teams_finish',
component: FinishSetup,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':teamId/agents',
name: 'settings_teams_add_agents',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: AddAgents,
},
],
@@ -70,18 +77,24 @@ export default {
path: '',
name: 'settings_teams_edit',
component: EditTeam,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'agents',
name: 'settings_teams_edit_members',
component: EditAgents,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'finish',
name: 'settings_teams_edit_finish',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: FinishSetup,
},
],
@@ -1,24 +1,30 @@
<script setup>
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
import { onMounted } from 'vue';
const toggleSupportWidgetVisibility = () => {
if (window.$chatwoot) {
window.$chatwoot.toggleBubbleVisibility('show');
}
};
const setupListenerForWidgetEvent = () => {
window.addEventListener('chatwoot:on-message', () => {
toggleSupportWidgetVisibility();
});
};
onMounted(() => {
toggleSupportWidgetVisibility();
setupListenerForWidgetEvent();
});
</script>
<template>
<div class="suspended-page">
<div class="items-center bg-slate-50 flex justify-center h-full w-full">
<empty-state
:title="$t('APP_GLOBAL.ACCOUNT_SUSPENDED.TITLE')"
:message="$t('APP_GLOBAL.ACCOUNT_SUSPENDED.MESSAGE')"
/>
</div>
</template>
<script>
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
export default {
components: { EmptyState },
};
</script>
<style scoped>
.suspended-page {
align-items: center;
background: var(--s-50);
display: flex;
justify-content: center;
height: 100%;
width: 100%;
}
</style>
+3 -28
View File
@@ -5,33 +5,12 @@ import dashboard from './dashboard/dashboard.routes';
import store from '../store';
import { validateLoggedInRoutes } from '../helper/routeHelpers';
import AnalyticsHelper from '../helper/AnalyticsHelper';
import { buildPermissionsFromRouter } from '../helper/permissionsHelper';
const routes = [...dashboard.routes];
window.roleWiseRoutes = {
agent: [],
administrator: [],
};
// generateRoleWiseRoute - updates window object with agent/admin route
const generateRoleWiseRoute = route => {
route.forEach(element => {
if (element.children) {
generateRoleWiseRoute(element.children);
}
if (element.roles) {
element.roles.forEach(roleEl => {
window.roleWiseRoutes[roleEl].push(element.name);
});
}
});
};
// Create a object of routes
// accessible by each role.
// returns an object with roles as keys and routeArr as values
generateRoleWiseRoute(routes);
export const router = new VueRouter({ mode: 'history', routes });
export const routesWithPermissions = buildPermissionsFromRouter(routes);
export const validateAuthenticateRoutePermission = (to, next, { getters }) => {
const { isLoggedIn, getCurrentUser: user } = getters;
@@ -45,11 +24,7 @@ export const validateAuthenticateRoutePermission = (to, next, { getters }) => {
return next(frontendURL(`accounts/${user.account_id}/dashboard`));
}
const nextRoute = validateLoggedInRoutes(
to,
getters.getCurrentUser,
window.roleWiseRoutes
);
const nextRoute = validateLoggedInRoutes(to, getters.getCurrentUser);
return nextRoute ? next(frontendURL(nextRoute)) : next();
};