feat: Rewrite command bar mixin to a composable (#10015)

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
Sivin Varghese
2024-08-26 15:55:59 +05:30
committed by GitHub
parent 3489783cb8
commit 7f8d718da3
24 changed files with 1547 additions and 756 deletions

View File

@@ -0,0 +1,197 @@
export const mockAssignableAgents = [
{
id: 1,
account_id: 1,
availability_status: 'online',
auto_offline: true,
confirmed: true,
email: 'john@doe.com',
available_name: 'John Doe',
name: 'John Doe',
role: 'administrator',
thumbnail: '',
},
];
export const mockCurrentChat = {
meta: {
sender: {
additional_attributes: {},
availability_status: 'offline',
email: null,
id: 212,
name: 'Chatwoot',
phone_number: null,
identifier: null,
thumbnail: '',
custom_attributes: {},
last_activity_at: 1723553344,
created_at: 1722588710,
},
channel: 'Channel::WebWidget',
assignee: {
id: 1,
account_id: 1,
availability_status: 'online',
auto_offline: true,
confirmed: true,
email: 'john@doe.com',
available_name: 'John Doe',
name: 'John Doe',
role: 'administrator',
thumbnail: '',
},
hmac_verified: false,
},
id: 138,
messages: [
{
id: 3348,
content: 'Hello, how can I assist you today?',
account_id: 1,
inbox_id: 1,
conversation_id: 138,
message_type: 1,
created_at: 1724398739,
updated_at: '2024-08-23T07:38:59.763Z',
private: false,
status: 'sent',
source_id: null,
content_type: 'text',
content_attributes: {},
sender_type: 'User',
sender_id: 1,
external_source_ids: {},
additional_attributes: {},
processed_message_content: 'Hello, how can I assist you today?',
sentiment: {},
conversation: {
assignee_id: 1,
unread_count: 0,
last_activity_at: 1724398739,
contact_inbox: {
source_id: '5e57317d-053b-4a72-8292-a25b9f29c401',
},
},
sender: {
id: 1,
name: 'John Doe',
available_name: 'John Doe',
avatar_url: '',
type: 'user',
availability_status: 'online',
thumbnail: '',
},
},
],
account_id: 1,
uuid: '69dd6922-2f0c-4317-8796-bbeb3679cead',
additional_attributes: {
browser: {
device_name: 'Unknown',
browser_name: 'Chrome',
platform_name: 'macOS',
browser_version: '127.0.0.0',
platform_version: '10.15.7',
},
referer: 'http://chatwoot.com/widget_tests?dark_mode=auto',
initiated_at: {
timestamp: 'Fri Aug 02 2024 15:21:18 GMT+0530 (India Standard Time)',
},
browser_language: 'en',
},
agent_last_seen_at: 1724400730,
assignee_last_seen_at: 1724400686,
can_reply: true,
contact_last_seen_at: 1723553351,
custom_attributes: {},
inbox_id: 1,
labels: ['billing'],
muted: false,
snoozed_until: null,
status: 'open',
created_at: 1722592278,
timestamp: 1724398739,
first_reply_created_at: 1722592316,
unread_count: 0,
last_non_activity_message: {},
last_activity_at: 1724398739,
priority: null,
waiting_since: 0,
sla_policy_id: 10,
applied_sla: {
id: 143,
sla_id: 10,
sla_status: 'missed',
created_at: 1722592279,
updated_at: 1722874214,
sla_description: '',
sla_name: 'Hacker SLA',
sla_first_response_time_threshold: 600,
sla_next_response_time_threshold: 240,
sla_only_during_business_hours: false,
sla_resolution_time_threshold: 259200,
},
sla_events: [
{
id: 270,
event_type: 'nrt',
meta: {
message_id: 2743,
},
updated_at: 1722592819,
created_at: 1722592819,
},
{
id: 275,
event_type: 'rt',
meta: {},
updated_at: 1722852322,
created_at: 1722852322,
},
],
allMessagesLoaded: false,
dataFetched: true,
};
export const mockTeamsList = [
{
id: 5,
name: 'design',
description: 'design team',
allow_auto_assign: true,
account_id: 1,
is_member: false,
},
];
export const mockActiveLabels = [
{
id: 16,
title: 'billing',
description: '',
color: '#D8EA19',
show_on_sidebar: true,
},
];
export const mockInactiveLabels = [
{
id: 2,
title: 'Feature Request',
description: '',
color: '#D8EA19',
show_on_sidebar: true,
},
];
export const MOCK_FEATURE_FLAGS = {
CRM: 'crm',
AGENT_MANAGEMENT: 'agent_management',
TEAM_MANAGEMENT: 'team_management',
INBOX_MANAGEMENT: 'inbox_management',
REPORTS: 'reports',
LABELS: 'labels',
CANNED_RESPONSES: 'canned_responses',
INTEGRATIONS: 'integrations',
};

View File

@@ -0,0 +1,83 @@
import { useAppearanceHotKeys } from '../useAppearanceHotKeys';
import { useI18n } from 'dashboard/composables/useI18n';
import { LocalStorage } from 'shared/helpers/localStorage';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { setColorTheme } from 'dashboard/helper/themeHelper.js';
vi.mock('dashboard/composables/useI18n');
vi.mock('shared/helpers/localStorage');
vi.mock('dashboard/helper/themeHelper.js');
describe('useAppearanceHotKeys', () => {
beforeEach(() => {
useI18n.mockReturnValue({
t: vi.fn(key => key),
});
window.matchMedia = vi.fn().mockReturnValue({ matches: false });
});
it('should return goToAppearanceHotKeys computed property', () => {
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
expect(goToAppearanceHotKeys.value).toBeDefined();
});
it('should have the correct number of appearance options', () => {
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
expect(goToAppearanceHotKeys.value.length).toBe(4); // 1 parent + 3 theme options
});
it('should have the correct parent option', () => {
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
const parentOption = goToAppearanceHotKeys.value.find(
option => option.id === 'appearance_settings'
);
expect(parentOption).toBeDefined();
expect(parentOption.children.length).toBe(3);
});
it('should have the correct theme options', () => {
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
const themeOptions = goToAppearanceHotKeys.value.filter(
option => option.parent === 'appearance_settings'
);
expect(themeOptions.length).toBe(3);
expect(themeOptions.map(option => option.id)).toEqual([
'light',
'dark',
'auto',
]);
});
it('should call setAppearance when a theme option is selected', () => {
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
const lightThemeOption = goToAppearanceHotKeys.value.find(
option => option.id === 'light'
);
lightThemeOption.handler();
expect(LocalStorage.set).toHaveBeenCalledWith(
LOCAL_STORAGE_KEYS.COLOR_SCHEME,
'light'
);
expect(setColorTheme).toHaveBeenCalledWith(false);
});
it('should handle system dark mode preference', () => {
window.matchMedia = vi.fn().mockReturnValue({ matches: true });
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
const autoThemeOption = goToAppearanceHotKeys.value.find(
option => option.id === 'auto'
);
autoThemeOption.handler();
expect(LocalStorage.set).toHaveBeenCalledWith(
LOCAL_STORAGE_KEYS.COLOR_SCHEME,
'auto'
);
expect(setColorTheme).toHaveBeenCalledWith(true);
});
});

View File

@@ -0,0 +1,102 @@
import { useBulkActionsHotKeys } from '../useBulkActionsHotKeys';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import wootConstants from 'dashboard/constants/globals';
import { emitter } from 'shared/helpers/mitt';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables/useI18n');
vi.mock('shared/helpers/mitt');
describe('useBulkActionsHotKeys', () => {
let store;
beforeEach(() => {
store = {
getters: {
'bulkActions/getSelectedConversationIds': [],
},
};
useStore.mockReturnValue(store);
useMapGetter.mockImplementation(key => ({
value: store.getters[key],
}));
useI18n.mockReturnValue({ t: vi.fn(key => key) });
emitter.emit = vi.fn();
});
it('should return bulk actions when conversations are selected', () => {
store.getters['bulkActions/getSelectedConversationIds'] = [1, 2, 3];
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
expect(bulkActionsHotKeys.value.length).toBeGreaterThan(0);
expect(bulkActionsHotKeys.value).toContainEqual(
expect.objectContaining({
id: 'bulk_action_snooze_conversation',
title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
})
);
expect(bulkActionsHotKeys.value).toContainEqual(
expect.objectContaining({
id: 'bulk_action_reopen_conversation',
title: 'COMMAND_BAR.COMMANDS.REOPEN_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
})
);
expect(bulkActionsHotKeys.value).toContainEqual(
expect.objectContaining({
id: 'bulk_action_resolve_conversation',
title: 'COMMAND_BAR.COMMANDS.RESOLVE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
})
);
});
it('should include snooze options in bulk actions', () => {
store.getters['bulkActions/getSelectedConversationIds'] = [1, 2, 3];
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
const snoozeAction = bulkActionsHotKeys.value.find(
action => action.id === 'bulk_action_snooze_conversation'
);
expect(snoozeAction).toBeDefined();
expect(snoozeAction.children).toEqual(
Object.values(wootConstants.SNOOZE_OPTIONS)
);
});
it('should create handlers for reopen and resolve actions', () => {
store.getters['bulkActions/getSelectedConversationIds'] = [1, 2, 3];
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
const reopenAction = bulkActionsHotKeys.value.find(
action => action.id === 'bulk_action_reopen_conversation'
);
const resolveAction = bulkActionsHotKeys.value.find(
action => action.id === 'bulk_action_resolve_conversation'
);
expect(reopenAction.handler).toBeDefined();
expect(resolveAction.handler).toBeDefined();
reopenAction.handler();
expect(emitter.emit).toHaveBeenCalledWith(
'CMD_BULK_ACTION_REOPEN_CONVERSATION'
);
resolveAction.handler();
expect(emitter.emit).toHaveBeenCalledWith(
'CMD_BULK_ACTION_RESOLVE_CONVERSATION'
);
});
it('should return an empty array when no conversations are selected', () => {
store.getters['bulkActions/getSelectedConversationIds'] = [];
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
expect(bulkActionsHotKeys.value).toEqual([]);
});
});

View File

@@ -0,0 +1,204 @@
import { useConversationHotKeys } from '../useConversationHotKeys';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRoute } from 'dashboard/composables/route';
import { useConversationLabels } from 'dashboard/composables/useConversationLabels';
import { useAI } from 'dashboard/composables/useAI';
import { useAgentsList } from 'dashboard/composables/useAgentsList';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
import {
mockAssignableAgents,
mockCurrentChat,
mockTeamsList,
mockActiveLabels,
mockInactiveLabels,
} from './fixtures';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables/useI18n');
vi.mock('dashboard/composables/route');
vi.mock('dashboard/composables/useConversationLabels');
vi.mock('dashboard/composables/useAI');
vi.mock('dashboard/composables/useAgentsList');
describe('useConversationHotKeys', () => {
let store;
beforeEach(() => {
store = {
dispatch: vi.fn(),
getters: {
getSelectedChat: mockCurrentChat,
'draftMessages/getReplyEditorMode': REPLY_EDITOR_MODES.REPLY,
getContextMenuChatId: null,
'teams/getTeams': mockTeamsList,
'draftMessages/get': vi.fn(),
},
};
useStore.mockReturnValue(store);
useMapGetter.mockImplementation(key => ({
value: store.getters[key],
}));
useI18n.mockReturnValue({ t: vi.fn(key => key) });
useRoute.mockReturnValue({ name: 'inbox_conversation' });
useConversationLabels.mockReturnValue({
activeLabels: { value: mockActiveLabels },
inactiveLabels: { value: mockInactiveLabels },
addLabelToConversation: vi.fn(),
removeLabelFromConversation: vi.fn(),
});
useAI.mockReturnValue({ isAIIntegrationEnabled: { value: true } });
useAgentsList.mockReturnValue({
agentsList: { value: [] },
assignableAgents: { value: mockAssignableAgents },
});
});
it('should return the correct computed properties', () => {
const { conversationHotKeys } = useConversationHotKeys();
expect(conversationHotKeys.value).toBeDefined();
});
it('should generate conversation hot keys', () => {
const { conversationHotKeys } = useConversationHotKeys();
expect(conversationHotKeys.value.length).toBeGreaterThan(0);
});
it('should include AI assist actions when AI integration is enabled', () => {
const { conversationHotKeys } = useConversationHotKeys();
const aiAssistAction = conversationHotKeys.value.find(
action => action.id === 'ai_assist'
);
expect(aiAssistAction).toBeDefined();
});
it('should not include AI assist actions when AI integration is disabled', () => {
useAI.mockReturnValue({ isAIIntegrationEnabled: { value: false } });
const { conversationHotKeys } = useConversationHotKeys();
const aiAssistAction = conversationHotKeys.value.find(
action => action.id === 'ai_assist'
);
expect(aiAssistAction).toBeUndefined();
});
it('should dispatch actions when handlers are called', () => {
const { conversationHotKeys } = useConversationHotKeys();
const assignAgentAction = conversationHotKeys.value.find(
action => action.id === 'assign_an_agent'
);
expect(assignAgentAction).toBeDefined();
if (assignAgentAction && assignAgentAction.children) {
const childAction = conversationHotKeys.value.find(
action => action.id === assignAgentAction.children[0]
);
if (childAction && childAction.handler) {
childAction.handler({ agentInfo: { id: 2 } });
expect(store.dispatch).toHaveBeenCalledWith('assignAgent', {
conversationId: 1,
agentId: 2,
});
}
}
});
it('should return snooze actions when in snooze context', () => {
store.getters.getContextMenuChatId = 1;
useMapGetter.mockImplementation(key => ({
value: store.getters[key],
}));
useRoute.mockReturnValue({ name: 'inbox_conversation' });
const { conversationHotKeys } = useConversationHotKeys();
const snoozeAction = conversationHotKeys.value.find(action =>
action.id.includes('snooze_conversation')
);
expect(snoozeAction).toBeDefined();
});
it('should return the correct label actions when there are active labels', () => {
const { conversationHotKeys } = useConversationHotKeys();
const addLabelAction = conversationHotKeys.value.find(
action => action.id === 'add_a_label_to_the_conversation'
);
const removeLabelAction = conversationHotKeys.value.find(
action => action.id === 'remove_a_label_to_the_conversation'
);
expect(addLabelAction).toBeDefined();
expect(removeLabelAction).toBeDefined();
});
it('should return only add label actions when there are no active labels', () => {
useConversationLabels.mockReturnValue({
activeLabels: { value: [] },
inactiveLabels: { value: [{ title: 'inactive_label' }] },
addLabelToConversation: vi.fn(),
removeLabelFromConversation: vi.fn(),
});
const { conversationHotKeys } = useConversationHotKeys();
const addLabelAction = conversationHotKeys.value.find(
action => action.id === 'add_a_label_to_the_conversation'
);
const removeLabelAction = conversationHotKeys.value.find(
action => action.id === 'remove_a_label_to_the_conversation'
);
expect(addLabelAction).toBeDefined();
expect(removeLabelAction).toBeUndefined();
});
it('should return the correct team assignment actions', () => {
const { conversationHotKeys } = useConversationHotKeys();
const assignTeamAction = conversationHotKeys.value.find(
action => action.id === 'assign_a_team'
);
expect(assignTeamAction).toBeDefined();
expect(assignTeamAction.children.length).toBe(mockTeamsList.length);
});
it('should return the correct priority assignment actions', () => {
const { conversationHotKeys } = useConversationHotKeys();
const assignPriorityAction = conversationHotKeys.value.find(
action => action.id === 'assign_priority'
);
expect(assignPriorityAction).toBeDefined();
expect(assignPriorityAction.children.length).toBe(4);
});
it('should return the correct conversation additional actions', () => {
const { conversationHotKeys } = useConversationHotKeys();
const muteAction = conversationHotKeys.value.find(
action => action.id === 'mute_conversation'
);
const sendTranscriptAction = conversationHotKeys.value.find(
action => action.id === 'send_transcript'
);
expect(muteAction).toBeDefined();
expect(sendTranscriptAction).toBeDefined();
});
it('should return unmute action when conversation is muted', () => {
store.getters.getSelectedChat = { ...mockCurrentChat, muted: true };
const { conversationHotKeys } = useConversationHotKeys();
const unmuteAction = conversationHotKeys.value.find(
action => action.id === 'unmute_conversation'
);
expect(unmuteAction).toBeDefined();
});
it('should not return conversation hot keys when not in conversation or inbox route', () => {
useRoute.mockReturnValue({ name: 'some_other_route' });
const { conversationHotKeys } = useConversationHotKeys();
expect(conversationHotKeys.value.length).toBe(0);
});
});

