Merge branch 'develop' into feat/captain-inbox-selector
This commit is contained in:
@@ -31,26 +31,29 @@ export default {
|
||||
currentAccountId: 'getCurrentAccountId',
|
||||
currentUserAutoOffline: 'getCurrentUserAutoOffline',
|
||||
}),
|
||||
statusList() {
|
||||
return [
|
||||
this.$t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUS.ONLINE'),
|
||||
this.$t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUS.BUSY'),
|
||||
this.$t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUS.OFFLINE'),
|
||||
];
|
||||
},
|
||||
availabilityDisplayLabel() {
|
||||
const availabilityIndex = AVAILABILITY_STATUS_KEYS.findIndex(
|
||||
key => key === this.currentUserAvailability
|
||||
);
|
||||
return this.$t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUSES_LIST')[
|
||||
availabilityIndex
|
||||
];
|
||||
return this.statusList[availabilityIndex];
|
||||
},
|
||||
currentUserAvailability() {
|
||||
return this.getCurrentUserAvailability;
|
||||
},
|
||||
availabilityStatuses() {
|
||||
return this.$t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUSES_LIST').map(
|
||||
(statusLabel, index) => ({
|
||||
label: statusLabel,
|
||||
value: AVAILABILITY_STATUS_KEYS[index],
|
||||
disabled:
|
||||
this.currentUserAvailability === AVAILABILITY_STATUS_KEYS[index],
|
||||
})
|
||||
);
|
||||
return this.statusList.map((statusLabel, index) => ({
|
||||
label: statusLabel,
|
||||
value: AVAILABILITY_STATUS_KEYS[index],
|
||||
disabled:
|
||||
this.currentUserAvailability === AVAILABILITY_STATUS_KEYS[index],
|
||||
}));
|
||||
},
|
||||
},
|
||||
|
||||
@@ -102,7 +105,7 @@ export default {
|
||||
size="small"
|
||||
:color-scheme="status.disabled ? '' : 'secondary'"
|
||||
:variant="status.disabled ? 'smooth' : 'clear'"
|
||||
class-names="status-change--dropdown-button"
|
||||
class="status-change--dropdown-button"
|
||||
@click="changeAvailabilityStatus(status.value)"
|
||||
>
|
||||
<AvailabilityStatusBadge :status="status.value" />
|
||||
@@ -129,7 +132,7 @@ export default {
|
||||
<woot-switch
|
||||
size="small"
|
||||
class="mx-1 mt-px mb-0"
|
||||
:value="currentUserAutoOffline"
|
||||
:model-value="currentUserAutoOffline"
|
||||
@input="updateAutoOffline"
|
||||
/>
|
||||
</WootDropdownItem>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { getSidebarItems } from './config/default-sidebar';
|
||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||
import { useRoute, useRouter } from 'dashboard/composables/route';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import PrimarySidebar from './sidebarComponents/Primary.vue';
|
||||
import SecondarySidebar from './sidebarComponents/Secondary.vue';
|
||||
import { routesWithPermissions } from '../../routes';
|
||||
import { hasPermissions } from '../../helper/permissionsHelper';
|
||||
import {
|
||||
getUserPermissions,
|
||||
hasPermissions,
|
||||
} from '../../helper/permissionsHelper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -20,15 +23,18 @@ export default {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
sidebarClassName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'toggleAccountModal',
|
||||
'showAddLabelPopup',
|
||||
'openNotificationPanel',
|
||||
'closeKeyShortcutModal',
|
||||
'openKeyShortcutModal',
|
||||
],
|
||||
setup(props, { emit }) {
|
||||
const sidebarRef = ref(null);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { accountId } = useAccount();
|
||||
|
||||
const toggleKeyShortcutModal = () => {
|
||||
emit('openKeyShortcutModal');
|
||||
@@ -64,11 +70,11 @@ export default {
|
||||
action: () => navigateToRoute('agent_list'),
|
||||
},
|
||||
};
|
||||
useKeyboardEvents(keyboardEvents, sidebarRef);
|
||||
useKeyboardEvents(keyboardEvents);
|
||||
|
||||
return {
|
||||
toggleKeyShortcutModal,
|
||||
sidebarRef,
|
||||
accountId,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
@@ -79,7 +85,6 @@ export default {
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
currentUser: 'getCurrentUser',
|
||||
globalConfig: 'globalConfig/get',
|
||||
inboxes: 'inboxes/getInboxes',
|
||||
@@ -99,6 +104,9 @@ export default {
|
||||
return '';
|
||||
},
|
||||
customViews() {
|
||||
if (!this.activeCustomView) {
|
||||
return [];
|
||||
}
|
||||
return this.$store.getters['customViews/getCustomViewsByFilterType'](
|
||||
this.activeCustomView
|
||||
);
|
||||
@@ -113,7 +121,10 @@ export default {
|
||||
return getSidebarItems(this.accountId);
|
||||
},
|
||||
primaryMenuItems() {
|
||||
const userPermissions = this.currentUser.permissions;
|
||||
const userPermissions = getUserPermissions(
|
||||
this.currentUser,
|
||||
this.accountId
|
||||
);
|
||||
const menuItems = this.sideMenuConfig.primaryMenu;
|
||||
return menuItems.filter(menuItem => {
|
||||
const isAvailableForTheUser = hasPermissions(
|
||||
@@ -156,6 +167,17 @@ export default {
|
||||
) || {};
|
||||
return activePrimaryMenu;
|
||||
},
|
||||
hasSecondaryMenu() {
|
||||
return (
|
||||
this.activeSecondaryMenu.menuItems &&
|
||||
this.activeSecondaryMenu.menuItems.length
|
||||
);
|
||||
},
|
||||
hasSecondarySidebar() {
|
||||
// if it is explicitly stated to show and it has secondary menu items to show
|
||||
// showSecondarySidebar corresponds to the UI settings, indicating if the user has toggled it
|
||||
return this.showSecondarySidebar && this.hasSecondaryMenu;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
@@ -195,7 +217,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside ref="sidebarRef" class="flex h-full">
|
||||
<aside class="flex h-full">
|
||||
<PrimarySidebar
|
||||
:logo-source="globalConfig.logoThumbnail"
|
||||
:installation-name="globalConfig.installationName"
|
||||
@@ -203,13 +225,12 @@ export default {
|
||||
:account-id="accountId"
|
||||
:menu-items="primaryMenuItems"
|
||||
:active-menu-item="activePrimaryMenu.key"
|
||||
@toggleAccounts="toggleAccountModal"
|
||||
@openKeyShortcutModal="toggleKeyShortcutModal"
|
||||
@openNotificationPanel="openNotificationPanel"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
@open-key-shortcut-modal="toggleKeyShortcutModal"
|
||||
@open-notification-panel="openNotificationPanel"
|
||||
/>
|
||||
<SecondarySidebar
|
||||
v-if="showSecondarySidebar"
|
||||
:class="sidebarClassName"
|
||||
v-if="hasSecondarySidebar"
|
||||
:account-id="accountId"
|
||||
:inboxes="inboxes"
|
||||
:labels="labels"
|
||||
@@ -218,8 +239,8 @@ export default {
|
||||
:menu-config="activeSecondaryMenu"
|
||||
:current-user="currentUser"
|
||||
:is-on-chatwoot-cloud="isOnChatwootCloud"
|
||||
@addLabel="showAddLabelPopup"
|
||||
@toggleAccounts="toggleAccountModal"
|
||||
@add-label="showAddLabelPopup"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
/>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -2,23 +2,23 @@ import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const campaigns = accountId => ({
|
||||
parentNav: 'campaigns',
|
||||
routes: ['ongoing_campaigns', 'one_off'],
|
||||
routes: ['campaigns_sms_index', 'campaigns_livechat_index'],
|
||||
menuItems: [
|
||||
{
|
||||
icon: 'arrow-swap',
|
||||
label: 'ONGOING',
|
||||
label: 'LIVE_CHAT',
|
||||
key: 'ongoingCampaigns',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/campaigns/ongoing`),
|
||||
toStateName: 'ongoing_campaigns',
|
||||
toState: frontendURL(`accounts/${accountId}/campaigns/live_chat`),
|
||||
toStateName: 'campaigns_livechat_index',
|
||||
},
|
||||
{
|
||||
key: 'oneOffCampaigns',
|
||||
icon: 'sound-source',
|
||||
label: 'ONE_OFF',
|
||||
label: 'SMS',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/campaigns/one_off`),
|
||||
toStateName: 'one_off',
|
||||
toState: frontendURL(`accounts/${accountId}/campaigns/sms`),
|
||||
toStateName: 'campaigns_sms_index',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -3,18 +3,20 @@ import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const contacts = accountId => ({
|
||||
parentNav: 'contacts',
|
||||
routes: [
|
||||
'contacts_dashboard',
|
||||
'contact_profile_dashboard',
|
||||
'contacts_segments_dashboard',
|
||||
'contacts_labels_dashboard',
|
||||
'contacts_dashboard_index',
|
||||
'contacts_dashboard_segments_index',
|
||||
'contacts_dashboard_labels_index',
|
||||
'contacts_edit',
|
||||
'contacts_edit_segment',
|
||||
'contacts_edit_label',
|
||||
],
|
||||
menuItems: [
|
||||
{
|
||||
icon: 'contact-card-group',
|
||||
label: 'ALL_CONTACTS',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/contacts`),
|
||||
toStateName: 'contacts_dashboard',
|
||||
toState: frontendURL(`accounts/${accountId}/contacts?page=1`),
|
||||
toStateName: 'contacts_dashboard_index',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ const primaryMenuItems = accountId => [
|
||||
key: 'captain',
|
||||
label: 'CAPTAIN',
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
toState: frontendURL(`accounts/${accountId}/captain`),
|
||||
toState: frontendURL(`accounts/${accountId}/captain/documents`),
|
||||
toStateName: 'captain',
|
||||
},
|
||||
{
|
||||
@@ -31,7 +31,7 @@ const primaryMenuItems = accountId => [
|
||||
label: 'CONTACTS',
|
||||
featureFlag: FEATURE_FLAGS.CRM,
|
||||
toState: frontendURL(`accounts/${accountId}/contacts`),
|
||||
toStateName: 'contacts_dashboard',
|
||||
toStateName: 'contacts_dashboard_index',
|
||||
},
|
||||
{
|
||||
icon: 'arrow-trending-lines',
|
||||
@@ -47,7 +47,7 @@ const primaryMenuItems = accountId => [
|
||||
label: 'CAMPAIGNS',
|
||||
featureFlag: FEATURE_FLAGS.CAMPAIGNS,
|
||||
toState: frontendURL(`accounts/${accountId}/campaigns`),
|
||||
toStateName: 'ongoing_campaigns',
|
||||
toStateName: 'campaigns_ongoing_index',
|
||||
},
|
||||
{
|
||||
icon: 'library',
|
||||
@@ -55,8 +55,8 @@ const primaryMenuItems = accountId => [
|
||||
label: 'HELP_CENTER.TITLE',
|
||||
featureFlag: FEATURE_FLAGS.HELP_CENTER,
|
||||
alwaysVisibleOnChatwootInstances: true,
|
||||
toState: frontendURL(`accounts/${accountId}/portals`),
|
||||
toStateName: 'default_portal_articles',
|
||||
toState: frontendURL(`accounts/${accountId}/portals/portal_articles_index`),
|
||||
toStateName: 'portals_index',
|
||||
},
|
||||
{
|
||||
icon: 'settings',
|
||||
|
||||
@@ -40,6 +40,7 @@ const settings = accountId => ({
|
||||
'settings_teams_list',
|
||||
'settings_teams_new',
|
||||
'sla_list',
|
||||
'custom_roles_list',
|
||||
],
|
||||
menuItems: [
|
||||
{
|
||||
@@ -179,6 +180,18 @@ const settings = accountId => ({
|
||||
isEnterpriseOnly: true,
|
||||
featureFlag: FEATURE_FLAGS.AUDIT_LOGS,
|
||||
},
|
||||
{
|
||||
icon: 'scan-person',
|
||||
label: 'CUSTOM_ROLES',
|
||||
hasSubMenu: false,
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
toState: frontendURL(`accounts/${accountId}/settings/custom-roles/list`),
|
||||
toStateName: 'custom_roles_list',
|
||||
isEnterpriseOnly: true,
|
||||
beta: true,
|
||||
},
|
||||
{
|
||||
icon: 'document-list-clock',
|
||||
label: 'SLA',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
emits: ['toggleAccounts'],
|
||||
data() {
|
||||
return { showSwitchButton: false };
|
||||
},
|
||||
@@ -25,6 +26,7 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
||||
<template>
|
||||
<div
|
||||
v-if="showShowCurrentAccountContext"
|
||||
|
||||
@@ -7,7 +7,7 @@ export default {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['closeAccountModal', 'showCreateAccountModal'],
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
@@ -52,9 +52,13 @@ export default {
|
||||
{{ account.name }}
|
||||
</div>
|
||||
<div
|
||||
class="text-xs font-medium text-slate-500 dark:text-slate-500 hover:underline-offset-4"
|
||||
class="text-xs font-medium lowercase text-slate-500 dark:text-slate-500 hover:underline-offset-4"
|
||||
>
|
||||
{{ account.role }}
|
||||
{{
|
||||
account.custom_role_id
|
||||
? account.custom_role.name
|
||||
: account.role
|
||||
}}
|
||||
</div>
|
||||
</label>
|
||||
</span>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
@@ -15,6 +15,7 @@ export default {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ['closeAccountCreateModal'],
|
||||
setup() {
|
||||
return { v$: useVuelidate() };
|
||||
},
|
||||
@@ -23,11 +24,13 @@ export default {
|
||||
accountName: '',
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
accountName: {
|
||||
required,
|
||||
minLength: minLength(1),
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
accountName: {
|
||||
required,
|
||||
minLength: minLength(1),
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@@ -76,7 +79,7 @@ export default {
|
||||
<label :class="{ error: v$.accountName.$error }">
|
||||
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
|
||||
<input
|
||||
v-model.trim="accountName"
|
||||
v-model="accountName"
|
||||
type="text"
|
||||
:placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')"
|
||||
@input="v$.accountName.$touch"
|
||||
|
||||
@@ -6,6 +6,7 @@ export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
},
|
||||
emits: ['toggleMenu'],
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'getCurrentUser',
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
emits: ['openNotificationPanel'],
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
notificationMetadata: 'notifications/getMeta',
|
||||
|
||||
@@ -17,6 +17,12 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'close',
|
||||
'openKeyShortcutModal',
|
||||
'toggleAccounts',
|
||||
'showSupportChatWindow',
|
||||
],
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'getCurrentUser',
|
||||
|
||||
@@ -7,6 +7,7 @@ import NotificationBell from './NotificationBell.vue';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
import { ACCOUNT_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -42,6 +43,7 @@ export default {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['toggleAccounts', 'openNotificationPanel', 'openKeyShortcutModal'],
|
||||
data() {
|
||||
return {
|
||||
helpDocsURL: wootConstants.DOCS_URL,
|
||||
@@ -60,7 +62,7 @@ export default {
|
||||
window.$chatwoot.toggle();
|
||||
},
|
||||
openNotificationPanel() {
|
||||
this.$track(ACCOUNT_EVENTS.OPENED_NOTIFICATIONS);
|
||||
useTrack(ACCOUNT_EVENTS.OPENED_NOTIFICATIONS);
|
||||
this.$emit('openNotificationPanel');
|
||||
},
|
||||
},
|
||||
@@ -80,6 +82,7 @@ export default {
|
||||
/>
|
||||
<PrimaryNavItem
|
||||
v-for="menuItem in menuItems"
|
||||
:id="menuItem.key"
|
||||
:key="menuItem.toState"
|
||||
:icon="menuItem.icon"
|
||||
:name="menuItem.label"
|
||||
@@ -88,20 +91,24 @@ export default {
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-end pb-6">
|
||||
<PrimaryNavItem
|
||||
<a
|
||||
v-if="!isACustomBrandedInstance"
|
||||
icon="book-open-globe"
|
||||
name="DOCS"
|
||||
open-in-new-page
|
||||
:to="helpDocsURL"
|
||||
/>
|
||||
<NotificationBell @openNotificationPanel="openNotificationPanel" />
|
||||
<AgentDetails @toggleMenu="toggleOptions" />
|
||||
v-tooltip.right="$t(`SIDEBAR.DOCS`)"
|
||||
:href="helpDocsURL"
|
||||
class="relative flex items-center justify-center w-10 h-10 my-2 rounded-lg text-slate-700 dark:text-slate-100 hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600"
|
||||
rel="noopener noreferrer nofollow"
|
||||
target="_blank"
|
||||
>
|
||||
<fluent-icon icon="book-open-globe" />
|
||||
<span class="sr-only">{{ $t(`SIDEBAR.DOCS`) }}</span>
|
||||
</a>
|
||||
<NotificationBell @open-notification-panel="openNotificationPanel" />
|
||||
<AgentDetails @toggle-menu="toggleOptions" />
|
||||
<OptionsMenu
|
||||
:show="showOptionsMenu"
|
||||
@toggleAccounts="toggleAccountModal"
|
||||
@showSupportChatWindow="toggleSupportChatWindow"
|
||||
@openKeyShortcutModal="$emit('openKeyShortcutModal')"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
@show-support-chat-window="toggleSupportChatWindow"
|
||||
@open-key-shortcut-modal="$emit('openKeyShortcutModal')"
|
||||
@close="toggleOptions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
<script>
|
||||
import { OnClickOutside } from '@vueuse/components';
|
||||
import { HELP_CENTER_MENU_ITEMS } from 'dashboard/helper/portalHelper';
|
||||
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DropdownMenu,
|
||||
OnClickOutside,
|
||||
},
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -26,15 +39,89 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
helpCenterMenu: HELP_CENTER_MENU_ITEMS,
|
||||
showHelpCenterMenu: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
helpCenterMenuItems() {
|
||||
return this.helpCenterMenu.map(item => ({
|
||||
...item,
|
||||
isSelected: this.isSelectedMenuItem(item),
|
||||
}));
|
||||
},
|
||||
isHelpCenter() {
|
||||
return this.id === 'helpcenter';
|
||||
},
|
||||
isHelpCenterSelected() {
|
||||
const routes = [
|
||||
'portals_new',
|
||||
'portals_index',
|
||||
'portals_articles_index',
|
||||
'portals_articles_new',
|
||||
'portals_articles_edit',
|
||||
'portals_categories_index',
|
||||
'portals_categories_articles_index',
|
||||
'portals_categories_articles_edit',
|
||||
'portals_locales_index',
|
||||
'portals_settings_index',
|
||||
];
|
||||
return routes.includes(this.$route.name);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isSelectedMenuItem(menuItem) {
|
||||
return menuItem.value.includes(this.$route.name);
|
||||
},
|
||||
toggleHelpCenterMenu() {
|
||||
this.showHelpCenterMenu = !this.showHelpCenterMenu;
|
||||
},
|
||||
handleHelpCenterAction({ action }) {
|
||||
this.$router.push({
|
||||
name: 'portals_index',
|
||||
params: {
|
||||
navigationPath: action,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-link v-slot="{ href, isActive, navigate }" :to="to" custom>
|
||||
<OnClickOutside v-if="isHelpCenter" @trigger="showHelpCenterMenu = false">
|
||||
<button
|
||||
v-tooltip.top="$t(`SIDEBAR.${name}`)"
|
||||
class="relative flex items-center justify-center w-10 h-10 my-2 rounded-lg text-slate-700 dark:text-slate-100 hover:!bg-slate-25 dark:hover:!bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600"
|
||||
:class="{
|
||||
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50':
|
||||
isHelpCenterSelected,
|
||||
}"
|
||||
@click="toggleHelpCenterMenu"
|
||||
>
|
||||
<fluent-icon
|
||||
:icon="icon"
|
||||
:class="{
|
||||
'text-woot-500': isHelpCenterSelected,
|
||||
}"
|
||||
/>
|
||||
<DropdownMenu
|
||||
v-if="showHelpCenterMenu && isHelpCenter"
|
||||
:menu-items="helpCenterMenuItems"
|
||||
class="ltr:left-10 rtl:right-10 w-36 z-[100] top-0 overflow-y-auto max-h-52"
|
||||
@action="handleHelpCenterAction"
|
||||
/>
|
||||
</button>
|
||||
</OnClickOutside>
|
||||
|
||||
<router-link v-else v-slot="{ href, isActive, navigate }" :to="to" custom>
|
||||
<a
|
||||
v-tooltip.right="$t(`SIDEBAR.${name}`)"
|
||||
:href="href"
|
||||
class="text-slate-700 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600 relative"
|
||||
class="relative flex items-center justify-center w-10 h-10 my-2 rounded-lg text-slate-700 dark:text-slate-100 hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600"
|
||||
:class="{
|
||||
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50':
|
||||
isActive || isChildMenuActive,
|
||||
@@ -52,7 +139,7 @@ export default {
|
||||
<span class="sr-only">{{ name }}</span>
|
||||
<span
|
||||
v-if="count"
|
||||
class="text-black-900 bg-yellow-500 absolute -top-1 -right-1"
|
||||
class="absolute bg-yellow-500 text-black-900 -top-1 -right-1"
|
||||
>
|
||||
{{ count }}
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,10 @@ import SecondaryNavItem from './SecondaryNavItem.vue';
|
||||
import AccountContext from './AccountContext.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { FEATURE_FLAGS } from '../../../featureFlags';
|
||||
import { hasPermissions } from '../../../helper/permissionsHelper';
|
||||
import {
|
||||
getUserPermissions,
|
||||
hasPermissions,
|
||||
} from '../../../helper/permissionsHelper';
|
||||
import { routesWithPermissions } from '../../../routes';
|
||||
|
||||
export default {
|
||||
@@ -46,20 +49,21 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['addLabel', 'toggleAccounts'],
|
||||
computed: {
|
||||
...mapGetters({
|
||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||
}),
|
||||
hasSecondaryMenu() {
|
||||
return this.menuConfig.menuItems && this.menuConfig.menuItems.length;
|
||||
},
|
||||
contactCustomViews() {
|
||||
return this.customViews.filter(view => view.filter_type === 'contact');
|
||||
},
|
||||
accessibleMenuItems() {
|
||||
const menuItemsFilteredByPermissions = this.menuConfig.menuItems.filter(
|
||||
menuItem => {
|
||||
const { permissions: userPermissions = [] } = this.currentUser;
|
||||
const userPermissions = getUserPermissions(
|
||||
this.currentUser,
|
||||
this.accountId
|
||||
);
|
||||
return hasPermissions(
|
||||
routesWithPermissions[menuItem.toStateName],
|
||||
userPermissions
|
||||
@@ -130,7 +134,7 @@ export default {
|
||||
icon: 'number-symbol',
|
||||
label: 'TAGGED_WITH',
|
||||
hasSubMenu: true,
|
||||
key: 'label',
|
||||
key: 'labels',
|
||||
newLink: this.showNewLink(FEATURE_FLAGS.TEAM_MANAGEMENT),
|
||||
newLinkTag: 'NEW_LABEL',
|
||||
toState: frontendURL(`accounts/${this.accountId}/settings/labels`),
|
||||
@@ -143,7 +147,7 @@ export default {
|
||||
color: label.color,
|
||||
truncateLabel: true,
|
||||
toState: frontendURL(
|
||||
`accounts/${this.accountId}/labels/${label.title}/contacts`
|
||||
`accounts/${this.accountId}/contacts/labels/${label.title}`
|
||||
),
|
||||
})),
|
||||
};
|
||||
@@ -190,7 +194,7 @@ export default {
|
||||
icon: 'folder',
|
||||
label: 'CUSTOM_VIEWS_SEGMENTS',
|
||||
hasSubMenu: true,
|
||||
key: 'custom_view',
|
||||
key: 'segments',
|
||||
children: this.customViews
|
||||
.filter(view => view.filter_type === 'contact')
|
||||
.map(view => ({
|
||||
@@ -198,7 +202,7 @@ export default {
|
||||
label: view.name,
|
||||
truncateLabel: true,
|
||||
toState: frontendURL(
|
||||
`accounts/${this.accountId}/contacts/custom_view/${view.id}`
|
||||
`accounts/${this.accountId}/contacts/segments/${view.id}`
|
||||
),
|
||||
})),
|
||||
};
|
||||
@@ -237,14 +241,13 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="hasSecondaryMenu"
|
||||
class="flex flex-col w-48 h-full px-2 pb-8 overflow-auto text-sm bg-white border-r dark:bg-slate-900 dark:border-slate-800/50 rtl:border-r-0 rtl:border-l border-slate-50"
|
||||
>
|
||||
<AccountContext @toggleAccounts="toggleAccountModal" />
|
||||
<AccountContext @toggle-accounts="toggleAccountModal" />
|
||||
<transition-group
|
||||
name="menu-list"
|
||||
tag="ul"
|
||||
class="pt-2 mb-0 ml-0 list-none"
|
||||
class="pt-2 list-none reset-base"
|
||||
>
|
||||
<SecondaryNavItem
|
||||
v-for="menuItem in accessibleMenuItems"
|
||||
@@ -255,7 +258,7 @@ export default {
|
||||
v-for="menuItem in additionalSecondaryMenuItems[menuConfig.parentNav]"
|
||||
:key="menuItem.key"
|
||||
:menu-item="menuItem"
|
||||
@addLabel="showAddLabelPopup"
|
||||
@add-label="showAddLabelPopup"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ export default {
|
||||
active-class="active"
|
||||
>
|
||||
<li
|
||||
class="font-medium h-7 my-1 hover:bg-slate-25 hover:text-bg-50 flex items-center px-2 rounded-md dark:hover:bg-slate-800"
|
||||
class="h-7 my-1 hover:bg-slate-25 hover:text-bg-50 flex items-center px-2 rounded-md dark:hover:bg-slate-800"
|
||||
:class="{
|
||||
'bg-woot-25 dark:bg-slate-800': isActive,
|
||||
'text-ellipsis overflow-hidden whitespace-nowrap max-w-full':
|
||||
@@ -105,7 +105,7 @@ export default {
|
||||
</span>
|
||||
<span
|
||||
v-if="showChildCount"
|
||||
class="bg-slate-50 dark:bg-slate-700 rounded-full min-w-[18px] justify-center items-center flex text-xxs font-medium mx-1 py-0 px-1"
|
||||
class="bg-slate-50 dark:bg-slate-700 rounded-full min-w-[18px] justify-center items-center flex text-xxs mx-1 py-0 px-1"
|
||||
:class="
|
||||
isCountZero
|
||||
? `text-slate-300 dark:text-slate-500`
|
||||
|
||||
@@ -22,6 +22,7 @@ export default {
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ['addLabel', 'open'],
|
||||
setup() {
|
||||
const { isAdmin } = useAdmin();
|
||||
const { isEnterprise } = useConfig();
|
||||
@@ -102,7 +103,7 @@ export default {
|
||||
},
|
||||
isInboxSettings() {
|
||||
return (
|
||||
this.$store.state.route.name === 'settings_inbox_show' &&
|
||||
this.$route.name === 'settings_inbox_show' &&
|
||||
this.menuItem.toStateName === 'settings_inbox_list'
|
||||
);
|
||||
},
|
||||
@@ -118,6 +119,13 @@ export default {
|
||||
this.menuItem.toStateName === 'settings_applications'
|
||||
);
|
||||
},
|
||||
isContactsDefaultRoute() {
|
||||
return (
|
||||
this.menuItem.toStateName === 'contacts_dashboard_index' &&
|
||||
(this.$store.state.route.name === 'contacts_dashboard_index' ||
|
||||
this.$store.state.route.name === 'contacts_edit')
|
||||
);
|
||||
},
|
||||
isCurrentRoute() {
|
||||
return this.$store.state.route.name.includes(this.menuItem.toStateName);
|
||||
},
|
||||
@@ -129,6 +137,7 @@ export default {
|
||||
this.isAllConversations ||
|
||||
this.isMentions ||
|
||||
this.isUnattended ||
|
||||
this.isContactsDefaultRoute ||
|
||||
this.isCurrentRoute
|
||||
) {
|
||||
return 'bg-woot-25 dark:bg-slate-800 text-woot-500 dark:text-woot-500 hover:text-woot-500 dark:hover:text-woot-500 active-view';
|
||||
@@ -208,7 +217,7 @@ export default {
|
||||
</div>
|
||||
<router-link
|
||||
v-else
|
||||
class="flex items-center p-2 m-0 text-sm font-medium leading-4 rounded-lg text-slate-700 dark:text-slate-100 hover:bg-slate-25 dark:hover:bg-slate-800"
|
||||
class="flex items-center p-2 m-0 text-sm leading-4 rounded-lg text-slate-700 dark:text-slate-100 hover:bg-slate-25 dark:hover:bg-slate-800"
|
||||
:class="computedClass"
|
||||
:to="menuItem && menuItem.toState"
|
||||
>
|
||||
@@ -220,7 +229,7 @@ export default {
|
||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||
<span
|
||||
v-if="showChildCount(menuItem.count)"
|
||||
class="px-1 py-0 mx-1 font-medium rounded-md text-xxs"
|
||||
class="px-1 py-0 mx-1 rounded-md text-xxs"
|
||||
:class="{
|
||||
'text-slate-300 dark:text-slate-600': isCountZero && !isActiveView,
|
||||
'text-slate-600 dark:text-slate-50': !isCountZero && !isActiveView,
|
||||
@@ -235,13 +244,13 @@ export default {
|
||||
v-if="menuItem.beta"
|
||||
data-view-component="true"
|
||||
label="Beta"
|
||||
class="inline-block px-1 mx-1 font-medium leading-4 text-green-500 border border-green-400 rounded-lg text-xxs"
|
||||
class="inline-block px-1 mx-1 leading-4 text-green-500 border border-green-400 rounded-lg text-xxs"
|
||||
>
|
||||
{{ $t('SIDEBAR.BETA') }}
|
||||
</span>
|
||||
</router-link>
|
||||
|
||||
<ul v-if="hasSubMenu" class="mb-0 ml-0 list-none">
|
||||
<ul v-if="hasSubMenu" class="list-none reset-base">
|
||||
<SecondaryChildNavItem
|
||||
v-for="child in menuItem.children"
|
||||
:key="child.id"
|
||||
|
||||
+43
-60
@@ -1,79 +1,62 @@
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { createStore } from 'vuex';
|
||||
import AccountSelector from '../AccountSelector.vue';
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import VueI18n from 'vue-i18n';
|
||||
|
||||
import i18n from 'dashboard/i18n';
|
||||
import WootModal from 'dashboard/components/Modal.vue';
|
||||
import WootModalHeader from 'dashboard/components/ModalHeader.vue';
|
||||
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.component('woot-modal', WootModal);
|
||||
localVue.component('woot-modal-header', WootModalHeader);
|
||||
localVue.component('fluent-icon', FluentIcon);
|
||||
|
||||
localVue.use(Vuex);
|
||||
localVue.use(VueI18n);
|
||||
|
||||
const i18nConfig = new VueI18n({
|
||||
locale: 'en',
|
||||
messages: i18n,
|
||||
const store = createStore({
|
||||
modules: {
|
||||
auth: {
|
||||
namespaced: false,
|
||||
getters: {
|
||||
getCurrentAccountId: () => 1,
|
||||
getCurrentUser: () => ({
|
||||
accounts: [
|
||||
{ id: 1, name: 'Chatwoot', role: 'administrator' },
|
||||
{ id: 2, name: 'GitX', role: 'agent' },
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
globalConfig: {
|
||||
namespaced: true,
|
||||
getters: {
|
||||
get: () => ({ createNewAccountFromDashboard: false }),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('accountSelctor', () => {
|
||||
describe('AccountSelector', () => {
|
||||
let accountSelector = null;
|
||||
const currentUser = {
|
||||
accounts: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Chatwoot',
|
||||
role: 'administrator',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'GitX',
|
||||
role: 'agent',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let actions = null;
|
||||
let modules = null;
|
||||
|
||||
beforeEach(() => {
|
||||
actions = {};
|
||||
modules = {
|
||||
auth: {
|
||||
getters: {
|
||||
getCurrentAccountId: () => 1,
|
||||
getCurrentUser: () => currentUser,
|
||||
},
|
||||
},
|
||||
globalConfig: {
|
||||
getters: {
|
||||
'globalConfig/get': () => ({ createNewAccountFromDashboard: false }),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let store = new Vuex.Store({ actions, modules });
|
||||
accountSelector = mount(AccountSelector, {
|
||||
store,
|
||||
localVue,
|
||||
i18n: i18nConfig,
|
||||
propsData: { showAccountModal: true },
|
||||
stubs: { WootButton: { template: '<button />' } },
|
||||
global: {
|
||||
plugins: [store],
|
||||
components: {
|
||||
'woot-modal': WootModal,
|
||||
'woot-modal-header': WootModalHeader,
|
||||
'fluent-icon': FluentIcon,
|
||||
},
|
||||
stubs: {
|
||||
WootButton: { template: '<button />' },
|
||||
// override global stub
|
||||
WootModalHeader: false,
|
||||
},
|
||||
},
|
||||
props: { showAccountModal: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('title and sub title exist', () => {
|
||||
const headerComponent = accountSelector.findComponent(WootModalHeader);
|
||||
const title = headerComponent.findComponent({ ref: 'modalHeaderTitle' });
|
||||
expect(title.text()).toBe('Switch Account');
|
||||
const content = headerComponent.findComponent({
|
||||
ref: 'modalHeaderContent',
|
||||
});
|
||||
const title = headerComponent.find('[data-test-id="modal-header-title"]');
|
||||
expect(title.text()).toBe('Switch account');
|
||||
const content = headerComponent.find(
|
||||
'[data-test-id="modal-header-content"]'
|
||||
);
|
||||
expect(content.text()).toBe('Select an account from the following list');
|
||||
});
|
||||
|
||||
|
||||
+32
-41
@@ -1,28 +1,10 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { createStore } from 'vuex';
|
||||
import AgentDetails from '../AgentDetails.vue';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import VTooltip from 'v-tooltip';
|
||||
|
||||
import i18n from 'dashboard/i18n';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import WootButton from 'dashboard/components/ui/WootButton.vue';
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
localVue.use(VueI18n);
|
||||
localVue.component('thumbnail', Thumbnail);
|
||||
localVue.component('woot-button', WootButton);
|
||||
localVue.component('woot-button', WootButton);
|
||||
localVue.use(VTooltip, {
|
||||
defaultHtml: false,
|
||||
});
|
||||
|
||||
const i18nConfig = new VueI18n({
|
||||
locale: 'en',
|
||||
messages: i18n,
|
||||
});
|
||||
|
||||
describe('agentDetails', () => {
|
||||
describe('AgentDetails', () => {
|
||||
const currentUser = {
|
||||
name: 'Neymar Junior',
|
||||
avatar_url: '',
|
||||
@@ -30,37 +12,46 @@ describe('agentDetails', () => {
|
||||
};
|
||||
const currentRole = 'agent';
|
||||
let store = null;
|
||||
let actions = null;
|
||||
let modules = null;
|
||||
let agentDetails = null;
|
||||
|
||||
beforeEach(() => {
|
||||
actions = {};
|
||||
const mockTooltipDirective = {
|
||||
mounted: (el, binding) => {
|
||||
// You can mock the behavior here if necessary
|
||||
el.setAttribute('data-tooltip', binding.value || '');
|
||||
},
|
||||
};
|
||||
|
||||
modules = {
|
||||
auth: {
|
||||
getters: {
|
||||
getCurrentUser: () => currentUser,
|
||||
getCurrentRole: () => currentRole,
|
||||
getCurrentUserAvailability: () => currentUser.availability_status,
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
auth: {
|
||||
namespaced: false,
|
||||
getters: {
|
||||
getCurrentUser: () => currentUser,
|
||||
getCurrentRole: () => currentRole,
|
||||
getCurrentUserAvailability: () => currentUser.availability_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
store = new Vuex.Store({
|
||||
actions,
|
||||
modules,
|
||||
});
|
||||
|
||||
agentDetails = shallowMount(AgentDetails, {
|
||||
store,
|
||||
localVue,
|
||||
i18n: i18nConfig,
|
||||
global: {
|
||||
plugins: [store],
|
||||
components: {
|
||||
Thumbnail,
|
||||
WootButton,
|
||||
},
|
||||
directives: {
|
||||
tooltip: mockTooltipDirective, // Mocking the tooltip directive
|
||||
},
|
||||
stubs: { WootButton: { template: '<button><slot /></button>' } },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(' the agent status', () => {
|
||||
expect(agentDetails.find('thumbnail-stub').vm.status).toBe('online');
|
||||
it('shows the correct agent status', () => {
|
||||
expect(agentDetails.findComponent(Thumbnail).vm.status).toBe('online');
|
||||
});
|
||||
|
||||
it('agent thumbnail exists', () => {
|
||||
|
||||
+32
-34
@@ -1,20 +1,7 @@
|
||||
import NotificationBell from '../NotificationBell.vue';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { createStore } from 'vuex';
|
||||
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
|
||||
|
||||
import i18n from 'dashboard/i18n';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
localVue.use(VueI18n);
|
||||
localVue.component('fluent-icon', FluentIcon);
|
||||
|
||||
const i18nConfig = new VueI18n({
|
||||
locale: 'en',
|
||||
messages: i18n,
|
||||
});
|
||||
import NotificationBell from '../NotificationBell.vue';
|
||||
|
||||
const $route = {
|
||||
name: 'notifications_index',
|
||||
@@ -33,43 +20,51 @@ describe('notificationBell', () => {
|
||||
};
|
||||
modules = {
|
||||
auth: {
|
||||
namespaced: false,
|
||||
getters: {
|
||||
getCurrentAccountId: () => accountId,
|
||||
},
|
||||
},
|
||||
notifications: {
|
||||
namespaced: false,
|
||||
getters: {
|
||||
'notifications/getMeta': () => notificationMetadata,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
store = new Vuex.Store({
|
||||
store = createStore({
|
||||
actions,
|
||||
modules,
|
||||
});
|
||||
});
|
||||
|
||||
it('it should return unread count 19 ', () => {
|
||||
it('it should return unread count 19', () => {
|
||||
const wrapper = shallowMount(NotificationBell, {
|
||||
localVue,
|
||||
i18n: i18nConfig,
|
||||
store,
|
||||
mocks: {
|
||||
$route,
|
||||
global: {
|
||||
plugins: [store],
|
||||
mocks: {
|
||||
$route,
|
||||
},
|
||||
components: {
|
||||
'fluent-icon': FluentIcon,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(wrapper.vm.unreadCount).toBe('19');
|
||||
});
|
||||
|
||||
it('it should return unread count 99+ ', async () => {
|
||||
it('it should return unread count 99+', async () => {
|
||||
notificationMetadata.unreadCount = 100;
|
||||
const wrapper = shallowMount(NotificationBell, {
|
||||
localVue,
|
||||
i18n: i18nConfig,
|
||||
store,
|
||||
mocks: {
|
||||
$route,
|
||||
global: {
|
||||
plugins: [store],
|
||||
mocks: {
|
||||
$route,
|
||||
},
|
||||
components: {
|
||||
'fluent-icon': FluentIcon,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(wrapper.vm.unreadCount).toBe('99+');
|
||||
@@ -77,11 +72,14 @@ describe('notificationBell', () => {
|
||||
|
||||
it('isNotificationPanelActive', async () => {
|
||||
const notificationBell = shallowMount(NotificationBell, {
|
||||
store,
|
||||
localVue,
|
||||
i18n: i18nConfig,
|
||||
mocks: {
|
||||
$route,
|
||||
global: {
|
||||
plugins: [store],
|
||||
mocks: {
|
||||
$route,
|
||||
},
|
||||
components: {
|
||||
'fluent-icon': FluentIcon,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { createStore } from 'vuex';
|
||||
import AvailabilityStatus from '../AvailabilityStatus.vue';
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import VTooltip from 'v-tooltip';
|
||||
|
||||
import WootButton from 'dashboard/components/ui/WootButton.vue';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||
@@ -11,70 +8,64 @@ import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader.vue
|
||||
import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider.vue';
|
||||
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
|
||||
|
||||
import i18n from 'dashboard/i18n';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VTooltip, {
|
||||
defaultHtml: false,
|
||||
});
|
||||
localVue.use(Vuex);
|
||||
localVue.use(VueI18n);
|
||||
localVue.component('woot-button', WootButton);
|
||||
localVue.component('woot-dropdown-header', WootDropdownHeader);
|
||||
localVue.component('woot-dropdown-menu', WootDropdownMenu);
|
||||
localVue.component('woot-dropdown-divider', WootDropdownDivider);
|
||||
localVue.component('woot-dropdown-item', WootDropdownItem);
|
||||
localVue.component('fluent-icon', FluentIcon);
|
||||
|
||||
const i18nConfig = new VueI18n({ locale: 'en', messages: i18n });
|
||||
|
||||
describe('AvailabilityStatus', () => {
|
||||
const currentAvailability = 'online';
|
||||
const currentAccountId = '1';
|
||||
const currentUserAutoOffline = false;
|
||||
let store = null;
|
||||
let actions = null;
|
||||
let modules = null;
|
||||
let availabilityStatus = null;
|
||||
|
||||
beforeEach(() => {
|
||||
actions = {
|
||||
updateAvailability: vi.fn(() => {
|
||||
return Promise.resolve();
|
||||
}),
|
||||
updateAvailability: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
modules = {
|
||||
auth: {
|
||||
getters: {
|
||||
getCurrentUserAvailability: () => currentAvailability,
|
||||
getCurrentAccountId: () => currentAccountId,
|
||||
getCurrentUserAutoOffline: () => currentUserAutoOffline,
|
||||
store = createStore({
|
||||
modules: {
|
||||
auth: {
|
||||
namespaced: false,
|
||||
getters: {
|
||||
getCurrentUserAvailability: () => currentAvailability,
|
||||
getCurrentAccountId: () => currentAccountId,
|
||||
getCurrentUserAutoOffline: () => currentUserAutoOffline,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
store = new Vuex.Store({ actions, modules });
|
||||
|
||||
availabilityStatus = mount(AvailabilityStatus, {
|
||||
store,
|
||||
localVue,
|
||||
i18n: i18nConfig,
|
||||
stubs: { WootSwitch: { template: '<button />' } },
|
||||
actions,
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches an action when user changes status', async () => {
|
||||
await availabilityStatus;
|
||||
availabilityStatus
|
||||
.findAll('.status-change--dropdown-button')
|
||||
.at(2)
|
||||
.trigger('click');
|
||||
const wrapper = mount(AvailabilityStatus, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
components: {
|
||||
WootButton,
|
||||
WootDropdownItem,
|
||||
WootDropdownMenu,
|
||||
WootDropdownHeader,
|
||||
WootDropdownDivider,
|
||||
FluentIcon,
|
||||
},
|
||||
stubs: {
|
||||
WootSwitch: { template: '<button />' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(actions.updateAvailability).toBeCalledWith(
|
||||
expect.any(Object),
|
||||
{ availability: 'offline', account_id: currentAccountId },
|
||||
undefined
|
||||
);
|
||||
// Ensure that the dropdown menu is opened
|
||||
await wrapper.vm.openStatusMenu();
|
||||
|
||||
// Simulate the user clicking the 3rd button (offline status)
|
||||
const buttons = wrapper.findAll('.status-change--dropdown-button');
|
||||
expect(buttons.length).toBeGreaterThan(0); // Ensure buttons exist
|
||||
|
||||
await buttons[2].trigger('click');
|
||||
|
||||
expect(actions.updateAvailability).toHaveBeenCalledTimes(1);
|
||||
expect(actions.updateAvailability.mock.calls[0][1]).toEqual({
|
||||
availability: 'offline',
|
||||
account_id: currentAccountId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user