-
-
-
-
- {{ t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS') }}
-
-
-
+
+
+
+
+
{
icon="i-lucide-check"
class="text-n-teal-11 size-5"
/>
-
-
-
-
- {{ t('CREATE_ACCOUNT.NEW_ACCOUNT') }}
-
-
-
-
-
+
+
+
+
+
+ {{ t('CREATE_ACCOUNT.NEW_ACCOUNT') }}
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenu.vue b/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenu.vue
index ae88ebe69..347e2e25d 100644
--- a/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenu.vue
+++ b/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenu.vue
@@ -4,11 +4,16 @@ import Auth from 'dashboard/api/auth';
import { useRouter } from 'vue-router';
import { useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'vue-i18n';
-import { useToggle } from '@vueuse/core';
import Avatar from 'next/avatar/Avatar.vue';
-import Icon from 'next/icon/Icon.vue';
import SidebarProfileMenuStatus from './SidebarProfileMenuStatus.vue';
+import {
+ DropdownContainer,
+ DropdownBody,
+ DropdownSeparator,
+ DropdownItem,
+} from 'next/dropdown-menu/base';
+
const emit = defineEmits(['close', 'openKeyShortcutModal']);
defineOptions({
@@ -21,14 +26,6 @@ const router = useRouter();
const globalConfig = useMapGetter('globalConfig/get');
const currentUser = useMapGetter('getCurrentUser');
const currentUserAvailability = useMapGetter('getCurrentUserAvailability');
-const [showProfileMenu, toggleProfileMenu] = useToggle(false);
-
-const closeMenu = () => {
- if (showProfileMenu.value) {
- emit('close');
- toggleProfileMenu(false);
- }
-};
const menuItems = computed(() => {
return [
@@ -37,7 +34,6 @@ const menuItems = computed(() => {
label: t('SIDEBAR_ITEMS.CONTACT_SUPPORT'),
icon: 'i-lucide-life-buoy',
click: () => {
- closeMenu();
window.$chatwoot.toggle();
},
},
@@ -46,7 +42,6 @@ const menuItems = computed(() => {
label: t('SIDEBAR_ITEMS.KEYBOARD_SHORTCUTS'),
icon: 'i-lucide-keyboard',
click: () => {
- closeMenu();
emit('openKeyShortcutModal');
},
},
@@ -55,20 +50,26 @@ const menuItems = computed(() => {
label: t('SIDEBAR_ITEMS.PROFILE_SETTINGS'),
icon: 'i-lucide-user-pen',
click: () => {
- closeMenu();
router.push({ name: 'profile_settings_index' });
},
},
{
show: true,
label: t('SIDEBAR_ITEMS.APPEARANCE'),
- icon: 'i-lucide-swatch-book',
+ icon: 'i-lucide-palette',
click: () => {
- closeMenu();
const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'appearance_settings' });
},
},
+ {
+ show: true,
+ label: t('SIDEBAR_ITEMS.DOCS'),
+ icon: 'i-lucide-book',
+ click: () => {
+ window.open('https://www.chatwoot.com/hc/user-guide/en', '_blank');
+ },
+ },
{
show: currentUser.value.type === 'SuperAdmin',
label: t('SIDEBAR_ITEMS.SUPER_ADMIN_CONSOLE'),
@@ -79,7 +80,7 @@ const menuItems = computed(() => {
{
show: true,
label: t('SIDEBAR_ITEMS.LOGOUT'),
- icon: 'i-lucide-log-out',
+ icon: 'i-lucide-power',
click: Auth.logout,
},
];
@@ -91,56 +92,40 @@ const allowedMenuItems = computed(() => {
-
-
-
-
+
+
-
-
+
+
+
+ {{ currentUser.available_name }}
+
+
+ {{ currentUser.email }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue b/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue
index 9642e4103..418319cc3 100644
--- a/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue
+++ b/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue
@@ -1,11 +1,18 @@
-
-
- {{ t('SIDEBAR.SET_AVAILABILITY_TITLE') }}
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
+
+
+
+
+ {{ $t('SIDEBAR.SET_YOUR_AVAILABILITY') }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
+
-
- {{ t('SIDEBAR.SET_AUTO_OFFLINE.INFO_SHORT') }}
-
-
-
-
-
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarSubGroup.vue b/app/javascript/dashboard/components-next/sidebar/SidebarSubGroup.vue
index 8c7f5c3a7..52886ebde 100644
--- a/app/javascript/dashboard/components-next/sidebar/SidebarSubGroup.vue
+++ b/app/javascript/dashboard/components-next/sidebar/SidebarSubGroup.vue
@@ -52,7 +52,7 @@ useEventListener(scrollableContainer, 'scroll', () => {
:icon
class="my-1"
/>
-
+
-
+
{
it('title and sub title exist', () => {
const headerComponent = accountSelector.findComponent(WootModalHeader);
const title = headerComponent.find('[data-test-id="modal-header-title"]');
- expect(title.text()).toBe('Switch Account');
+ expect(title.text()).toBe('Switch account');
const content = headerComponent.find(
'[data-test-id="modal-header-content"]'
);
diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json
index 414291dfd..9b2c5fde6 100644
--- a/app/javascript/dashboard/i18n/locale/en/settings.json
+++ b/app/javascript/dashboard/i18n/locale/en/settings.json
@@ -177,14 +177,16 @@
},
"SIDEBAR_ITEMS": {
"CHANGE_AVAILABILITY_STATUS": "Change",
- "CHANGE_ACCOUNTS": "Switch Account",
- "CONTACT_SUPPORT": "Contact Support",
+ "CHANGE_ACCOUNTS": "Switch account",
+ "SWITCH_WORKSPACE": "Switch workspace",
+ "CONTACT_SUPPORT": "Contact support",
"SELECTOR_SUBTITLE": "Select an account from the following list",
- "PROFILE_SETTINGS": "Profile Settings",
- "KEYBOARD_SHORTCUTS": "Keyboard Shortcuts",
- "APPEARANCE": "Change Appearance",
- "SUPER_ADMIN_CONSOLE": "Super Admin Console",
- "LOGOUT": "Logout"
+ "PROFILE_SETTINGS": "Profile settings",
+ "KEYBOARD_SHORTCUTS": "Keyboard shortcuts",
+ "APPEARANCE": "Change appearance",
+ "SUPER_ADMIN_CONSOLE": "SuperAdmin console",
+ "DOCS": "Read documentation",
+ "LOGOUT": "Log out"
},
"APP_GLOBAL": {
"TRIAL_MESSAGE": "days trial remaining.",
@@ -279,6 +281,7 @@
"REPORTS_INBOX": "Inbox",
"REPORTS_TEAM": "Team",
"SET_AVAILABILITY_TITLE": "Set yourself as",
+ "SET_YOUR_AVAILABILITY": "Set your availability",
"SLA": "SLA",
"CUSTOM_ROLES": "Custom Roles",
"BETA": "Beta",
diff --git a/app/javascript/dashboard/store/modules/auth.js b/app/javascript/dashboard/store/modules/auth.js
index e2c1aac75..695edc3cb 100644
--- a/app/javascript/dashboard/store/modules/auth.js
+++ b/app/javascript/dashboard/store/modules/auth.js
@@ -151,8 +151,15 @@ export const actions = {
}
},
- updateAvailability: async ({ commit, dispatch }, params) => {
+ updateAvailability: async (
+ { commit, dispatch, getters: _getters },
+ params
+ ) => {
+ const previousStatus = _getters.getCurrentUserAvailability;
+
try {
+ // optimisticly update current status
+ commit(types.SET_CURRENT_USER_AVAILABILITY, params.availability);
const response = await authAPI.updateAvailability(params);
const userData = response.data;
const { id } = userData;
@@ -162,16 +169,23 @@ export const actions = {
availabilityStatus: params.availability,
});
} catch (error) {
- // Ignore error
+ // revert back to previous status if update fails
+ commit(types.SET_CURRENT_USER_AVAILABILITY, previousStatus);
}
},
- updateAutoOffline: async ({ commit }, { accountId, autoOffline }) => {
+ updateAutoOffline: async (
+ { commit, getters: _getters },
+ { accountId, autoOffline }
+ ) => {
+ const previousAutoOffline = _getters.getCurrentUserAutoOffline;
+
try {
+ commit(types.SET_CURRENT_USER_AUTO_OFFLINE, autoOffline);
const response = await authAPI.updateAutoOffline(accountId, autoOffline);
commit(types.SET_CURRENT_USER, response.data);
} catch (error) {
- // Ignore error
+ commit(types.SET_CURRENT_USER_AUTO_OFFLINE, previousAutoOffline);
}
},
@@ -212,6 +226,19 @@ export const mutations = {
accounts,
};
},
+ [types.SET_CURRENT_USER_AUTO_OFFLINE](_state, autoOffline) {
+ const accounts = _state.currentUser.accounts.map(account => {
+ if (account.id === _state.currentUser.account_id) {
+ return { ...account, autoOffline: autoOffline };
+ }
+ return account;
+ });
+
+ _state.currentUser = {
+ ..._state.currentUser,
+ accounts,
+ };
+ },
[types.CLEAR_USER](_state) {
_state.currentUser = initialState.currentUser;
},
diff --git a/app/javascript/dashboard/store/modules/specs/auth/actions.spec.js b/app/javascript/dashboard/store/modules/specs/auth/actions.spec.js
index f5206a8a6..3de56b1fe 100644
--- a/app/javascript/dashboard/store/modules/specs/auth/actions.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/auth/actions.spec.js
@@ -1,7 +1,7 @@
import axios from 'axios';
import Cookies from 'js-cookie';
import { actions } from '../../auth';
-import * as types from '../../../mutation-types';
+import types from '../../../mutation-types';
import * as APIHelpers from '../../../utils/api';
import '../../../../routes';
@@ -25,7 +25,7 @@ describe('#actions', () => {
await actions.validityCheck({ commit });
expect(APIHelpers.setUser).toHaveBeenCalledTimes(1);
expect(commit.mock.calls).toEqual([
- [types.default.SET_CURRENT_USER, { id: 1, name: 'John' }],
+ [types.SET_CURRENT_USER, { id: 1, name: 'John' }],
]);
});
it('sends correct actions if API is error', async () => {
@@ -45,7 +45,7 @@ describe('#actions', () => {
});
await actions.updateProfile({ commit }, { name: 'Pranav' });
expect(commit.mock.calls).toEqual([
- [types.default.SET_CURRENT_USER, { id: 1, name: 'John' }],
+ [types.SET_CURRENT_USER, { id: 1, name: 'John' }],
]);
});
});
@@ -61,12 +61,13 @@ describe('#actions', () => {
headers: { expiry: 581842904 },
});
await actions.updateAvailability(
- { commit, dispatch },
+ { commit, dispatch, getters: { getCurrentUserAvailability: 'online' } },
{ availability: 'offline', account_id: 1 }
);
expect(commit.mock.calls).toEqual([
+ [types.SET_CURRENT_USER_AVAILABILITY, 'offline'],
[
- types.default.SET_CURRENT_USER,
+ types.SET_CURRENT_USER,
{
id: 1,
name: 'John',
@@ -81,6 +82,18 @@ describe('#actions', () => {
],
]);
});
+
+ it('sends correct actions if API is a failure', async () => {
+ axios.post.mockRejectedValue({ error: 'Authentication Failure' });
+ await actions.updateAvailability(
+ { commit, dispatch, getters: { getCurrentUserAvailability: 'online' } },
+ { availability: 'offline', account_id: 1 }
+ );
+ expect(commit.mock.calls).toEqual([
+ [types.SET_CURRENT_USER_AVAILABILITY, 'offline'],
+ [types.SET_CURRENT_USER_AVAILABILITY, 'online'],
+ ]);
+ });
});
describe('#updateAutoOffline', () => {
@@ -99,12 +112,13 @@ describe('#actions', () => {
headers: { expiry: 581842904 },
});
await actions.updateAutoOffline(
- { commit, dispatch },
+ { commit, dispatch, getters: { getCurrentUserAutoOffline: true } },
{ autoOffline: false, accountId: 1 }
);
expect(commit.mock.calls).toEqual([
+ [types.SET_CURRENT_USER_AUTO_OFFLINE, false],
[
- types.default.SET_CURRENT_USER,
+ types.SET_CURRENT_USER,
{
id: 1,
name: 'John',
@@ -113,6 +127,17 @@ describe('#actions', () => {
],
]);
});
+ it('sends correct actions if API is failure', async () => {
+ axios.post.mockRejectedValue({ error: 'Authentication Failure' });
+ await actions.updateAutoOffline(
+ { commit, dispatch, getters: { getCurrentUserAutoOffline: true } },
+ { autoOffline: false, accountId: 1 }
+ );
+ expect(commit.mock.calls).toEqual([
+ [types.SET_CURRENT_USER_AUTO_OFFLINE, false],
+ [types.SET_CURRENT_USER_AUTO_OFFLINE, true],
+ ]);
+ });
});
describe('#updateUISettings', () => {
@@ -132,11 +157,11 @@ describe('#actions', () => {
);
expect(commit.mock.calls).toEqual([
[
- types.default.SET_CURRENT_USER_UI_SETTINGS,
+ types.SET_CURRENT_USER_UI_SETTINGS,
{ uiSettings: { is_contact_sidebar_open: false } },
],
[
- types.default.SET_CURRENT_USER,
+ types.SET_CURRENT_USER,
{
id: 1,
name: 'John',
@@ -160,8 +185,8 @@ describe('#actions', () => {
Cookies.get.mockImplementation(() => false);
actions.setUser({ commit, dispatch });
expect(commit.mock.calls).toEqual([
- [types.default.CLEAR_USER],
- [types.default.SET_CURRENT_USER_UI_FLAGS, { isFetching: false }],
+ [types.CLEAR_USER],
+ [types.SET_CURRENT_USER_UI_FLAGS, { isFetching: false }],
]);
expect(dispatch).toHaveBeenCalledTimes(0);
});
@@ -177,7 +202,7 @@ describe('#actions', () => {
{ 1: 'online' }
);
expect(commit.mock.calls).toEqual([
- [types.default.SET_CURRENT_USER_AVAILABILITY, 'online'],
+ [types.SET_CURRENT_USER_AVAILABILITY, 'online'],
]);
});
diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js
index 2a6bcacb8..7cc5223a0 100644
--- a/app/javascript/dashboard/store/mutation-types.js
+++ b/app/javascript/dashboard/store/mutation-types.js
@@ -3,6 +3,7 @@ export default {
CLEAR_USER: 'LOGOUT',
SET_CURRENT_USER: 'SET_CURRENT_USER',
SET_CURRENT_USER_AVAILABILITY: 'SET_CURRENT_USER_AVAILABILITY',
+ SET_CURRENT_USER_AUTO_OFFLINE: 'SET_CURRENT_USER_AUTO_OFFLINE',
SET_CURRENT_USER_UI_SETTINGS: 'SET_CURRENT_USER_UI_SETTINGS',
SET_CURRENT_USER_UI_FLAGS: 'SET_CURRENT_USER_UI_FLAGS',