View File

@@ -0,0 +1,188 @@
import { useGoToCommandHotKeys } from '../useGoToCommandHotKeys';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRouter } from 'dashboard/composables/route';
import { useAdmin } from 'dashboard/composables/useAdmin';
import { frontendURL } from 'dashboard/helper/URLHelper';
import { MOCK_FEATURE_FLAGS } from './fixtures';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables/useI18n');
vi.mock('dashboard/composables/route');
vi.mock('dashboard/composables/useAdmin');
vi.mock('dashboard/helper/URLHelper');
const mockRoutes = [
{ path: 'accounts/:accountId/dashboard', name: 'dashboard' },
{
path: 'accounts/:accountId/contacts',
name: 'contacts',
featureFlag: MOCK_FEATURE_FLAGS.CRM,
},
{
path: 'accounts/:accountId/settings/agents/list',
name: 'agent_settings',
featureFlag: MOCK_FEATURE_FLAGS.AGENT_MANAGEMENT,
},
{
path: 'accounts/:accountId/settings/teams/list',
name: 'team_settings',
featureFlag: MOCK_FEATURE_FLAGS.TEAM_MANAGEMENT,
},
{
path: 'accounts/:accountId/settings/inboxes/list',
name: 'inbox_settings',
featureFlag: MOCK_FEATURE_FLAGS.INBOX_MANAGEMENT,
},
{ path: 'accounts/:accountId/profile/settings', name: 'profile_settings' },
{ path: 'accounts/:accountId/notifications', name: 'notifications' },
{
path: 'accounts/:accountId/reports/overview',
name: 'reports_overview',
featureFlag: MOCK_FEATURE_FLAGS.REPORTS,
},
{
path: 'accounts/:accountId/settings/labels/list',
name: 'label_settings',
featureFlag: MOCK_FEATURE_FLAGS.LABELS,
},
{
path: 'accounts/:accountId/settings/canned-response/list',
name: 'canned_responses',
featureFlag: MOCK_FEATURE_FLAGS.CANNED_RESPONSES,
},
{
path: 'accounts/:accountId/settings/applications',
name: 'applications',
featureFlag: MOCK_FEATURE_FLAGS.INTEGRATIONS,
},
];
describe('useGoToCommandHotKeys', () => {
let store;
beforeEach(() => {
store = {
getters: {
getCurrentAccountId: 1,
'accounts/isFeatureEnabledonAccount': vi.fn().mockReturnValue(true),
},
};
useStore.mockReturnValue(store);
useMapGetter.mockImplementation(key => ({
value: store.getters[key],
}));
useI18n.mockReturnValue({ t: vi.fn(key => key) });
useRouter.mockReturnValue({ push: vi.fn() });
useAdmin.mockReturnValue({ isAdmin: { value: true } });
frontendURL.mockImplementation(url => url);
});
it('should return goToCommandHotKeys computed property', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
expect(goToCommandHotKeys.value).toBeDefined();
expect(goToCommandHotKeys.value.length).toBeGreaterThan(0);
});
it('should filter commands based on feature flags', () => {
store.getters['accounts/isFeatureEnabledonAccount'] = vi.fn(
(accountId, flag) => flag !== MOCK_FEATURE_FLAGS.CRM
);
const { goToCommandHotKeys } = useGoToCommandHotKeys();
mockRoutes.forEach(route => {
const command = goToCommandHotKeys.value.find(cmd =>
cmd.id.includes(route.name)
);
if (route.featureFlag === MOCK_FEATURE_FLAGS.CRM) {
expect(command).toBeUndefined();
} else if (!route.featureFlag) {
expect(command).toBeDefined();
}
});
});
it('should filter commands for non-admin users', () => {
useAdmin.mockReturnValue({ isAdmin: { value: false } });
const { goToCommandHotKeys } = useGoToCommandHotKeys();
const adminOnlyCommands = goToCommandHotKeys.value.filter(
cmd =>
cmd.id.includes('agent_settings') ||
cmd.id.includes('team_settings') ||
cmd.id.includes('inbox_settings')
);
expect(adminOnlyCommands.length).toBe(0);
});
it('should include commands for both admin and agent roles when user is admin', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
const adminCommand = goToCommandHotKeys.value.find(cmd =>
cmd.id.includes('agent_settings')
);
const agentCommand = goToCommandHotKeys.value.find(cmd =>
cmd.id.includes('profile_settings')
);
expect(adminCommand).toBeDefined();
expect(agentCommand).toBeDefined();
});
it('should translate section and title for each command', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
goToCommandHotKeys.value.forEach(command => {
expect(useI18n().t).toHaveBeenCalledWith(
expect.stringContaining('COMMAND_BAR.SECTIONS.')
);
expect(useI18n().t).toHaveBeenCalledWith(
expect.stringContaining('COMMAND_BAR.COMMANDS.')
);
expect(command.section).toBeDefined();
expect(command.title).toBeDefined();
});
});
it('should call router.push with correct URL when handler is called', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
goToCommandHotKeys.value.forEach(command => {
command.handler();
expect(useRouter().push).toHaveBeenCalledWith(expect.any(String));
});
});
it('should use current account ID in the path', () => {
store.getters.getCurrentAccountId = 42;
const { goToCommandHotKeys } = useGoToCommandHotKeys();
goToCommandHotKeys.value.forEach(command => {
command.handler();
expect(useRouter().push).toHaveBeenCalledWith(
expect.stringContaining('42')
);
});
});
it('should include icon for each command', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
goToCommandHotKeys.value.forEach(command => {
expect(command.icon).toBeDefined();
});
});
it('should return commands for all enabled features', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
const enabledFeatureCommands = goToCommandHotKeys.value.filter(cmd =>
mockRoutes.some(route => route.featureFlag && cmd.id.includes(route.name))
);
expect(enabledFeatureCommands.length).toBeGreaterThan(0);
});
it('should not return commands for disabled features', () => {
store.getters['accounts/isFeatureEnabledonAccount'] = vi.fn(() => false);
const { goToCommandHotKeys } = useGoToCommandHotKeys();
const disabledFeatureCommands = goToCommandHotKeys.value.filter(cmd =>
mockRoutes.some(route => route.featureFlag && cmd.id.includes(route.name))
);
expect(disabledFeatureCommands.length).toBe(0);
});
});

