diff --git a/app/controllers/devise_overrides/passwords_controller.rb b/app/controllers/devise_overrides/passwords_controller.rb index 501b9f90c..06092c5ab 100644 --- a/app/controllers/devise_overrides/passwords_controller.rb +++ b/app/controllers/devise_overrides/passwords_controller.rb @@ -11,7 +11,7 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController @recoverable = User.find_by(reset_password_token: reset_password_token) if @recoverable && reset_password_and_confirmation(@recoverable) send_auth_headers(@recoverable) - render partial: 'devise/auth.json', locals: { resource: @recoverable } + render partial: 'devise/auth', formats: [:json], locals: { resource: @recoverable } else render json: { message: 'Invalid token', redirect_url: '/' }, status: :unprocessable_entity end diff --git a/app/javascript/dashboard/api/agentBots.js b/app/javascript/dashboard/api/agentBots.js new file mode 100644 index 000000000..4de6fcee0 --- /dev/null +++ b/app/javascript/dashboard/api/agentBots.js @@ -0,0 +1,9 @@ +import ApiClient from './ApiClient'; + +class AgentBotsAPI extends ApiClient { + constructor() { + super('agent_bots', { accountScoped: true }); + } +} + +export default new AgentBotsAPI(); diff --git a/app/javascript/dashboard/api/macros.js b/app/javascript/dashboard/api/macros.js new file mode 100644 index 000000000..7b123c9e8 --- /dev/null +++ b/app/javascript/dashboard/api/macros.js @@ -0,0 +1,16 @@ +/* global axios */ +import ApiClient from './ApiClient'; + +class MacrosAPI extends ApiClient { + constructor() { + super('macros', { accountScoped: true }); + } + + executeMacro({ macroId, conversationIds }) { + return axios.post(`${this.url}/${macroId}/execute`, { + conversation_ids: conversationIds, + }); + } +} + +export default new MacrosAPI(); diff --git a/app/javascript/dashboard/api/specs/agentBots.spec.js b/app/javascript/dashboard/api/specs/agentBots.spec.js new file mode 100644 index 000000000..c89dbfdf5 --- /dev/null +++ b/app/javascript/dashboard/api/specs/agentBots.spec.js @@ -0,0 +1,13 @@ +import AgentBotsAPI from '../agentBots'; +import ApiClient from '../ApiClient'; + +describe('#AgentBotsAPI', () => { + it('creates correct instance', () => { + expect(AgentBotsAPI).toBeInstanceOf(ApiClient); + expect(AgentBotsAPI).toHaveProperty('get'); + expect(AgentBotsAPI).toHaveProperty('show'); + expect(AgentBotsAPI).toHaveProperty('create'); + expect(AgentBotsAPI).toHaveProperty('update'); + expect(AgentBotsAPI).toHaveProperty('delete'); + }); +}); diff --git a/app/javascript/dashboard/api/specs/macros.spec.js b/app/javascript/dashboard/api/specs/macros.spec.js new file mode 100644 index 000000000..94e936521 --- /dev/null +++ b/app/javascript/dashboard/api/specs/macros.spec.js @@ -0,0 +1,14 @@ +import macros from '../macros'; +import ApiClient from '../ApiClient'; + +describe('#macrosAPI', () => { + it('creates correct instance', () => { + expect(macros).toBeInstanceOf(ApiClient); + expect(macros).toHaveProperty('get'); + expect(macros).toHaveProperty('create'); + expect(macros).toHaveProperty('update'); + expect(macros).toHaveProperty('delete'); + expect(macros).toHaveProperty('show'); + expect(macros.url).toBe('/api/v1/macros'); + }); +}); diff --git a/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js b/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js index 990b35d4a..83a0c2309 100644 --- a/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js +++ b/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js @@ -3,35 +3,39 @@ import { frontendURL } from '../../../../helper/URLHelper'; const settings = accountId => ({ parentNav: 'settings', routes: [ + 'agent_bots', 'agent_list', - 'canned_list', - 'labels_list', - 'settings_inbox', 'attributes_list', - 'settings_inbox_new', - 'settings_inbox_list', - 'settings_inbox_show', - 'settings_inboxes_page_channel', - 'settings_inboxes_add_agents', - 'settings_inbox_finish', - 'settings_integrations', - 'settings_integrations_webhook', - 'settings_integrations_integration', - 'settings_applications', - 'settings_integrations_dashboard_apps', - 'settings_applications_webhook', - 'settings_applications_integration', - 'general_settings', + 'automation_list', + 'billing_settings_index', + 'canned_list', 'general_settings_index', + 'general_settings', + 'labels_list', + 'macros_edit', + 'macros_new', + 'macros_wrapper', + 'settings_applications_integration', + 'settings_applications_webhook', + 'settings_applications', + 'settings_inbox_finish', + 'settings_inbox_list', + 'settings_inbox_new', + 'settings_inbox_show', + 'settings_inbox', + 'settings_inboxes_add_agents', + 'settings_inboxes_page_channel', + 'settings_integrations_dashboard_apps', + 'settings_integrations_integration', + 'settings_integrations_webhook', + 'settings_integrations', + 'settings_teams_add_agents', + 'settings_teams_edit_finish', + 'settings_teams_edit_members', + 'settings_teams_edit', + 'settings_teams_finish', 'settings_teams_list', 'settings_teams_new', - 'settings_teams_add_agents', - 'settings_teams_finish', - 'settings_teams_edit', - 'settings_teams_edit_members', - 'settings_teams_edit_finish', - 'billing_settings_index', - 'automation_list', ], menuItems: [ { @@ -74,10 +78,29 @@ const settings = accountId => ({ { icon: 'automation', label: 'AUTOMATION', + beta: true, hasSubMenu: false, toState: frontendURL(`accounts/${accountId}/settings/automation/list`), toStateName: 'automation_list', }, + { + icon: 'bot', + label: 'AGENT_BOTS', + beta: true, + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings/agent-bots`), + toStateName: 'agent_bots', + featureFlagKey: 'agent_bots', + }, + { + icon: 'flash-settings', + label: 'MACROS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings/macros`), + toStateName: 'macros_wrapper', + beta: true, + featureFlagKey: 'macros', + }, { icon: 'chat-multiple', label: 'CANNED_RESPONSES', diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue b/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue index 220ab18fa..a7a6761cf 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue +++ b/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue @@ -1,5 +1,5 @@ + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue new file mode 100644 index 000000000..2de1f787c --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue @@ -0,0 +1,9 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js new file mode 100644 index 000000000..9752b79fb --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js @@ -0,0 +1,38 @@ +import SettingsContent from '../Wrapper'; +import Macros from './Index'; +const MacroEditor = () => import('./MacroEditor'); +import { frontendURL } from 'dashboard/helper/URLHelper'; + +export default { + routes: [ + { + path: frontendURL('accounts/:accountId/settings/macros'), + component: SettingsContent, + props: { + headerTitle: 'MACROS.HEADER', + icon: 'flash-settings', + showNewButton: false, + }, + children: [ + { + path: '', + name: 'macros_wrapper', + component: Macros, + roles: ['administrator', 'agent'], + }, + { + path: 'new', + name: 'macros_new', + component: MacroEditor, + roles: ['administrator', 'agent'], + }, + { + path: ':macroId/edit', + name: 'macros_edit', + component: MacroEditor, + roles: ['administrator', 'agent'], + }, + ], + }, + ], +}; diff --git a/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js b/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js index 8f3de8f4e..7c8cc11fa 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js @@ -1,19 +1,21 @@ import { frontendURL } from '../../../helper/URLHelper'; import account from './account/account.routes'; import agent from './agents/agent.routes'; -import canned from './canned/canned.routes'; -import inbox from './inbox/inbox.routes'; -import integrations from './integrations/integrations.routes'; -import integrationapps from './integrationapps/integrations.routes'; -import labels from './labels/labels.routes'; -import profile from './profile/profile.routes'; -import reports from './reports/reports.routes'; -import campaigns from './campaigns/campaigns.routes'; -import teams from './teams/teams.routes'; +import agentBot from './agentBots/agentBot.routes'; import attributes from './attributes/attributes.routes'; import automation from './automation/automation.routes'; -import store from '../../../store'; import billing from './billing/billing.routes'; +import campaigns from './campaigns/campaigns.routes'; +import canned from './canned/canned.routes'; +import inbox from './inbox/inbox.routes'; +import integrationapps from './integrationapps/integrations.routes'; +import integrations from './integrations/integrations.routes'; +import labels from './labels/labels.routes'; +import macros from './macros/macros.routes'; +import profile from './profile/profile.routes'; +import reports from './reports/reports.routes'; +import store from '../../../store'; +import teams from './teams/teams.routes'; export default { routes: [ @@ -30,6 +32,7 @@ export default { }, ...account.routes, ...agent.routes, + ...agentBot.routes, ...attributes.routes, ...automation.routes, ...billing.routes, @@ -39,6 +42,7 @@ export default { ...integrationapps.routes, ...integrations.routes, ...labels.routes, + ...macros.routes, ...profile.routes, ...reports.routes, ...teams.routes, diff --git a/app/javascript/dashboard/store/index.js b/app/javascript/dashboard/store/index.js index 8ffbb2db4..f2fe95b50 100755 --- a/app/javascript/dashboard/store/index.js +++ b/app/javascript/dashboard/store/index.js @@ -3,6 +3,7 @@ import Vuex from 'vuex'; import accounts from './modules/accounts'; import agents from './modules/agents'; +import agentBots from './modules/agentBots'; import attributes from './modules/attributes'; import auth from './modules/auth'; import automations from './modules/automations'; @@ -44,6 +45,7 @@ export default new Vuex.Store({ modules: { accounts, agents, + agentBots, attributes, auth, automations, diff --git a/app/javascript/dashboard/store/modules/agentBots.js b/app/javascript/dashboard/store/modules/agentBots.js new file mode 100644 index 000000000..b5042b1aa --- /dev/null +++ b/app/javascript/dashboard/store/modules/agentBots.js @@ -0,0 +1,107 @@ +import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers'; +import types from '../mutation-types'; +import AgentBotsAPI from '../../api/agentBots'; +import { throwErrorMessage } from '../utils/api'; + +export const state = { + records: [], + uiFlags: { + isFetching: false, + isFetchingItem: false, + isCreating: false, + isDeleting: false, + isUpdating: false, + }, +}; + +export const getters = { + getBots($state) { + return $state.records; + }, + getUIFlags($state) { + return $state.uiFlags; + }, + getBot: $state => botId => { + const [bot] = $state.records.filter(record => record.id === Number(botId)); + return bot || {}; + }, +}; + +export const actions = { + get: async ({ commit }) => { + commit(types.SET_AGENT_BOT_UI_FLAG, { isFetching: true }); + try { + const response = await AgentBotsAPI.get(); + commit(types.SET_AGENT_BOTS, response.data); + } catch (error) { + // Ignore error + } finally { + commit(types.SET_AGENT_BOT_UI_FLAG, { isFetching: false }); + } + }, + create: async ({ commit }, agentBotObj) => { + commit(types.SET_AGENT_BOT_UI_FLAG, { isCreating: true }); + try { + const response = await AgentBotsAPI.create(agentBotObj); + commit(types.ADD_AGENT_BOT, response.data); + } catch (error) { + throwErrorMessage(error); + } finally { + commit(types.SET_AGENT_BOT_UI_FLAG, { isCreating: false }); + } + }, + update: async ({ commit }, { id, ...agentBotObj }) => { + commit(types.SET_AGENT_BOT_UI_FLAG, { isUpdating: true }); + try { + const response = await AgentBotsAPI.update(id, agentBotObj); + commit(types.EDIT_AGENT_BOT, response.data); + } catch (error) { + throwErrorMessage(error); + } finally { + commit(types.SET_AGENT_BOT_UI_FLAG, { isUpdating: false }); + } + }, + delete: async ({ commit }, id) => { + commit(types.SET_AGENT_BOT_UI_FLAG, { isDeleting: true }); + try { + await AgentBotsAPI.delete(id); + commit(types.DELETE_AGENT_BOT, id); + } catch (error) { + throwErrorMessage(error); + } finally { + commit(types.SET_AGENT_BOT_UI_FLAG, { isDeleting: false }); + } + }, + show: async ({ commit }, id) => { + commit(types.SET_AGENT_BOT_UI_FLAG, { isFetchingItem: true }); + try { + const { data } = await AgentBotsAPI.show(id); + commit(types.DELETE_AGENT_BOT, data); + } catch (error) { + throwErrorMessage(error); + } finally { + commit(types.SET_AGENT_BOT_UI_FLAG, { isFetchingItem: false }); + } + }, +}; + +export const mutations = { + [types.SET_AGENT_BOT_UI_FLAG]($state, data) { + $state.uiFlags = { + ...$state.uiFlags, + ...data, + }; + }, + [types.ADD_AGENT_BOT]: MutationHelpers.create, + [types.SET_AGENT_BOTS]: MutationHelpers.set, + [types.EDIT_AGENT_BOT]: MutationHelpers.update, + [types.DELETE_AGENT_BOT]: MutationHelpers.destroy, +}; + +export default { + namespaced: true, + actions, + state, + getters, + mutations, +}; diff --git a/app/javascript/dashboard/store/modules/macros.js b/app/javascript/dashboard/store/modules/macros.js new file mode 100644 index 000000000..952f53f17 --- /dev/null +++ b/app/javascript/dashboard/store/modules/macros.js @@ -0,0 +1,117 @@ +import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers'; +import types from '../mutation-types'; +import MacrosAPI from '../../api/macros'; +import { throwErrorMessage } from '../utils/api'; + +export const state = { + records: [], + uiFlags: { + isFetchingItem: false, + isFetching: false, + isCreating: false, + isDeleting: false, + isUpdating: false, + isExecuting: false, + }, +}; + +export const getters = { + getMacros($state) { + return $state.records; + }, + getMacro: $state => id => { + return $state.records.find(record => record.id === Number(id)); + }, + getUIFlags($state) { + return $state.uiFlags; + }, +}; + +export const actions = { + get: async function getMacros({ commit }) { + commit(types.SET_MACROS_UI_FLAG, { isFetching: true }); + try { + const response = await MacrosAPI.get(); + commit(types.SET_MACROS, response.data.payload); + } catch (error) { + // Ignore error + } finally { + commit(types.SET_MACROS_UI_FLAG, { isFetching: false }); + } + }, + getSingleMacro: async function getMacroById({ commit }, macroId) { + commit(types.SET_MACROS_UI_FLAG, { isFetchingItem: true }); + try { + const response = await MacrosAPI.show(macroId); + commit(types.ADD_MACRO, response.data.payload); + } catch (error) { + // Ignore error + } finally { + commit(types.SET_MACROS_UI_FLAG, { isFetchingItem: false }); + } + }, + create: async function createMacro({ commit }, macrosObj) { + commit(types.SET_MACROS_UI_FLAG, { isCreating: true }); + try { + const response = await MacrosAPI.create(macrosObj); + commit(types.ADD_MACRO, response.data.payload); + } catch (error) { + throwErrorMessage(error); + } finally { + commit(types.SET_MACROS_UI_FLAG, { isCreating: false }); + } + }, + execute: async function executeMacro({ commit }, macrosObj) { + commit(types.SET_MACROS_UI_FLAG, { isExecuting: true }); + try { + await MacrosAPI.executeMacro(macrosObj); + } catch (error) { + throwErrorMessage(error); + } finally { + commit(types.SET_MACROS_UI_FLAG, { isExecuting: false }); + } + }, + update: async ({ commit }, { id, ...updateObj }) => { + commit(types.SET_MACROS_UI_FLAG, { isUpdating: true }); + try { + const response = await MacrosAPI.update(id, updateObj); + commit(types.EDIT_MACRO, response.data.payload); + } catch (error) { + throwErrorMessage(error); + } finally { + commit(types.SET_MACROS_UI_FLAG, { isUpdating: false }); + } + }, + delete: async ({ commit }, id) => { + commit(types.SET_MACROS_UI_FLAG, { isDeleting: true }); + try { + await MacrosAPI.delete(id); + commit(types.DELETE_MACRO, id); + } catch (error) { + throwErrorMessage(error); + } finally { + commit(types.SET_MACROS_UI_FLAG, { isDeleting: false }); + } + }, +}; + +export const mutations = { + [types.SET_MACROS_UI_FLAG]($state, data) { + $state.uiFlags = { + ...$state.uiFlags, + ...data, + }; + }, + [types.ADD_MACRO]: MutationHelpers.setSingleRecord, + [types.SET_MACROS]: MutationHelpers.set, + [types.EDIT_MACRO]: MutationHelpers.update, + [types.DELETE_MACRO]: MutationHelpers.destroy, +}; + +export default { + namespaced: true, + actions, + state, + getters, + mutations, +}; diff --git a/app/javascript/dashboard/store/modules/specs/agentBots/actions.spec.js b/app/javascript/dashboard/store/modules/specs/agentBots/actions.spec.js new file mode 100644 index 000000000..e5a51468e --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/agentBots/actions.spec.js @@ -0,0 +1,93 @@ +import axios from 'axios'; +import { actions } from '../../agentBots'; +import types from '../../../mutation-types'; +import { agentBotRecords } from './fixtures'; + +const commit = jest.fn(); +global.axios = axios; +jest.mock('axios'); + +describe('#actions', () => { + describe('#get', () => { + it('sends correct actions if API is success', async () => { + axios.get.mockResolvedValue({ data: agentBotRecords }); + await actions.get({ commit }); + expect(commit.mock.calls).toEqual([ + [types.SET_AGENT_BOT_UI_FLAG, { isFetching: true }], + [types.SET_AGENT_BOTS, agentBotRecords], + [types.SET_AGENT_BOT_UI_FLAG, { isFetching: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.get.mockRejectedValue({ message: 'Incorrect header' }); + await actions.get({ commit }); + expect(commit.mock.calls).toEqual([ + [types.SET_AGENT_BOT_UI_FLAG, { isFetching: true }], + [types.SET_AGENT_BOT_UI_FLAG, { isFetching: false }], + ]); + }); + }); + describe('#create', () => { + it('sends correct actions if API is success', async () => { + axios.post.mockResolvedValue({ data: agentBotRecords[0] }); + await actions.create({ commit }, agentBotRecords[0]); + expect(commit.mock.calls).toEqual([ + [types.SET_AGENT_BOT_UI_FLAG, { isCreating: true }], + [types.ADD_AGENT_BOT, agentBotRecords[0]], + [types.SET_AGENT_BOT_UI_FLAG, { isCreating: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.post.mockRejectedValue({ message: 'Incorrect header' }); + await expect(actions.create({ commit })).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.SET_AGENT_BOT_UI_FLAG, { isCreating: true }], + [types.SET_AGENT_BOT_UI_FLAG, { isCreating: false }], + ]); + }); + }); + + describe('#update', () => { + it('sends correct actions if API is success', async () => { + axios.patch.mockResolvedValue({ data: agentBotRecords[0] }); + await actions.update({ commit }, agentBotRecords[0]); + expect(commit.mock.calls).toEqual([ + [types.SET_AGENT_BOT_UI_FLAG, { isUpdating: true }], + [types.EDIT_AGENT_BOT, agentBotRecords[0]], + [types.SET_AGENT_BOT_UI_FLAG, { isUpdating: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.patch.mockRejectedValue({ message: 'Incorrect header' }); + await expect( + actions.update({ commit }, agentBotRecords[0]) + ).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.SET_AGENT_BOT_UI_FLAG, { isUpdating: true }], + [types.SET_AGENT_BOT_UI_FLAG, { isUpdating: false }], + ]); + }); + }); + + describe('#delete', () => { + it('sends correct actions if API is success', async () => { + axios.delete.mockResolvedValue({ data: agentBotRecords[0] }); + await actions.delete({ commit }, agentBotRecords[0].id); + expect(commit.mock.calls).toEqual([ + [types.SET_AGENT_BOT_UI_FLAG, { isDeleting: true }], + [types.DELETE_AGENT_BOT, agentBotRecords[0].id], + [types.SET_AGENT_BOT_UI_FLAG, { isDeleting: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.delete.mockRejectedValue({ message: 'Incorrect header' }); + await expect( + actions.delete({ commit }, agentBotRecords[0].id) + ).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.SET_AGENT_BOT_UI_FLAG, { isDeleting: true }], + [types.SET_AGENT_BOT_UI_FLAG, { isDeleting: false }], + ]); + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/specs/agentBots/fixtures.js b/app/javascript/dashboard/store/modules/specs/agentBots/fixtures.js new file mode 100644 index 000000000..e13735b14 --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/agentBots/fixtures.js @@ -0,0 +1,15 @@ +export const agentBotRecords = [ + { + id: 11, + name: 'Agent Bot 11', + description: 'Agent Bot Description', + type: 'csml', + }, + + { + id: 12, + name: 'Agent Bot 12', + description: 'Agent Bot Description 12', + type: 'csml', + }, +]; diff --git a/app/javascript/dashboard/store/modules/specs/agentBots/getters.spec.js b/app/javascript/dashboard/store/modules/specs/agentBots/getters.spec.js new file mode 100644 index 000000000..bff536b56 --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/agentBots/getters.spec.js @@ -0,0 +1,31 @@ +import { getters } from '../../agentBots'; +import { agentBotRecords } from './fixtures'; + +describe('#getters', () => { + it('getBots', () => { + const state = { records: agentBotRecords }; + expect(getters.getBots(state)).toEqual(agentBotRecords); + }); + + it('getBot', () => { + const state = { records: agentBotRecords }; + expect(getters.getBot(state)(11)).toEqual(agentBotRecords[0]); + }); + + it('getUIFlags', () => { + const state = { + uiFlags: { + isFetching: true, + isCreating: false, + isUpdating: false, + isDeleting: false, + }, + }; + expect(getters.getUIFlags(state)).toEqual({ + isFetching: true, + isCreating: false, + isUpdating: false, + isDeleting: false, + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/specs/agentBots/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/agentBots/mutations.spec.js new file mode 100644 index 000000000..d76c98404 --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/agentBots/mutations.spec.js @@ -0,0 +1,44 @@ +import types from '../../../mutation-types'; +import { mutations } from '../../agentBots'; +import { agentBotRecords } from './fixtures'; + +describe('#mutations', () => { + describe('#SET_AGENT_BOT_UI_FLAG', () => { + it('set uiFlags', () => { + const state = { uiFlags: { isFetchingItem: false } }; + mutations[types.SET_AGENT_BOT_UI_FLAG](state, { isFetchingItem: true }); + expect(state.uiFlags.isFetchingItem).toEqual(true); + }); + }); + describe('#SET_AGENT_BOTS', () => { + it('set agent bot records', () => { + const state = { records: [] }; + mutations[types.SET_AGENT_BOTS](state, agentBotRecords); + expect(state.records).toEqual(agentBotRecords); + }); + }); + describe('#ADD_AGENT_BOT', () => { + it('push newly created bot to the store', () => { + const state = { records: [agentBotRecords[0]] }; + mutations[types.ADD_AGENT_BOT](state, agentBotRecords[1]); + expect(state.records).toEqual([agentBotRecords[0], agentBotRecords[1]]); + }); + }); + describe('#EDIT_AGENT_BOT', () => { + it('update agent bot record', () => { + const state = { records: [agentBotRecords[0]] }; + mutations[types.EDIT_AGENT_BOT](state, { + id: 11, + name: 'agent-bot-11', + }); + expect(state.records[0].name).toEqual('agent-bot-11'); + }); + }); + describe('#DELETE_AGENT_BOT', () => { + it('delete agent bot record', () => { + const state = { records: [agentBotRecords[0]] }; + mutations[types.DELETE_AGENT_BOT](state, agentBotRecords[0]); + expect(state.records).toEqual([agentBotRecords[0]]); + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/specs/macros/actions.spec.js b/app/javascript/dashboard/store/modules/specs/macros/actions.spec.js new file mode 100644 index 000000000..95bba8e1d --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/macros/actions.spec.js @@ -0,0 +1,151 @@ +import axios from 'axios'; +import { actions } from '../../macros'; +import * as types from '../../../mutation-types'; +import macrosList from './fixtures'; + +const commit = jest.fn(); +global.axios = axios; +jest.mock('axios'); + +describe('#actions', () => { + describe('#get', () => { + it('sends correct actions if API is success', async () => { + axios.get.mockResolvedValue({ data: { payload: macrosList } }); + await actions.get({ commit }); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isFetching: true }], + [types.default.SET_MACROS, macrosList], + [types.default.SET_MACROS_UI_FLAG, { isFetching: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.get.mockRejectedValue({ message: 'Incorrect header' }); + await actions.get({ commit }); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isFetching: true }], + [types.default.SET_MACROS_UI_FLAG, { isFetching: false }], + ]); + }); + }); + + describe('#getMacroById', () => { + it('sends correct actions if API is success', async () => { + axios.get.mockResolvedValue({ data: { payload: macrosList[0] } }); + await actions.getSingleMacro({ commit }, 22); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isFetchingItem: true }], + [types.default.ADD_MACRO, macrosList[0]], + [types.default.SET_MACROS_UI_FLAG, { isFetchingItem: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.get.mockRejectedValue({ message: 'Incorrect header' }); + await actions.getSingleMacro({ commit }, 22); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isFetchingItem: true }], + [types.default.SET_MACROS_UI_FLAG, { isFetchingItem: false }], + ]); + }); + }); + + describe('#create', () => { + it('sends correct actions if API is success', async () => { + axios.post.mockResolvedValue({ data: { payload: macrosList[0] } }); + await actions.create({ commit }, macrosList[0]); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isCreating: true }], + [types.default.ADD_MACRO, macrosList[0]], + [types.default.SET_MACROS_UI_FLAG, { isCreating: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.post.mockRejectedValue({ message: 'Incorrect header' }); + await expect(actions.create({ commit })).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isCreating: true }], + [types.default.SET_MACROS_UI_FLAG, { isCreating: false }], + ]); + }); + }); + + describe('#execute', () => { + const macroId = 12; + const conversationIds = [1]; + it('sends correct actions if API is success', async () => { + axios.post.mockResolvedValue({ data: null }); + await actions.execute( + { commit }, + { + macroId, + conversationIds, + } + ); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isExecuting: true }], + [types.default.SET_MACROS_UI_FLAG, { isExecuting: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.post.mockRejectedValue({ message: 'Incorrect header' }); + await expect( + actions.execute( + { commit }, + { + macroId, + conversationIds, + } + ) + ).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isExecuting: true }], + [types.default.SET_MACROS_UI_FLAG, { isExecuting: false }], + ]); + }); + }); + + describe('#update', () => { + it('sends correct actions if API is success', async () => { + axios.patch.mockResolvedValue({ + data: { payload: macrosList[0] }, + }); + await actions.update({ commit }, macrosList[0]); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isUpdating: true }], + [types.default.EDIT_MACRO, macrosList[0]], + [types.default.SET_MACROS_UI_FLAG, { isUpdating: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.patch.mockRejectedValue({ message: 'Incorrect header' }); + await expect(actions.update({ commit }, macrosList[0])).rejects.toThrow( + Error + ); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isUpdating: true }], + [types.default.SET_MACROS_UI_FLAG, { isUpdating: false }], + ]); + }); + }); + + describe('#delete', () => { + it('sends correct actions if API is success', async () => { + axios.delete.mockResolvedValue({ data: macrosList[0] }); + await actions.delete({ commit }, macrosList[0].id); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isDeleting: true }], + [types.default.DELETE_MACRO, macrosList[0].id], + [types.default.SET_MACROS_UI_FLAG, { isDeleting: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.delete.mockRejectedValue({ message: 'Incorrect header' }); + await expect( + actions.delete({ commit }, macrosList[0].id) + ).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.default.SET_MACROS_UI_FLAG, { isDeleting: true }], + [types.default.SET_MACROS_UI_FLAG, { isDeleting: false }], + ]); + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/specs/macros/fixtures.js b/app/javascript/dashboard/store/modules/specs/macros/fixtures.js new file mode 100644 index 000000000..b75bd2836 --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/macros/fixtures.js @@ -0,0 +1,135 @@ +export default [ + { + id: 22, + name: 'Assign billing label and sales team and message user', + visibility: 'global', + created_by: { + id: 1, + account_id: 1, + availability_status: 'online', + auto_offline: true, + confirmed: true, + email: 'john@acme.inc', + available_name: 'Fayaz Ahmed', + name: 'Fayaz Ahmed', + role: 'administrator', + thumbnail: + 'http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBUUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--16c85844c93f9c139deb782137b49c87c9bc871c/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2QzNKbGMybDZaVWtpRERJMU1IZ3lOVEFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e0e35266e8ed66e90c51be02408be8a022aca545/memoji.png', + }, + updated_by: { + id: 1, + account_id: 1, + availability_status: 'online', + auto_offline: true, + confirmed: true, + email: 'john@acme.inc', + available_name: 'Fayaz Ahmed', + name: 'Fayaz Ahmed', + role: 'administrator', + thumbnail: + 'http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBUUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--16c85844c93f9c139deb782137b49c87c9bc871c/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2QzNKbGMybDZaVWtpRERJMU1IZ3lOVEFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e0e35266e8ed66e90c51be02408be8a022aca545/memoji.png', + }, + account_id: 1, + actions: [ + { + action_name: 'add_label', + action_params: ['sales', 'billing'], + }, + { + action_name: 'assign_team', + action_params: [1], + }, + { + action_name: 'send_message', + action_params: [ + "Thank you for reaching out, we're looking into this on priority and we'll get back to you asap.", + ], + }, + ], + }, + { + id: 23, + name: 'Assign label priority and send email to team', + visibility: 'global', + created_by: { + id: 1, + account_id: 1, + availability_status: 'online', + auto_offline: true, + confirmed: true, + email: 'john@acme.inc', + available_name: 'Fayaz Ahmed', + name: 'Fayaz Ahmed', + role: 'administrator', + thumbnail: + 'http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBUUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--16c85844c93f9c139deb782137b49c87c9bc871c/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2QzNKbGMybDZaVWtpRERJMU1IZ3lOVEFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e0e35266e8ed66e90c51be02408be8a022aca545/memoji.png', + }, + updated_by: { + id: 1, + account_id: 1, + availability_status: 'online', + auto_offline: true, + confirmed: true, + email: 'john@acme.inc', + available_name: 'Fayaz Ahmed', + name: 'Fayaz Ahmed', + role: 'administrator', + thumbnail: + 'http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBUUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--16c85844c93f9c139deb782137b49c87c9bc871c/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2QzNKbGMybDZaVWtpRERJMU1IZ3lOVEFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e0e35266e8ed66e90c51be02408be8a022aca545/memoji.png', + }, + account_id: 1, + actions: [ + { + action_name: 'add_label', + action_params: ['priority'], + }, + { + action_name: 'send_email_to_team', + action_params: [ + { + message: 'Hello team,\n\nThis looks important, please take look.', + team_ids: [1], + }, + ], + }, + ], + }, + { + id: 25, + name: 'Webhook', + visibility: 'global', + created_by: { + id: 1, + account_id: 1, + availability_status: 'online', + auto_offline: true, + confirmed: true, + email: 'john@acme.inc', + available_name: 'Fayaz Ahmed', + name: 'Fayaz Ahmed', + role: 'administrator', + thumbnail: + 'http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBUUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--16c85844c93f9c139deb782137b49c87c9bc871c/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2QzNKbGMybDZaVWtpRERJMU1IZ3lOVEFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e0e35266e8ed66e90c51be02408be8a022aca545/memoji.png', + }, + updated_by: { + id: 1, + account_id: 1, + availability_status: 'online', + auto_offline: true, + confirmed: true, + email: 'john@acme.inc', + available_name: 'Fayaz Ahmed', + name: 'Fayaz Ahmed', + role: 'administrator', + thumbnail: + 'http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBUUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--16c85844c93f9c139deb782137b49c87c9bc871c/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2QzNKbGMybDZaVWtpRERJMU1IZ3lOVEFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e0e35266e8ed66e90c51be02408be8a022aca545/memoji.png', + }, + account_id: 1, + actions: [ + { + action_name: 'send_webhook_event', + action_params: ['https://google.com'], + }, + ], + }, +]; diff --git a/app/javascript/dashboard/store/modules/specs/macros/getters.spec.js b/app/javascript/dashboard/store/modules/specs/macros/getters.spec.js new file mode 100644 index 000000000..d855f66ff --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/macros/getters.spec.js @@ -0,0 +1,32 @@ +import { getters } from '../../macros'; +import macros from './fixtures'; +describe('#getters', () => { + it('getMacros', () => { + const state = { records: macros }; + expect(getters.getMacros(state)).toEqual(macros); + }); + + it('getMacro', () => { + const state = { records: macros }; + expect(getters.getMacro(state)(22)).toEqual(macros[0]); + }); + + it('getUIFlags', () => { + const state = { + uiFlags: { + isFetching: true, + isCreating: false, + isUpdating: false, + isDeleting: false, + isExecuting: false, + }, + }; + expect(getters.getUIFlags(state)).toEqual({ + isFetching: true, + isCreating: false, + isUpdating: false, + isDeleting: false, + isExecuting: false, + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/specs/macros/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/macros/mutations.spec.js new file mode 100644 index 000000000..436738638 --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/macros/mutations.spec.js @@ -0,0 +1,38 @@ +import types from '../../../mutation-types'; +import { mutations } from '../../macros'; +import macros from './fixtures'; +describe('#mutations', () => { + describe('#SET_MACROS', () => { + it('set macrtos records', () => { + const state = { records: [] }; + mutations[types.SET_MACROS](state, macros); + expect(state.records).toEqual(macros); + }); + }); + + describe('#ADD_MACRO', () => { + it('push newly created macro to the store', () => { + const state = { records: [macros[0]] }; + mutations[types.ADD_MACRO](state, macros[1]); + expect(state.records).toEqual([macros[0], macros[1]]); + }); + }); + + describe('#EDIT_MACRO', () => { + it('update macro record', () => { + const state = { records: [macros[0]] }; + mutations[types.EDIT_MACRO](state, macros[0]); + expect(state.records[0].name).toEqual( + 'Assign billing label and sales team and message user' + ); + }); + }); + + describe('#DELETE_MACRO', () => { + it('delete macro record', () => { + const state = { records: [macros[0]] }; + mutations[types.DELETE_MACRO](state, 22); + expect(state.records).toEqual([]); + }); + }); +}); diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js index 17a2c78ee..bcb6ad9a5 100755 --- a/app/javascript/dashboard/store/mutation-types.js +++ b/app/javascript/dashboard/store/mutation-types.js @@ -245,4 +245,18 @@ export default { UPDATE_CATEGORY: 'UPDATE_CATEGORY', REMOVE_CATEGORY: 'REMOVE_CATEGORY', REMOVE_CATEGORY_ID: 'REMOVE_CATEGORY_ID', + + // Agent Bots + SET_AGENT_BOT_UI_FLAG: 'SET_AGENT_BOT_UI_FLAG', + SET_AGENT_BOTS: 'SET_AGENT_BOTS', + ADD_AGENT_BOT: 'ADD_AGENT_BOT', + EDIT_AGENT_BOT: 'EDIT_AGENT_BOT', + DELETE_AGENT_BOT: 'DELETE_AGENT_BOT', + + // MACROS + SET_MACROS_UI_FLAG: 'SET_MACROS_UI_FLAG', + SET_MACROS: 'SET_MACROS', + ADD_MACRO: 'ADD_MACRO', + EDIT_MACRO: 'EDIT_MACRO', + DELETE_MACRO: 'DELETE_MACRO', }; diff --git a/app/javascript/shared/components/FluentIcon/dashboard-icons.json b/app/javascript/shared/components/FluentIcon/dashboard-icons.json index 8b9412efc..9a5eaa893 100644 --- a/app/javascript/shared/components/FluentIcon/dashboard-icons.json +++ b/app/javascript/shared/components/FluentIcon/dashboard-icons.json @@ -29,6 +29,7 @@ "M6.5 2A2.5 2.5 0 0 0 4 4.5v15A2.5 2.5 0 0 0 6.5 22h13.25a.75.75 0 0 0 0-1.5H6.5a1 1 0 0 1-1-1h14.25a.75.75 0 0 0 .75-.75V4.5A2.5 2.5 0 0 0 18 2H6.5ZM19 18H5.5V4.5a1 1 0 0 1 1-1H18a1 1 0 0 1 1 1V18Z" ], "book-open-globe-outline": "M3.5 5.75a.25.25 0 0 1 .25-.25H10c.69 0 1.25.56 1.25 1.25v8.959a6.49 6.49 0 0 1 1.5-2.646V6.75c0-.69.56-1.25 1.25-1.25h6.25a.25.25 0 0 1 .25.25v5.982A6.518 6.518 0 0 1 22 12.81V5.75A1.75 1.75 0 0 0 20.25 4H14c-.788 0-1.499.331-2 .863A2.742 2.742 0 0 0 10 4H3.75A1.75 1.75 0 0 0 2 5.75v12.5c0 .966.784 1.75 1.75 1.75H10c.495 0 .96-.13 1.36-.36a6.473 6.473 0 0 1-.343-1.663A1.248 1.248 0 0 1 10 18.5H3.75a.25.25 0 0 1-.25-.25V5.75ZM16.007 17c.04-1.415.248-2.669.553-3.585.171-.513.364-.893.554-1.134.195-.247.329-.281.386-.281.057 0 .192.034.386.281.19.241.383.62.554 1.134.305.916.513 2.17.553 3.585h-2.986Zm-.396-3.9c.108-.323.23-.622.368-.887A5.504 5.504 0 0 0 12.023 17h2.984c.04-1.5.26-2.866.604-3.9Zm3.778 0a6.133 6.133 0 0 0-.368-.887A5.504 5.504 0 0 1 22.978 17h-2.985c-.04-1.5-.26-2.866-.604-3.9Zm.604 4.9h2.985a5.504 5.504 0 0 1-3.957 4.787c.138-.265.26-.564.368-.886.345-1.035.564-2.4.604-3.901Zm-2.107 4.719c-.194.247-.329.281-.386.281-.057 0-.191-.034-.386-.281-.19-.241-.383-.62-.554-1.135-.305-.915-.513-2.17-.553-3.584h2.986c-.04 1.415-.248 2.669-.553 3.584-.171.514-.364.894-.554 1.135ZM12.023 18a5.504 5.504 0 0 0 3.956 4.787 6.133 6.133 0 0 1-.367-.886c-.346-1.035-.565-2.4-.605-3.901h-2.984Z", + "bot-outline": "M17.753 14a2.25 2.25 0 0 1 2.25 2.25v.905a3.75 3.75 0 0 1-1.307 2.846C17.13 21.345 14.89 22 12 22c-2.89 0-5.128-.656-6.691-2a3.75 3.75 0 0 1-1.306-2.843v-.908A2.25 2.25 0 0 1 6.253 14h11.5Zm0 1.5h-11.5a.75.75 0 0 0-.75.75v.908c0 .655.286 1.278.784 1.706C7.545 19.945 9.44 20.502 12 20.502c2.56 0 4.458-.557 5.719-1.64a2.25 2.25 0 0 0 .784-1.706v-.906a.75.75 0 0 0-.75-.75ZM11.898 2.008 12 2a.75.75 0 0 1 .743.648l.007.102V3.5h3.5a2.25 2.25 0 0 1 2.25 2.25v4.505a2.25 2.25 0 0 1-2.25 2.25h-8.5a2.25 2.25 0 0 1-2.25-2.25V5.75A2.25 2.25 0 0 1 7.75 3.5h3.5v-.749a.75.75 0 0 1 .648-.743L12 2l-.102.007ZM16.25 5h-8.5a.75.75 0 0 0-.75.75v4.505c0 .414.336.75.75.75h8.5a.75.75 0 0 0 .75-.75V5.75a.75.75 0 0 0-.75-.75Zm-6.5 1.5a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.492 0a1.25 1.25 0 1 1 0 2.499 1.25 1.25 0 0 1 0-2.499Z", "building-bank-outline": "M13.032 2.325a1.75 1.75 0 0 0-2.064 0L3.547 7.74c-.978.713-.473 2.26.736 2.26H4.5v5.8A2.75 2.75 0 0 0 3 18.25v1.5c0 .413.336.75.75.75h16.5a.75.75 0 0 0 .75-.75v-1.5a2.75 2.75 0 0 0-1.5-2.45V10h.217c1.21 0 1.713-1.547.736-2.26l-7.421-5.416Zm-1.18 1.211a.25.25 0 0 1 .295 0L18.95 8.5H5.05l6.803-4.964ZM18 10v5.5h-2V10h2Zm-3.5 0v5.5h-1.75V10h1.75Zm-3.25 0v5.5H9.5V10h1.75Zm-5.5 7h12.5c.69 0 1.25.56 1.25 1.25V19h-15v-.75c0-.69.56-1.25 1.25-1.25ZM6 15.5V10h2v5.5H6Z", "calendar-clock-outline": [ "M21 6.25A3.25 3.25 0 0 0 17.75 3H6.25A3.25 3.25 0 0 0 3 6.25v11.5A3.25 3.25 0 0 0 6.25 21h5.772a6.471 6.471 0 0 1-.709-1.5H6.25a1.75 1.75 0 0 1-1.75-1.75V8.5h15v2.813a6.471 6.471 0 0 1 1.5.709V6.25ZM6.25 4.5h11.5c.966 0 1.75.784 1.75 1.75V7h-15v-.75c0-.966.784-1.75 1.75-1.75Z", @@ -79,6 +80,7 @@ "filter-outline": "M13.5 16a.75.75 0 0 1 0 1.5h-3a.75.75 0 0 1 0-1.5h3Zm3-5a.75.75 0 0 1 0 1.5h-9a.75.75 0 0 1 0-1.5h9Zm3-5a.75.75 0 0 1 0 1.5h-15a.75.75 0 0 1 0-1.5h15Z", "file-upload-outline": "M6 2a2 2 0 0 0-2 2v5.207a5.48 5.48 0 0 1 1-.185V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-3.6a5.507 5.507 0 0 1-.657 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6Zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7ZM5.5 19a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9Zm2.354-4.854a.5.5 0 1 1-.708.708L6 13.707V16.5a.5.5 0 0 1-1 0v-2.793l-1.146 1.147a.5.5 0 1 1-.708-.707l2-2A.5.5 0 0 1 5.497 12h.006a.498.498 0 0 1 .348.144l.003.003l2 2Z", "flash-on-outline": "m8.294 14-1.767 7.068c-.187.746.736 1.256 1.269.701L19.79 9.27A.75.75 0 0 0 19.25 8h-4.46l1.672-5.013A.75.75 0 0 0 15.75 2h-7a.75.75 0 0 0-.721.544l-3 10.5A.75.75 0 0 0 5.75 14h2.544Zm4.745-5.487a.75.75 0 0 0 .711.987h3.74l-8.824 9.196 1.316-5.264a.75.75 0 0 0-.727-.932h-2.51l2.57-9h5.394l-1.67 5.013Z", + "flash-settings-outline": "M6.19 2.77c.13-.455.547-.77 1.02-.77h5.25c.724 0 1.236.71 1.007 1.398l-.002.008L12.204 7h2.564c.946 0 1.407 1.144.766 1.811l-.003.004l-.237.242a5.545 5.545 0 0 0-1.374-.027l.894-.912a.056.056 0 0 0 .017-.032a.084.084 0 0 0-.007-.044a.079.079 0 0 0-.025-.034c-.005-.004-.013-.008-.031-.008h-3.27a.5.5 0 0 1-.471-.666L12.52 3.08a.062.062 0 0 0-.06-.08H7.211a.062.062 0 0 0-.06.045l-2.25 7.874c-.01.04.019.08.06.08H6.87a.5.5 0 0 1 .485.62l-1.325 5.3a.086.086 0 0 0-.003.03a.02.02 0 0 0 .003.011a.08.08 0 0 0 .072.04a.03.03 0 0 0 .01-.004a.087.087 0 0 0 .024-.018l.003-.004l2.882-2.94a5.573 5.573 0 0 0 .054 1.372l-2.22 2.267c-.754.782-2.059.06-1.795-.996l1.17-4.679H4.96a1.062 1.062 0 0 1-1.021-1.354l2.25-7.873Zm5.877 8.673a2 2 0 0 1-1.431 2.478l-.461.118a4.702 4.702 0 0 0 .01 1.016l.35.083a2 2 0 0 1 1.456 2.519l-.127.422c.257.204.537.378.835.518l.325-.344a2 2 0 0 1 2.91.002l.337.358c.292-.135.568-.302.822-.498l-.157-.556a2 2 0 0 1 1.431-2.479l.46-.117a4.702 4.702 0 0 0-.01-1.017l-.348-.082a2 2 0 0 1-1.456-2.52l.126-.421a4.32 4.32 0 0 0-.835-.519l-.325.344a2 2 0 0 1-2.91-.001l-.337-.358a4.316 4.316 0 0 0-.821.497l.156.557ZM14.5 15.5a1 1 0 1 1 0-2a1 1 0 0 1 0 2Z", "folder-outline": "M8.207 4c.46 0 .908.141 1.284.402l.156.12L12.022 6.5h7.728a2.25 2.25 0 0 1 2.229 1.938l.016.158.005.154v9a2.25 2.25 0 0 1-2.096 2.245L19.75 20H4.25a2.25 2.25 0 0 1-2.245-2.096L2 17.75V6.25a2.25 2.25 0 0 1 2.096-2.245L4.25 4h3.957Zm1.44 5.979a2.25 2.25 0 0 1-1.244.512l-.196.009-4.707-.001v7.251c0 .38.282.694.648.743l.102.007h15.5a.75.75 0 0 0 .743-.648l.007-.102v-9a.75.75 0 0 0-.648-.743L19.75 8h-7.729L9.647 9.979ZM8.207 5.5H4.25a.75.75 0 0 0-.743.648L3.5 6.25v2.749L8.207 9a.75.75 0 0 0 .395-.113l.085-.06 1.891-1.578-1.89-1.575a.75.75 0 0 0-.377-.167L8.207 5.5Z", "globe-outline": "M12 1.999c5.524 0 10.002 4.478 10.002 10.002 0 5.523-4.478 10.001-10.002 10.001-5.524 0-10.002-4.478-10.002-10.001C1.998 6.477 6.476 1.999 12 1.999ZM14.939 16.5H9.06c.652 2.414 1.786 4.002 2.939 4.002s2.287-1.588 2.939-4.002Zm-7.43 0H4.785a8.532 8.532 0 0 0 4.094 3.411c-.522-.82-.953-1.846-1.27-3.015l-.102-.395Zm11.705 0h-2.722c-.324 1.335-.792 2.5-1.373 3.411a8.528 8.528 0 0 0 3.91-3.127l.185-.283ZM7.094 10H3.735l-.005.017a8.525 8.525 0 0 0-.233 1.984c0 1.056.193 2.067.545 3h3.173a20.847 20.847 0 0 1-.123-5Zm8.303 0H8.603a18.966 18.966 0 0 0 .135 5h6.524a18.974 18.974 0 0 0 .135-5Zm4.868 0h-3.358c.062.647.095 1.317.095 2a20.3 20.3 0 0 1-.218 3h3.173a8.482 8.482 0 0 0 .544-3c0-.689-.082-1.36-.236-2ZM8.88 4.09l-.023.008A8.531 8.531 0 0 0 4.25 8.5h3.048c.314-1.752.86-3.278 1.583-4.41ZM12 3.499l-.116.005C10.62 3.62 9.396 5.622 8.83 8.5h6.342c-.566-2.87-1.783-4.869-3.045-4.995L12 3.5Zm3.12.59.107.175c.669 1.112 1.177 2.572 1.475 4.237h3.048a8.533 8.533 0 0 0-4.339-4.29l-.291-.121Z", "globe-desktop-outline": "M22.002 12C22.002 6.477 17.524 2 12 2 6.476 1.999 2 6.477 2 12.001c0 5.186 3.947 9.45 9.001 9.952V20.11c-.778-.612-1.478-1.905-1.939-3.61h1.94V15H8.737a18.969 18.969 0 0 1-.135-5h6.794c.068.64.105 1.31.105 2h1.5c0-.684-.033-1.353-.095-2h3.358c.154.64.237 1.31.237 2h1.5ZM4.786 16.5h2.722l.102.396c.317 1.17.748 2.195 1.27 3.015a8.532 8.532 0 0 1-4.094-3.41ZM3.736 10h3.358a20.847 20.847 0 0 0-.095 2c0 1.043.075 2.051.217 3H4.043a8.483 8.483 0 0 1-.544-3c0-.682.08-1.347.232-1.983L3.736 10Zm5.122-5.902.023-.008C8.16 5.222 7.611 6.748 7.298 8.5H4.25c.905-2 2.56-3.587 4.608-4.402Zm3.026-.594L12 3.5l.126.006c1.262.126 2.48 2.125 3.045 4.995H8.83c.568-2.878 1.79-4.88 3.055-4.996Zm3.343.76-.107-.174.291.121a8.533 8.533 0 0 1 4.339 4.29h-3.048c-.298-1.665-.806-3.125-1.475-4.237Z M12 19a1 1 0 0 0 1 1h3v2h-.5a.5.5 0 1 0 0 1h4a.5.5 0 0 0 0-1H19v-2h3a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1h-9a1 1 0 0 0-1 1v5Z", diff --git a/app/javascript/shared/components/emoji/EmojiInput.vue b/app/javascript/shared/components/emoji/EmojiInput.vue index 387605913..84891b0e2 100644 --- a/app/javascript/shared/components/emoji/EmojiInput.vue +++ b/app/javascript/shared/components/emoji/EmojiInput.vue @@ -5,21 +5,21 @@
  • +
    + {{ selectedKey }} +
    -
    - {{ selectedKey }} -