View File

@@ -0,0 +1,37 @@
import { useInboxHotKeys } from '../useInboxHotKeys';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRoute } from 'dashboard/composables/route';
import { isAInboxViewRoute } from 'dashboard/helper/routeHelpers';
vi.mock('dashboard/composables/useI18n');
vi.mock('dashboard/composables/route');
vi.mock('dashboard/helper/routeHelpers');
vi.mock('shared/helpers/mitt');
describe('useInboxHotKeys', () => {
beforeEach(() => {
useI18n.mockReturnValue({ t: vi.fn(key => key) });
useRoute.mockReturnValue({ name: 'inbox_dashboard' });
isAInboxViewRoute.mockReturnValue(true);
});
it('should return inbox hot keys when on an inbox view route', () => {
const { inboxHotKeys } = useInboxHotKeys();
expect(inboxHotKeys.value.length).toBeGreaterThan(0);
expect(inboxHotKeys.value[0].id).toBe('snooze_notification');
});
it('should return an empty array when not on an inbox view route', () => {
isAInboxViewRoute.mockReturnValue(false);
const { inboxHotKeys } = useInboxHotKeys();
expect(inboxHotKeys.value).toEqual([]);
});
it('should have the correct structure for snooze actions', () => {
const { inboxHotKeys } = useInboxHotKeys();
const snoozeNotificationAction = inboxHotKeys.value.find(
action => action.id === 'snooze_notification'
);
expect(snoozeNotificationAction).toBeDefined();
});
});

View File

@@ -0,0 +1,70 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import {
ICON_APPEARANCE,
ICON_LIGHT_MODE,
ICON_DARK_MODE,
ICON_SYSTEM_MODE,
} from 'dashboard/helper/commandbar/icons';
import { LocalStorage } from 'shared/helpers/localStorage';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { setColorTheme } from 'dashboard/helper/themeHelper.js';
const getThemeOptions = t => [
{
key: 'light',
label: t('COMMAND_BAR.COMMANDS.LIGHT_MODE'),
icon: ICON_LIGHT_MODE,
},
{
key: 'dark',
label: t('COMMAND_BAR.COMMANDS.DARK_MODE'),
icon: ICON_DARK_MODE,
},
{
key: 'auto',
label: t('COMMAND_BAR.COMMANDS.SYSTEM_MODE'),
icon: ICON_SYSTEM_MODE,
},
];
const setAppearance = theme => {
LocalStorage.set(LOCAL_STORAGE_KEYS.COLOR_SCHEME, theme);
const isOSOnDarkMode = window.matchMedia(
'(prefers-color-scheme: dark)'
).matches;
setColorTheme(isOSOnDarkMode);
};
export function useAppearanceHotKeys() {
const { t } = useI18n();
const themeOptions = computed(() => getThemeOptions(t));
const goToAppearanceHotKeys = computed(() => {
const options = themeOptions.value.map(theme => ({
id: theme.key,
title: theme.label,
parent: 'appearance_settings',
section: t('COMMAND_BAR.SECTIONS.APPEARANCE'),
icon: theme.icon,
handler: () => {
setAppearance(theme.key);
},
}));
return [
{
id: 'appearance_settings',
title: t('COMMAND_BAR.COMMANDS.CHANGE_APPEARANCE'),
section: t('COMMAND_BAR.SECTIONS.APPEARANCE'),
icon: ICON_APPEARANCE,
children: options.map(option => option.id),
},
...options,
];
});
return {
goToAppearanceHotKeys,
};
}

View File

@@ -0,0 +1,89 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useMapGetter } from 'dashboard/composables/store';
import wootConstants from 'dashboard/constants/globals';
import {
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
CMD_BULK_ACTION_REOPEN_CONVERSATION,
CMD_BULK_ACTION_RESOLVE_CONVERSATION,
} from 'dashboard/helper/commandbar/events';
import {
ICON_SNOOZE_CONVERSATION,
ICON_REOPEN_CONVERSATION,
ICON_RESOLVE_CONVERSATION,
} from 'dashboard/helper/commandbar/icons';
import { emitter } from 'shared/helpers/mitt';
import { createSnoozeHandlers } from 'dashboard/helper/commandbar/actions';
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
const createEmitHandler = event => () => emitter.emit(event);
const SNOOZE_CONVERSATION_BULK_ACTIONS = [
{
id: 'bulk_action_snooze_conversation',
title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_SNOOZE_CONVERSATION,
children: Object.values(SNOOZE_OPTIONS),
},
...createSnoozeHandlers(
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
'bulk_action_snooze_conversation',
'COMMAND_BAR.SECTIONS.BULK_ACTIONS'
),
];
const RESOLVED_CONVERSATION_BULK_ACTIONS = [
{
id: 'bulk_action_reopen_conversation',
title: 'COMMAND_BAR.COMMANDS.REOPEN_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_REOPEN_CONVERSATION,
handler: createEmitHandler(CMD_BULK_ACTION_REOPEN_CONVERSATION),
},
];
const OPEN_CONVERSATION_BULK_ACTIONS = [
{
id: 'bulk_action_resolve_conversation',
title: 'COMMAND_BAR.COMMANDS.RESOLVE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_RESOLVE_CONVERSATION,
handler: createEmitHandler(CMD_BULK_ACTION_RESOLVE_CONVERSATION),
},
];
export function useBulkActionsHotKeys() {
const { t } = useI18n();
const selectedConversations = useMapGetter(
'bulkActions/getSelectedConversationIds'
);
const prepareActions = actions => {
return actions.map(action => ({
...action,
title: t(action.title),
section: t(action.section),
}));
};
const bulkActionsHotKeys = computed(() => {
let actions = [];
if (selectedConversations.value.length > 0) {
actions = [
...SNOOZE_CONVERSATION_BULK_ACTIONS,
...RESOLVED_CONVERSATION_BULK_ACTIONS,
...OPEN_CONVERSATION_BULK_ACTIONS,
];
}
return prepareActions(actions);
});
return {
bulkActionsHotKeys,
};
}

View File

@@ -0,0 +1,408 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useRoute } from 'dashboard/composables/route';
import { emitter } from 'shared/helpers/mitt';
import { useConversationLabels } from 'dashboard/composables/useConversationLabels';
import { useAI } from 'dashboard/composables/useAI';
import { useAgentsList } from 'dashboard/composables/useAgentsList';
import { CMD_AI_ASSIST } from 'dashboard/helper/commandbar/events';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
import wootConstants from 'dashboard/constants/globals';
import {
ICON_ADD_LABEL,
ICON_ASSIGN_AGENT,
ICON_ASSIGN_PRIORITY,
ICON_ASSIGN_TEAM,
ICON_REMOVE_LABEL,
ICON_PRIORITY_URGENT,
ICON_PRIORITY_HIGH,
ICON_PRIORITY_LOW,
ICON_PRIORITY_MEDIUM,
ICON_PRIORITY_NONE,
ICON_AI_ASSIST,
ICON_AI_SUMMARY,
ICON_AI_SHORTEN,
ICON_AI_EXPAND,
ICON_AI_GRAMMAR,
} from 'dashboard/helper/commandbar/icons';
import {
OPEN_CONVERSATION_ACTIONS,
SNOOZE_CONVERSATION_ACTIONS,
RESOLVED_CONVERSATION_ACTIONS,
SEND_TRANSCRIPT_ACTION,
UNMUTE_ACTION,
MUTE_ACTION,
} from 'dashboard/helper/commandbar/actions';
import {
isAConversationRoute,
isAInboxViewRoute,
} from 'dashboard/helper/routeHelpers';
const prepareActions = (actions, t) => {
return actions.map(action => ({
...action,
title: t(action.title),
section: t(action.section),
}));
};
const createPriorityOptions = (t, currentPriority) => {
return [
{
label: t('CONVERSATION.PRIORITY.OPTIONS.NONE'),
key: null,
icon: ICON_PRIORITY_NONE,
},
{
label: t('CONVERSATION.PRIORITY.OPTIONS.URGENT'),
key: 'urgent',
icon: ICON_PRIORITY_URGENT,
},
{
label: t('CONVERSATION.PRIORITY.OPTIONS.HIGH'),
key: 'high',
icon: ICON_PRIORITY_HIGH,
},
{
label: t('CONVERSATION.PRIORITY.OPTIONS.MEDIUM'),
key: 'medium',
icon: ICON_PRIORITY_MEDIUM,
},
{
label: t('CONVERSATION.PRIORITY.OPTIONS.LOW'),
key: 'low',
icon: ICON_PRIORITY_LOW,
},
].filter(item => item.key !== currentPriority);
};
const createNonDraftMessageAIAssistActions = (t, replyMode) => {
if (replyMode === REPLY_EDITOR_MODES.REPLY) {
return [
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.REPLY_SUGGESTION'),
key: 'reply_suggestion',
icon: ICON_AI_ASSIST,
},
];
}
return [
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SUMMARIZE'),
key: 'summarize',
icon: ICON_AI_SUMMARY,
},
];
};
const createDraftMessageAIAssistActions = t => {
return [
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.REPHRASE'),
key: 'rephrase',
icon: ICON_AI_ASSIST,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.FIX_SPELLING_GRAMMAR'),
key: 'fix_spelling_grammar',
icon: ICON_AI_GRAMMAR,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.EXPAND'),
key: 'expand',
icon: ICON_AI_EXPAND,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SHORTEN'),
key: 'shorten',
icon: ICON_AI_SHORTEN,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.MAKE_FRIENDLY'),
key: 'make_friendly',
icon: ICON_AI_ASSIST,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.MAKE_FORMAL'),
key: 'make_formal',
icon: ICON_AI_ASSIST,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SIMPLIFY'),
key: 'simplify',
icon: ICON_AI_ASSIST,
},
];
};
export function useConversationHotKeys() {
const { t } = useI18n();
const store = useStore();
const route = useRoute();
const {
activeLabels,
inactiveLabels,
addLabelToConversation,
removeLabelFromConversation,
} = useConversationLabels();
const { isAIIntegrationEnabled } = useAI();
const { agentsList } = useAgentsList();
const currentChat = useMapGetter('getSelectedChat');
const replyMode = useMapGetter('draftMessages/getReplyEditorMode');
const contextMenuChatId = useMapGetter('getContextMenuChatId');
const teams = useMapGetter('teams/getTeams');
const getDraftMessage = useMapGetter('draftMessages/get');
const conversationId = computed(() => currentChat.value?.id);
const draftKey = computed(
() => `draft-${conversationId.value}-${replyMode.value}`
);
const draftMessage = computed(() => getDraftMessage.value(draftKey.value));
const hasAnAssignedTeam = computed(() => !!currentChat.value?.meta?.team);
const teamsList = computed(() => {
if (hasAnAssignedTeam.value) {
return [{ id: 0, name: t('TEAMS_SETTINGS.LIST.NONE') }, ...teams.value];
}
return teams.value;
});
const onChangeAssignee = action => {
store.dispatch('assignAgent', {
conversationId: currentChat.value.id,
agentId: action.agentInfo.id,
});
};
const onChangePriority = action => {
store.dispatch('assignPriority', {
conversationId: currentChat.value.id,
priority: action.priority.key,
});
};
const onChangeTeam = action => {
store.dispatch('assignTeam', {
conversationId: currentChat.value.id,
teamId: action.teamInfo.id,
});
};
const statusActions = computed(() => {
const isOpen = currentChat.value?.status === wootConstants.STATUS_TYPE.OPEN;
const isSnoozed =
currentChat.value?.status === wootConstants.STATUS_TYPE.SNOOZED;
const isResolved =
currentChat.value?.status === wootConstants.STATUS_TYPE.RESOLVED;
let actions = [];
if (isOpen) {
actions = [...OPEN_CONVERSATION_ACTIONS, ...SNOOZE_CONVERSATION_ACTIONS];
} else if (isResolved || isSnoozed) {
actions = RESOLVED_CONVERSATION_ACTIONS;
}
return prepareActions(actions, t);
});
const priorityOptions = computed(() =>
createPriorityOptions(t, currentChat.value?.priority)
);
const assignAgentActions = computed(() => {
const agentOptions = agentsList.value.map(agent => ({
id: `agent-${agent.id}`,
title: agent.name,
parent: 'assign_an_agent',
section: t('COMMAND_BAR.SECTIONS.CHANGE_ASSIGNEE'),
agentInfo: agent,
icon: ICON_ASSIGN_AGENT,
handler: onChangeAssignee,
}));
return [
{
id: 'assign_an_agent',
title: t('COMMAND_BAR.COMMANDS.ASSIGN_AN_AGENT'),
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ASSIGN_AGENT,
children: agentOptions.map(option => option.id),
},
...agentOptions,
];
});
const assignPriorityActions = computed(() => {
const options = priorityOptions.value.map(priority => ({
id: `priority-${priority.key}`,
title: priority.label,
parent: 'assign_priority',
section: t('COMMAND_BAR.SECTIONS.CHANGE_PRIORITY'),
priority: priority,
icon: priority.icon,
handler: onChangePriority,
}));
return [
{
id: 'assign_priority',
title: t('COMMAND_BAR.COMMANDS.ASSIGN_PRIORITY'),
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ASSIGN_PRIORITY,
children: options.map(option => option.id),
},
...options,
];
});
const assignTeamActions = computed(() => {
const teamOptions = teamsList.value.map(team => ({
id: `team-${team.id}`,
title: team.name,
parent: 'assign_a_team',
section: t('COMMAND_BAR.SECTIONS.CHANGE_TEAM'),
teamInfo: team,
icon: ICON_ASSIGN_TEAM,
handler: onChangeTeam,
}));
return [
{
id: 'assign_a_team',
title: t('COMMAND_BAR.COMMANDS.ASSIGN_A_TEAM'),
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ASSIGN_TEAM,
children: teamOptions.map(option => option.id),
},
...teamOptions,
];
});
const addLabelActions = computed(() => {
const availableLabels = inactiveLabels.value.map(label => ({
id: label.title,
title: `#${label.title}`,
parent: 'add_a_label_to_the_conversation',
section: t('COMMAND_BAR.SECTIONS.ADD_LABEL'),
icon: ICON_ADD_LABEL,
handler: action => addLabelToConversation({ title: action.id }),
}));
return [
...availableLabels,
{
id: 'add_a_label_to_the_conversation',
title: t('COMMAND_BAR.COMMANDS.ADD_LABELS_TO_CONVERSATION'),
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ADD_LABEL,
children: inactiveLabels.value.map(label => label.title),
},
];
});
const removeLabelActions = computed(() => {
const activeLabelsComputed = activeLabels.value.map(label => ({
id: label.title,
title: `#${label.title}`,
parent: 'remove_a_label_to_the_conversation',
section: t('COMMAND_BAR.SECTIONS.REMOVE_LABEL'),
icon: ICON_REMOVE_LABEL,
handler: action => removeLabelFromConversation(action.id),
}));
return [
...activeLabelsComputed,
{
id: 'remove_a_label_to_the_conversation',
title: t('COMMAND_BAR.COMMANDS.REMOVE_LABEL_FROM_CONVERSATION'),
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_REMOVE_LABEL,
children: activeLabels.value.map(label => label.title),
},
];
});
const labelActions = computed(() => {
if (activeLabels.value.length) {
return [...addLabelActions.value, ...removeLabelActions.value];
}
return addLabelActions.value;
});
const conversationAdditionalActions = computed(() => {
return prepareActions(
[
currentChat.value.muted ? UNMUTE_ACTION : MUTE_ACTION,
SEND_TRANSCRIPT_ACTION,
],
t
);
});
const AIAssistActions = computed(() => {
const aiOptions = draftMessage.value
? createDraftMessageAIAssistActions(t)
: createNonDraftMessageAIAssistActions(t, replyMode.value);
const options = aiOptions.map(item => ({
id: `ai-assist-${item.key}`,
title: item.label,
parent: 'ai_assist',
section: t('COMMAND_BAR.SECTIONS.AI_ASSIST'),
priority: item,
icon: item.icon,
handler: () => emitter.emit(CMD_AI_ASSIST, item.key),
}));
return [
{
id: 'ai_assist',
title: t('COMMAND_BAR.COMMANDS.AI_ASSIST'),
section: t('COMMAND_BAR.SECTIONS.AI_ASSIST'),
icon: ICON_AI_ASSIST,
children: options.map(option => option.id),
},
...options,
];
});
const isConversationOrInboxRoute = computed(() => {
return isAConversationRoute(route.name) || isAInboxViewRoute(route.name);
});
const shouldShowSnoozeOption = computed(() => {
return (
isAConversationRoute(route.name, true, false) && contextMenuChatId.value
);
});
const getDefaultConversationHotKeys = computed(() => {
const defaultConversationHotKeys = [
...statusActions.value,
...conversationAdditionalActions.value,
...assignAgentActions.value,
...assignTeamActions.value,
...labelActions.value,
...assignPriorityActions.value,
];
if (isAIIntegrationEnabled.value) {
return [...defaultConversationHotKeys, ...AIAssistActions.value];
}
return defaultConversationHotKeys;
});
const conversationHotKeys = computed(() => {
if (shouldShowSnoozeOption.value) {
return prepareActions(SNOOZE_CONVERSATION_ACTIONS, t);
}
if (isConversationOrInboxRoute.value) {
return getDefaultConversationHotKeys.value;
}
return [];
});
return {
conversationHotKeys,
};
}

View File

@@ -0,0 +1,219 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useMapGetter } from 'dashboard/composables/store';
import { useRouter } from 'dashboard/composables/route';
import { useAdmin } from 'dashboard/composables/useAdmin';
import {
ICON_ACCOUNT_SETTINGS,
ICON_AGENT_REPORTS,
ICON_APPS,
ICON_CANNED_RESPONSE,
ICON_CONTACT_DASHBOARD,
ICON_CONVERSATION_DASHBOARD,
ICON_INBOXES,
ICON_INBOX_REPORTS,
ICON_LABELS,
ICON_LABEL_REPORTS,
ICON_NOTIFICATION,
ICON_REPORTS_OVERVIEW,
ICON_TEAM_REPORTS,
ICON_USER_PROFILE,
ICON_CONVERSATION_REPORTS,
} from 'dashboard/helper/commandbar/icons';
import { frontendURL } from 'dashboard/helper/URLHelper';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
const GO_TO_COMMANDS = [
{
id: 'goto_conversation_dashboard',
title: 'COMMAND_BAR.COMMANDS.GO_TO_CONVERSATION_DASHBOARD',
section: 'COMMAND_BAR.SECTIONS.GENERAL',
icon: ICON_CONVERSATION_DASHBOARD,
path: accountId => `accounts/${accountId}/dashboard`,
role: ['administrator', 'agent'],
},
{
id: 'goto_contacts_dashboard',
title: 'COMMAND_BAR.COMMANDS.GO_TO_CONTACTS_DASHBOARD',
section: 'COMMAND_BAR.SECTIONS.GENERAL',
featureFlag: FEATURE_FLAGS.CRM,
icon: ICON_CONTACT_DASHBOARD,
path: accountId => `accounts/${accountId}/contacts`,
role: ['administrator', 'agent'],
},
{
id: 'open_reports_overview',
section: 'COMMAND_BAR.SECTIONS.REPORTS',
title: 'COMMAND_BAR.COMMANDS.GO_TO_REPORTS_OVERVIEW',
featureFlag: FEATURE_FLAGS.REPORTS,
icon: ICON_REPORTS_OVERVIEW,
path: accountId => `accounts/${accountId}/reports/overview`,
role: ['administrator'],
},
{
id: 'open_conversation_reports',
section: 'COMMAND_BAR.SECTIONS.REPORTS',
title: 'COMMAND_BAR.COMMANDS.GO_TO_CONVERSATION_REPORTS',
featureFlag: FEATURE_FLAGS.REPORTS,
icon: ICON_CONVERSATION_REPORTS,
path: accountId => `accounts/${accountId}/reports/conversation`,
role: ['administrator'],
},
{
id: 'open_agent_reports',
section: 'COMMAND_BAR.SECTIONS.REPORTS',
title: 'COMMAND_BAR.COMMANDS.GO_TO_AGENT_REPORTS',
featureFlag: FEATURE_FLAGS.REPORTS,
icon: ICON_AGENT_REPORTS,
path: accountId => `accounts/${accountId}/reports/agent`,
role: ['administrator'],
},
{
id: 'open_label_reports',
section: 'COMMAND_BAR.SECTIONS.REPORTS',
title: 'COMMAND_BAR.COMMANDS.GO_TO_LABEL_REPORTS',
featureFlag: FEATURE_FLAGS.REPORTS,
icon: ICON_LABEL_REPORTS,
path: accountId => `accounts/${accountId}/reports/label`,
role: ['administrator'],
},
{
id: 'open_inbox_reports',
section: 'COMMAND_BAR.SECTIONS.REPORTS',
title: 'COMMAND_BAR.COMMANDS.GO_TO_INBOX_REPORTS',
featureFlag: FEATURE_FLAGS.REPORTS,
icon: ICON_INBOX_REPORTS,
path: accountId => `accounts/${accountId}/reports/inboxes`,
role: ['administrator'],
},
{
id: 'open_team_reports',
section: 'COMMAND_BAR.SECTIONS.REPORTS',
title: 'COMMAND_BAR.COMMANDS.GO_TO_TEAM_REPORTS',
featureFlag: FEATURE_FLAGS.REPORTS,
icon: ICON_TEAM_REPORTS,
path: accountId => `accounts/${accountId}/reports/teams`,
role: ['administrator'],
},
{
id: 'open_agent_settings',
section: 'COMMAND_BAR.SECTIONS.SETTINGS',
title: 'COMMAND_BAR.COMMANDS.GO_TO_SETTINGS_AGENTS',
featureFlag: FEATURE_FLAGS.AGENT_MANAGEMENT,
icon: ICON_AGENT_REPORTS,
path: accountId => `accounts/${accountId}/settings/agents/list`,
role: ['administrator'],
},
{
id: 'open_team_settings',
title: 'COMMAND_BAR.COMMANDS.GO_TO_SETTINGS_TEAMS',
featureFlag: FEATURE_FLAGS.TEAM_MANAGEMENT,
section: 'COMMAND_BAR.SECTIONS.SETTINGS',
icon: ICON_TEAM_REPORTS,
path: accountId => `accounts/${accountId}/settings/teams/list`,
role: ['administrator'],
},
{
id: 'open_inbox_settings',
title: 'COMMAND_BAR.COMMANDS.GO_TO_SETTINGS_INBOXES',
featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT,
section: 'COMMAND_BAR.SECTIONS.SETTINGS',
icon: ICON_INBOXES,
path: accountId => `accounts/${accountId}/settings/inboxes/list`,
role: ['administrator'],
},
{
id: 'open_label_settings',
title: 'COMMAND_BAR.COMMANDS.GO_TO_SETTINGS_LABELS',
featureFlag: FEATURE_FLAGS.LABELS,
section: 'COMMAND_BAR.SECTIONS.SETTINGS',
icon: ICON_LABELS,
path: accountId => `accounts/${accountId}/settings/labels/list`,
role: ['administrator'],
},
{
id: 'open_canned_response_settings',
title: 'COMMAND_BAR.COMMANDS.GO_TO_SETTINGS_CANNED_RESPONSES',
featureFlag: FEATURE_FLAGS.CANNED_RESPONSES,
section: 'COMMAND_BAR.SECTIONS.SETTINGS',
icon: ICON_CANNED_RESPONSE,
path: accountId => `accounts/${accountId}/settings/canned-response/list`,
role: ['administrator', 'agent'],
},
{
id: 'open_applications_settings',
title: 'COMMAND_BAR.COMMANDS.GO_TO_SETTINGS_APPLICATIONS',
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
section: 'COMMAND_BAR.SECTIONS.SETTINGS',
icon: ICON_APPS,
path: accountId => `accounts/${accountId}/settings/applications`,
role: ['administrator'],
},
{
id: 'open_account_settings',
title: 'COMMAND_BAR.COMMANDS.GO_TO_SETTINGS_ACCOUNT',
section: 'COMMAND_BAR.SECTIONS.SETTINGS',
icon: ICON_ACCOUNT_SETTINGS,
path: accountId => `accounts/${accountId}/settings/general`,
role: ['administrator'],
},
{
id: 'open_profile_settings',
title: 'COMMAND_BAR.COMMANDS.GO_TO_SETTINGS_PROFILE',
section: 'COMMAND_BAR.SECTIONS.SETTINGS',
icon: ICON_USER_PROFILE,
path: accountId => `accounts/${accountId}/profile/settings`,
role: ['administrator', 'agent'],
},
{
id: 'open_notifications',
title: 'COMMAND_BAR.COMMANDS.GO_TO_NOTIFICATIONS',
section: 'COMMAND_BAR.SECTIONS.SETTINGS',
icon: ICON_NOTIFICATION,
path: accountId => `accounts/${accountId}/notifications`,
role: ['administrator', 'agent'],
},
];
export function useGoToCommandHotKeys() {
const { t } = useI18n();
const router = useRouter();
const { isAdmin } = useAdmin();
const currentAccountId = useMapGetter('getCurrentAccountId');
const isFeatureEnabledOnAccount = useMapGetter(
'accounts/isFeatureEnabledonAccount'
);
const openRoute = url => {
router.push(frontendURL(url));
};
const goToCommandHotKeys = computed(() => {
let commands = GO_TO_COMMANDS.filter(cmd => {
if (cmd.featureFlag) {
return isFeatureEnabledOnAccount.value(
currentAccountId.value,
cmd.featureFlag
);
}
return true;
});
if (!isAdmin.value) {
commands = commands.filter(command => command.role.includes('agent'));
}
return commands.map(command => ({
id: command.id,
section: t(command.section),
title: t(command.title),
icon: command.icon,
handler: () => openRoute(command.path(currentAccountId.value)),
}));
});
return {
goToCommandHotKeys,
};
}

View File

@@ -0,0 +1,88 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRoute } from 'dashboard/composables/route';
import wootConstants from 'dashboard/constants/globals';
import { CMD_SNOOZE_NOTIFICATION } from 'dashboard/helper/commandbar/events';
import { ICON_SNOOZE_NOTIFICATION } from 'dashboard/helper/commandbar/icons';
import { emitter } from 'shared/helpers/mitt';
import { isAInboxViewRoute } from 'dashboard/helper/routeHelpers';
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
const createSnoozeHandler = option => () =>
emitter.emit(CMD_SNOOZE_NOTIFICATION, option);
const INBOX_SNOOZE_EVENTS = [
{
id: 'snooze_notification',
title: 'COMMAND_BAR.COMMANDS.SNOOZE_NOTIFICATION',
icon: ICON_SNOOZE_NOTIFICATION,
children: Object.values(SNOOZE_OPTIONS),
},
{
id: SNOOZE_OPTIONS.AN_HOUR_FROM_NOW,
title: 'COMMAND_BAR.COMMANDS.AN_HOUR_FROM_NOW',
parent: 'snooze_notification',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
icon: ICON_SNOOZE_NOTIFICATION,
handler: createSnoozeHandler(SNOOZE_OPTIONS.AN_HOUR_FROM_NOW),
},
{
id: SNOOZE_OPTIONS.UNTIL_TOMORROW,
title: 'COMMAND_BAR.COMMANDS.UNTIL_TOMORROW',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: createSnoozeHandler(SNOOZE_OPTIONS.UNTIL_TOMORROW),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_WEEK,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_WEEK',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: createSnoozeHandler(SNOOZE_OPTIONS.UNTIL_NEXT_WEEK),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_MONTH,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_MONTH',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: createSnoozeHandler(SNOOZE_OPTIONS.UNTIL_NEXT_MONTH),
},
{
id: SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME,
title: 'COMMAND_BAR.COMMANDS.UNTIL_CUSTOM_TIME',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: createSnoozeHandler(SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME),
},
];
export function useInboxHotKeys() {
const { t } = useI18n();
const route = useRoute();
const prepareActions = actions => {
return actions.map(action => ({
...action,
title: t(action.title),
section: action.section ? t(action.section) : undefined,
}));
};
const inboxHotKeys = computed(() => {
if (isAInboxViewRoute(route.name)) {
return prepareActions(INBOX_SNOOZE_EVENTS);
}
return [];
});
return {
inboxHotKeys,
};
}