diff --git a/app/javascript/dashboard/modules/search/search.routes.js b/app/javascript/dashboard/modules/search/search.routes.js
index d2d7b19c4..e677248e9 100644
--- a/app/javascript/dashboard/modules/search/search.routes.js
+++ b/app/javascript/dashboard/modules/search/search.routes.js
@@ -1,5 +1,10 @@
/* eslint-disable storybook/default-exports */
import { frontendURL } from '../../helper/URLHelper';
+import {
+ ROLES,
+ CONVERSATION_PERMISSIONS,
+ CONTACT_PERMISSIONS,
+} from 'dashboard/constants/permissions.js';
const SearchView = () => import('./components/SearchView.vue');
@@ -8,7 +13,7 @@ export const routes = [
path: frontendURL('accounts/:accountId/search'),
name: 'search',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: [...ROLES, ...CONVERSATION_PERMISSIONS, CONTACT_PERMISSIONS],
},
component: SearchView,
},
diff --git a/app/javascript/dashboard/routes/dashboard/contacts/routes.js b/app/javascript/dashboard/routes/dashboard/contacts/routes.js
index a07ca6bf3..d28718c9e 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/routes.js
+++ b/app/javascript/dashboard/routes/dashboard/contacts/routes.js
@@ -8,7 +8,7 @@ export const routes = [
path: frontendURL('accounts/:accountId/contacts'),
name: 'contacts_dashboard',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'contact_manage'],
},
component: ContactsView,
},
@@ -16,7 +16,7 @@ export const routes = [
path: frontendURL('accounts/:accountId/contacts/custom_view/:id'),
name: 'contacts_segments_dashboard',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'contact_manage'],
},
component: ContactsView,
props: route => {
@@ -27,7 +27,7 @@ export const routes = [
path: frontendURL('accounts/:accountId/labels/:label/contacts'),
name: 'contacts_labels_dashboard',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'contact_manage'],
},
component: ContactsView,
props: route => {
@@ -38,7 +38,7 @@ export const routes = [
path: frontendURL('accounts/:accountId/contacts/:contactId'),
name: 'contact_profile_dashboard',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'contact_manage'],
},
component: ContactManageView,
props: route => {
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js b/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js
index d86ca271b..019d659ac 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js
@@ -2,13 +2,21 @@
import { frontendURL } from '../../../helper/URLHelper';
const ConversationView = () => import('./ConversationView.vue');
+const CONVERSATION_PERMISSIONS = [
+ 'administrator',
+ 'agent',
+ 'conversation_manage',
+ 'conversation_unassigned_manage',
+ 'conversation_participating_manage',
+];
+
export default {
routes: [
{
path: frontendURL('accounts/:accountId/dashboard'),
name: 'home',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: () => {
@@ -19,7 +27,7 @@ export default {
path: frontendURL('accounts/:accountId/conversations/:conversation_id'),
name: 'inbox_conversation',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => {
@@ -30,7 +38,7 @@ export default {
path: frontendURL('accounts/:accountId/inbox/:inbox_id'),
name: 'inbox_dashboard',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => {
@@ -43,7 +51,7 @@ export default {
),
name: 'conversation_through_inbox',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => {
@@ -57,7 +65,7 @@ export default {
path: frontendURL('accounts/:accountId/label/:label'),
name: 'label_conversations',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => ({ label: route.params.label }),
@@ -68,7 +76,7 @@ export default {
),
name: 'conversations_through_label',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => ({
@@ -80,7 +88,7 @@ export default {
path: frontendURL('accounts/:accountId/team/:teamId'),
name: 'team_conversations',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => ({ teamId: route.params.teamId }),
@@ -91,7 +99,7 @@ export default {
),
name: 'conversations_through_team',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => ({
@@ -103,7 +111,7 @@ export default {
path: frontendURL('accounts/:accountId/custom_view/:id'),
name: 'folder_conversations',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => ({ foldersId: route.params.id }),
@@ -114,7 +122,7 @@ export default {
),
name: 'conversations_through_folders',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => ({
@@ -126,7 +134,7 @@ export default {
path: frontendURL('accounts/:accountId/mentions/conversations'),
name: 'conversation_mentions',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: () => ({ conversationType: 'mention' }),
@@ -137,7 +145,7 @@ export default {
),
name: 'conversation_through_mentions',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => ({
@@ -149,7 +157,7 @@ export default {
path: frontendURL('accounts/:accountId/unattended/conversations'),
name: 'conversation_unattended',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: () => ({ conversationType: 'unattended' }),
@@ -160,7 +168,7 @@ export default {
),
name: 'conversation_through_unattended',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => ({
@@ -172,7 +180,7 @@ export default {
path: frontendURL('accounts/:accountId/participating/conversations'),
name: 'conversation_participating',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: () => ({ conversationType: 'participating' }),
@@ -183,7 +191,7 @@ export default {
),
name: 'conversation_through_participating',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: CONVERSATION_PERMISSIONS,
},
component: ConversationView,
props: route => ({
diff --git a/app/javascript/dashboard/routes/dashboard/dashboard.routes.js b/app/javascript/dashboard/routes/dashboard/dashboard.routes.js
index 0f30f1efc..4ca9f7e4b 100644
--- a/app/javascript/dashboard/routes/dashboard/dashboard.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/dashboard.routes.js
@@ -38,7 +38,7 @@ export default {
path: frontendURL('accounts/:accountId/suspended'),
name: 'account_suspended',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'custom_role'],
},
component: Suspended,
},
diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js b/app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js
index d17a24ab9..95f678abc 100644
--- a/app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js
@@ -33,7 +33,7 @@ const portalRoutes = [
path: getPortalRoute(''),
name: 'default_portal_articles',
meta: {
- permissions: ['administrator'],
+ permissions: ['administrator', 'knowledge_base_manage'],
},
component: DefaultPortalArticles,
},
@@ -41,7 +41,7 @@ const portalRoutes = [
path: getPortalRoute('all'),
name: 'list_all_portals',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: ListAllPortals,
},
@@ -54,7 +54,7 @@ const portalRoutes = [
name: 'new_portal_information',
component: PortalDetails,
meta: {
- permissions: ['administrator'],
+ permissions: ['administrator', 'knowledge_base_manage'],
},
},
{
@@ -62,7 +62,7 @@ const portalRoutes = [
name: 'portal_customization',
component: PortalCustomization,
meta: {
- permissions: ['administrator'],
+ permissions: ['administrator', 'knowledge_base_manage'],
},
},
{
@@ -70,7 +70,7 @@ const portalRoutes = [
name: 'portal_finish',
component: PortalSettingsFinish,
meta: {
- permissions: ['administrator'],
+ permissions: ['administrator', 'knowledge_base_manage'],
},
},
],
@@ -79,14 +79,14 @@ const portalRoutes = [
path: getPortalRoute(':portalSlug'),
name: 'portalSlug',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: ShowPortal,
},
{
path: getPortalRoute(':portalSlug/edit'),
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: EditPortal,
children: [
@@ -95,7 +95,7 @@ const portalRoutes = [
name: 'edit_portal_information',
component: EditPortalBasic,
meta: {
- permissions: ['administrator'],
+ permissions: ['administrator', 'knowledge_base_manage'],
},
},
{
@@ -103,7 +103,7 @@ const portalRoutes = [
name: 'edit_portal_customization',
component: EditPortalCustomization,
meta: {
- permissions: ['administrator'],
+ permissions: ['administrator', 'knowledge_base_manage'],
},
},
{
@@ -111,14 +111,14 @@ const portalRoutes = [
name: 'edit_portal_locales',
component: EditPortalLocales,
meta: {
- permissions: ['administrator'],
+ permissions: ['administrator', 'knowledge_base_manage'],
},
},
{
path: 'categories',
name: 'list_all_locale_categories',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: ListAllCategories,
},
@@ -131,7 +131,7 @@ const articleRoutes = [
path: getPortalRoute(':portalSlug/:locale/articles'),
name: 'list_all_locale_articles',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: ListAllArticles,
},
@@ -139,7 +139,7 @@ const articleRoutes = [
path: getPortalRoute(':portalSlug/:locale/articles/new'),
name: 'new_article',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: NewArticle,
},
@@ -147,7 +147,7 @@ const articleRoutes = [
path: getPortalRoute(':portalSlug/:locale/articles/mine'),
name: 'list_mine_articles',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: ListAllArticles,
},
@@ -155,7 +155,7 @@ const articleRoutes = [
path: getPortalRoute(':portalSlug/:locale/articles/archived'),
name: 'list_archived_articles',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: ListAllArticles,
},
@@ -164,7 +164,7 @@ const articleRoutes = [
path: getPortalRoute(':portalSlug/:locale/articles/draft'),
name: 'list_draft_articles',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: ListAllArticles,
},
@@ -173,7 +173,7 @@ const articleRoutes = [
path: getPortalRoute(':portalSlug/:locale/articles/:articleSlug'),
name: 'edit_article',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: EditArticle,
},
@@ -184,7 +184,7 @@ const categoryRoutes = [
path: getPortalRoute(':portalSlug/:locale/categories'),
name: 'all_locale_categories',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: ListAllCategories,
},
@@ -192,7 +192,7 @@ const categoryRoutes = [
path: getPortalRoute(':portalSlug/:locale/categories/new'),
name: 'new_category_in_locale',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: NewCategory,
},
@@ -200,7 +200,7 @@ const categoryRoutes = [
path: getPortalRoute(':portalSlug/:locale/categories/:categorySlug'),
name: 'show_category',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: ListAllArticles,
},
@@ -210,7 +210,7 @@ const categoryRoutes = [
),
name: 'show_category_articles',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: ListCategoryArticles,
},
@@ -218,7 +218,7 @@ const categoryRoutes = [
path: getPortalRoute(':portalSlug/:locale/categories/:categorySlug'),
name: 'edit_category',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'knowledge_base_manage'],
},
component: EditCategory,
},
diff --git a/app/javascript/dashboard/routes/dashboard/inbox/routes.js b/app/javascript/dashboard/routes/dashboard/inbox/routes.js
index 729fee76d..b55c33d73 100644
--- a/app/javascript/dashboard/routes/dashboard/inbox/routes.js
+++ b/app/javascript/dashboard/routes/dashboard/inbox/routes.js
@@ -2,6 +2,10 @@ import { frontendURL } from 'dashboard/helper/URLHelper';
const InboxListView = () => import('./InboxList.vue');
const InboxDetailView = () => import('./InboxView.vue');
const InboxEmptyStateView = () => import('./InboxEmptyState.vue');
+import {
+ ROLES,
+ CONVERSATION_PERMISSIONS,
+} from 'dashboard/constants/permissions.js';
export const routes = [
{
@@ -13,7 +17,7 @@ export const routes = [
name: 'inbox_view',
component: InboxEmptyStateView,
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
},
{
@@ -21,7 +25,7 @@ export const routes = [
name: 'inbox_view_conversation',
component: InboxDetailView,
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
},
],
diff --git a/app/javascript/dashboard/routes/dashboard/notifications/routes.js b/app/javascript/dashboard/routes/dashboard/notifications/routes.js
index e3d125333..f3d32ba1e 100644
--- a/app/javascript/dashboard/routes/dashboard/notifications/routes.js
+++ b/app/javascript/dashboard/routes/dashboard/notifications/routes.js
@@ -19,7 +19,7 @@ export const routes = [
name: 'notifications_index',
component: NotificationsView,
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: ['administrator', 'agent', 'custom_role'],
},
},
],
diff --git a/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue b/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue
index 74720e28a..2822704c9 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue
@@ -1,156 +1,164 @@
-
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/agents/EditAgent.vue b/app/javascript/dashboard/routes/dashboard/settings/agents/EditAgent.vue
index 4385723b6..daca5511c 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/agents/EditAgent.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/agents/EditAgent.vue
@@ -1,203 +1,220 @@
-
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/agents/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/agents/Index.vue
index cb0402e12..48e738f11 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/agents/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/agents/Index.vue
@@ -3,7 +3,11 @@ import { useAlert } from 'dashboard/composables';
import { computed, onMounted, ref } from 'vue';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import { useI18n } from 'dashboard/composables/useI18n';
-import { useStoreGetters, useStore } from 'dashboard/composables/store';
+import {
+ useStoreGetters,
+ useStore,
+ useMapGetter,
+} from 'dashboard/composables/store';
import AddAgent from './AddAgent.vue';
import EditAgent from './EditAgent.vue';
@@ -34,11 +38,32 @@ const deleteMessage = computed(() => {
const agentList = computed(() => getters['agents/getAgents'].value);
const uiFlags = computed(() => getters['agents/getUIFlags'].value);
const currentUserId = computed(() => getters.getCurrentUserID.value);
+const customRoles = useMapGetter('customRole/getCustomRoles');
onMounted(() => {
store.dispatch('agents/get');
+ store.dispatch('customRole/getCustomRole');
});
+const findCustomRole = agent =>
+ customRoles.value.find(role => role.id === agent.custom_role_id);
+
+const getAgentRoleName = agent => {
+ if (!agent.custom_role_id) {
+ return t(`AGENT_MGMT.AGENT_TYPES.${agent.role.toUpperCase()}`);
+ }
+ const customRole = findCustomRole(agent);
+ return customRole ? customRole.name : '';
+};
+
+const getAgentRolePermissions = agent => {
+ if (!agent.custom_role_id) {
+ return [];
+ }
+ const customRole = findCustomRole(agent);
+ return customRole?.permissions || [];
+};
+
const verifiedAdministrators = computed(() => {
return agentList.value.filter(
agent => agent.role === 'administrator' && agent.confirmed
@@ -63,6 +88,7 @@ const showDeleteAction = agent => {
}
return true;
};
+
const showAlertMessage = message => {
loading.value[currentAgent.value.id] = false;
currentAgent.value = {};
@@ -124,7 +150,7 @@ const confirmDeletion = () => {
>
@@ -140,7 +166,7 @@ const confirmDeletion = () => {
>
|
- |
-
-
- {{ $t(`AGENT_MGMT.AGENT_TYPES.${agent.role.toUpperCase()}`) }}
+ |
+
+ {{ getAgentRoleName(agent) }}
+
+
+
+
+ {{ $t('AGENT_MGMT.LIST.AVAILABLE_CUSTOM_ROLE') }}
+
+
+ -
+ {{
+ $t(
+ `CUSTOM_ROLE.PERMISSIONS.${permission.toUpperCase()}`
+ )
+ }}
+
+
+
+
|
@@ -200,7 +256,7 @@ const confirmDeletion = () => {
-
+
@@ -211,7 +267,8 @@ const confirmDeletion = () => {
:type="currentAgent.role"
:email="currentAgent.email"
:availability="currentAgent.availability_status"
- :on-close="hideEditPopup"
+ :custom-role-id="currentAgent.custom_role_id"
+ @close="hideEditPopup"
/>
diff --git a/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js b/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js
index 9140e2c17..2a464c162 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js
@@ -1,5 +1,8 @@
import { frontendURL } from '../../../../helper/URLHelper';
-
+import {
+ ROLES,
+ CONVERSATION_PERMISSIONS,
+} from 'dashboard/constants/permissions.js';
const SettingsWrapper = () => import('../SettingsWrapper.vue');
const CannedHome = () => import('./Index.vue');
@@ -17,7 +20,7 @@ export default {
path: 'list',
name: 'canned_list',
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
component: CannedHome,
},
diff --git a/app/javascript/dashboard/routes/dashboard/settings/components/BasePaywallModal.vue b/app/javascript/dashboard/routes/dashboard/settings/components/BasePaywallModal.vue
new file mode 100644
index 000000000..85d642dcc
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/components/BasePaywallModal.vue
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+ {{ $t(`${featurePrefix}.PAYWALL.TITLE`) }}
+
+
+
+
+ {{ $t(`${featurePrefix}.${i18nKey}.UPGRADE_PROMPT`) }}
+
+ {{ $t(`${featurePrefix}.ENTERPRISE_PAYWALL.ASK_ADMIN`) }}
+
+
+
+
+ {{ $t(`${featurePrefix}.PAYWALL.UPGRADE_NOW`) }}
+
+
+ {{ $t(`${featurePrefix}.PAYWALL.CANCEL_ANYTIME`) }}
+
+
+
+
+
+ {{ $t(`${featurePrefix}.PAYWALL.UPGRADE_NOW`) }}
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/customRoles/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/customRoles/Index.vue
new file mode 100644
index 000000000..156ca4519
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/customRoles/Index.vue
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
+ {{ $t('CUSTOM_ROLE.HEADER_BTN_TXT') }}
+
+
+
+
+
+
+
+
+
+ |
+
+ {{ thHeader }}
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleModal.vue b/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleModal.vue
new file mode 100644
index 000000000..f1e011e01
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleModal.vue
@@ -0,0 +1,248 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRolePaywall.vue b/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRolePaywall.vue
new file mode 100644
index 000000000..69ed4acda
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRolePaywall.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+ |
+
+ {{ thHeader }}
+
+ |
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue b/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue
new file mode 100644
index 000000000..a04f2f8cb
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+ |
+ {{ customRole.name }}
+ |
+
+ {{ customRole.description }}
+ |
+
+ {{ getFormattedPermissions(customRole) }}
+ |
+
+
+
+ |
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/customRoles/customRole.routes.js b/app/javascript/dashboard/routes/dashboard/settings/customRoles/customRole.routes.js
new file mode 100644
index 000000000..623a0081a
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/customRoles/customRole.routes.js
@@ -0,0 +1,27 @@
+import { frontendURL } from 'dashboard/helper/URLHelper';
+
+const SettingsWrapper = () => import('../SettingsWrapper.vue');
+const CustomRolesHome = () => import('./Index.vue');
+
+export default {
+ routes: [
+ {
+ path: frontendURL('accounts/:accountId/settings/custom-roles'),
+ component: SettingsWrapper,
+ children: [
+ {
+ path: '',
+ redirect: 'list',
+ },
+ {
+ path: 'list',
+ name: 'custom_roles_list',
+ meta: {
+ permissions: ['administrator'],
+ },
+ component: CustomRolesHome,
+ },
+ ],
+ },
+ ],
+};
diff --git a/app/javascript/dashboard/routes/dashboard/settings/helper/settingsHelper.js b/app/javascript/dashboard/routes/dashboard/settings/helper/settingsHelper.js
new file mode 100644
index 000000000..1da2bd54f
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/helper/settingsHelper.js
@@ -0,0 +1,4 @@
+export const getI18nKey = (prefix, event) => {
+ const eventName = event.toUpperCase();
+ return `${prefix}.${eventName}`;
+};
diff --git a/app/javascript/dashboard/routes/dashboard/settings/helper/spec/settingsHelper.spec.js b/app/javascript/dashboard/routes/dashboard/settings/helper/spec/settingsHelper.spec.js
new file mode 100644
index 000000000..e7d86eb52
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/helper/spec/settingsHelper.spec.js
@@ -0,0 +1,30 @@
+import { getI18nKey } from '../settingsHelper';
+
+describe('settingsHelper', () => {
+ describe('getI18nKey', () => {
+ it('should return the correct i18n key', () => {
+ const prefix = 'CUSTOM_ROLE.PERMISSIONS';
+ const event = 'conversation_manage';
+ const expectedKey = 'CUSTOM_ROLE.PERMISSIONS.CONVERSATION_MANAGE';
+
+ expect(getI18nKey(prefix, event)).toBe(expectedKey);
+ });
+
+ it('should handle different prefixes', () => {
+ const prefix = 'INTEGRATION_SETTINGS.WEBHOOK.FORM.SUBSCRIPTIONS.EVENTS';
+ const event = 'message_created';
+ const expectedKey =
+ 'INTEGRATION_SETTINGS.WEBHOOK.FORM.SUBSCRIPTIONS.EVENTS.MESSAGE_CREATED';
+
+ expect(getI18nKey(prefix, event)).toBe(expectedKey);
+ });
+
+ it('should convert event to uppercase', () => {
+ const prefix = 'TEST_PREFIX';
+ const event = 'lowercaseEvent';
+ const expectedKey = 'TEST_PREFIX.LOWERCASEEVENT';
+
+ expect(getI18nKey(prefix, event)).toBe(expectedKey);
+ });
+ });
+});
diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/WebhookForm.vue b/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/WebhookForm.vue
index f5fbc6a3d..1eca60015 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/WebhookForm.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/WebhookForm.vue
@@ -2,7 +2,7 @@
import { useVuelidate } from '@vuelidate/core';
import { required, url, minLength } from '@vuelidate/validators';
import wootConstants from 'dashboard/constants/globals';
-import { getEventNamei18n } from './webhookHelper';
+import { getI18nKey } from 'dashboard/routes/dashboard/settings/helper/settingsHelper';
const { EXAMPLE_WEBHOOK_URL } = wootConstants;
@@ -69,7 +69,7 @@ export default {
subscriptions: this.subscriptions,
});
},
- getEventNamei18n,
+ getI18nKey,
},
};
@@ -108,13 +108,20 @@ export default {
class="mr-2"
/>
-
+
import { computed } from 'vue';
-import { getEventNamei18n } from './webhookHelper';
+import { getI18nKey } from 'dashboard/routes/dashboard/settings/helper/settingsHelper';
import ShowMore from 'dashboard/components/widgets/ShowMore.vue';
import { useI18n } from 'dashboard/composables/useI18n';
@@ -17,7 +17,16 @@ const props = defineProps({
const { t } = useI18n();
const subscribedEvents = computed(() => {
const { subscriptions } = props.webhook;
- return subscriptions.map(event => t(getEventNamei18n(event))).join(', ');
+ return subscriptions
+ .map(event =>
+ t(
+ getI18nKey(
+ 'INTEGRATION_SETTINGS.WEBHOOK.FORM.SUBSCRIPTIONS.EVENTS',
+ event
+ )
+ )
+ )
+ .join(', ');
});
@@ -27,7 +36,7 @@ const subscribedEvents = computed(() => {
{{ webhook.url }}
-
+
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.SUBSCRIBED_EVENTS') }}:
@@ -35,7 +44,7 @@ const subscribedEvents = computed(() => {
|
-
+
{
- it('returns correct i18n translation text', () => {
- expect(getEventNamei18n('message_created')).toEqual(
- `INTEGRATION_SETTINGS.WEBHOOK.FORM.SUBSCRIPTIONS.EVENTS.MESSAGE_CREATED`
- );
- });
-});
diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/webhookHelper.js b/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/webhookHelper.js
deleted file mode 100644
index 699dafdc8..000000000
--- a/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/webhookHelper.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export const getEventNamei18n = event => {
- const eventName = event.toUpperCase();
- return `INTEGRATION_SETTINGS.WEBHOOK.FORM.SUBSCRIPTIONS.EVENTS.${eventName}`;
-};
diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js
index 6f77e0c18..24559dffd 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js
@@ -1,5 +1,9 @@
import { frontendURL } from 'dashboard/helper/URLHelper';
+import {
+ ROLES,
+ CONVERSATION_PERMISSIONS,
+} from 'dashboard/constants/permissions.js';
const SettingsContent = () => import('../Wrapper.vue');
const SettingsWrapper = () => import('../SettingsWrapper.vue');
const Macros = () => import('./Index.vue');
@@ -16,7 +20,7 @@ export default {
name: 'macros_wrapper',
component: Macros,
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
},
],
@@ -37,7 +41,7 @@ export default {
name: 'macros_edit',
component: MacroEditor,
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
},
{
@@ -45,7 +49,7 @@ export default {
name: 'macros_new',
component: MacroEditor,
meta: {
- permissions: ['administrator', 'agent'],
+ permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
},
],
diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue
index 1f37a1355..f2a5452ab 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue
@@ -14,12 +14,18 @@ import NotificationPreferences from './NotificationPreferences.vue';
import AudioNotifications from './AudioNotifications.vue';
import FormSection from 'dashboard/components/FormSection.vue';
import AccessToken from './AccessToken.vue';
+import Policy from 'dashboard/components/policy.vue';
+import {
+ ROLES,
+ CONVERSATION_PERMISSIONS,
+} from 'dashboard/constants/permissions.js';
export default {
components: {
MessageSignature,
FormSection,
UserProfilePicture,
+ Policy,
UserBasicDetails,
HotKeyCard,
ChangePassword,
@@ -71,6 +77,8 @@ export default {
'/assets/images/dashboard/profile/hot-key-ctrl-enter-dark.svg',
},
],
+ notificationPermissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
+ audioNotificationPermissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
};
},
computed: {
@@ -235,17 +243,21 @@ export default {
>
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- {{ $t('SLA.PAYWALL.TITLE') }}
-
-
-
-
- {{ $t(`SLA.${i18nKey}.UPGRADE_PROMPT`) }}
-
- {{ $t('SLA.ENTERPRISE_PAYWALL.ASK_ADMIN') }}
-
-
-
-
- {{ $t('SLA.PAYWALL.UPGRADE_NOW') }}
-
-
- {{ $t('SLA.PAYWALL.CANCEL_ANYTIME') }}
-
-
-
-
-
- {{ $t('SLA.PAYWALL.UPGRADE_NOW') }}
-
-
-
-
+
diff --git a/app/javascript/dashboard/store/index.js b/app/javascript/dashboard/store/index.js
index 4f5b5af6b..f2e03f733 100755
--- a/app/javascript/dashboard/store/index.js
+++ b/app/javascript/dashboard/store/index.js
@@ -27,6 +27,7 @@ import conversationTypingStatus from './modules/conversationTypingStatus';
import conversationWatchers from './modules/conversationWatchers';
import csat from './modules/csat';
import customViews from './modules/customViews';
+import customRole from './modules/customRole';
import dashboardApps from './modules/dashboardApps';
import globalConfig from 'shared/store/globalConfig';
import inboxAssignableAgents from './modules/inboxAssignableAgents';
@@ -77,6 +78,7 @@ export default new Vuex.Store({
conversationWatchers,
csat,
customViews,
+ customRole,
dashboardApps,
globalConfig,
inboxAssignableAgents,
diff --git a/app/javascript/dashboard/store/modules/auth.js b/app/javascript/dashboard/store/modules/auth.js
index d1952f43d..5f3604aa2 100644
--- a/app/javascript/dashboard/store/modules/auth.js
+++ b/app/javascript/dashboard/store/modules/auth.js
@@ -66,6 +66,14 @@ export const getters = {
return currentAccount.role;
},
+ getCurrentCustomRoleId($state, $getters) {
+ const { accounts = [] } = $state.currentUser;
+ const [currentAccount = {}] = accounts.filter(
+ account => account.id === $getters.getCurrentAccountId
+ );
+ return currentAccount.custom_role_id;
+ },
+
getCurrentUser($state) {
return $state.currentUser;
},
diff --git a/app/javascript/dashboard/store/modules/customRole.js b/app/javascript/dashboard/store/modules/customRole.js
new file mode 100644
index 000000000..7f931f6fa
--- /dev/null
+++ b/app/javascript/dashboard/store/modules/customRole.js
@@ -0,0 +1,100 @@
+import { throwErrorMessage } from 'dashboard/store/utils/api';
+import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
+import * as types from '../mutation-types';
+import CustomRoleAPI from '../../api/customRole';
+
+export const state = {
+ records: [],
+ uiFlags: {
+ fetchingList: false,
+ creatingItem: false,
+ updatingItem: false,
+ deletingItem: false,
+ },
+};
+
+export const getters = {
+ getCustomRoles($state) {
+ return $state.records;
+ },
+ getUIFlags($state) {
+ return $state.uiFlags;
+ },
+};
+
+export const actions = {
+ getCustomRole: async function getCustomRole({ commit }) {
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { fetchingList: true });
+ try {
+ const response = await CustomRoleAPI.get();
+ commit(types.default.SET_CUSTOM_ROLE, response.data);
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { fetchingList: false });
+ } catch (error) {
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { fetchingList: false });
+ }
+ },
+
+ createCustomRole: async function createCustomRole({ commit }, customRoleObj) {
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { creatingItem: true });
+ try {
+ const response = await CustomRoleAPI.create(customRoleObj);
+ commit(types.default.ADD_CUSTOM_ROLE, response.data);
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { creatingItem: false });
+ return response.data;
+ } catch (error) {
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { creatingItem: false });
+ return throwErrorMessage(error);
+ }
+ },
+
+ updateCustomRole: async function updateCustomRole(
+ { commit },
+ { id, ...updateObj }
+ ) {
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { updatingItem: true });
+ try {
+ const response = await CustomRoleAPI.update(id, updateObj);
+ commit(types.default.EDIT_CUSTOM_ROLE, response.data);
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { updatingItem: false });
+ return response.data;
+ } catch (error) {
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { updatingItem: false });
+ return throwErrorMessage(error);
+ }
+ },
+
+ deleteCustomRole: async function deleteCustomRole({ commit }, id) {
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { deletingItem: true });
+ try {
+ await CustomRoleAPI.delete(id);
+ commit(types.default.DELETE_CUSTOM_ROLE, id);
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { deletingItem: true });
+ return id;
+ } catch (error) {
+ commit(types.default.SET_CUSTOM_ROLE_UI_FLAG, { deletingItem: true });
+ return throwErrorMessage(error);
+ }
+ },
+};
+
+export const mutations = {
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG](_state, data) {
+ _state.uiFlags = {
+ ..._state.uiFlags,
+ ...data,
+ };
+ },
+
+ [types.default.SET_CUSTOM_ROLE]: MutationHelpers.set,
+ [types.default.ADD_CUSTOM_ROLE]: MutationHelpers.create,
+ [types.default.EDIT_CUSTOM_ROLE]: MutationHelpers.update,
+ [types.default.DELETE_CUSTOM_ROLE]: MutationHelpers.destroy,
+};
+
+export default {
+ namespaced: true,
+ state,
+ getters,
+ actions,
+ mutations,
+};
diff --git a/app/javascript/dashboard/store/modules/specs/auth/getters.spec.js b/app/javascript/dashboard/store/modules/specs/auth/getters.spec.js
index e89386af3..8644428db 100644
--- a/app/javascript/dashboard/store/modules/specs/auth/getters.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/auth/getters.spec.js
@@ -42,6 +42,26 @@ describe('#getters', () => {
});
});
+ describe('#getCurrentCustomRoleId', () => {
+ it('returns current custom role id', () => {
+ expect(
+ getters.getCurrentCustomRoleId(
+ { currentUser: { accounts: [{ id: 1, custom_role_id: 1 }] } },
+ { getCurrentAccountId: 1 }
+ )
+ ).toEqual(1);
+ });
+
+ it('returns undefined if account is not available', () => {
+ expect(
+ getters.getCurrentCustomRoleId(
+ { currentUser: { accounts: [{ id: 1, custom_role_id: 1 }] } },
+ { getCurrentAccountId: 2 }
+ )
+ ).toEqual(undefined);
+ });
+ });
+
describe('#getCurrentUserAvailability', () => {
it('returns correct availability status', () => {
expect(
diff --git a/app/javascript/dashboard/store/modules/specs/customRole/actions.spec.js b/app/javascript/dashboard/store/modules/specs/customRole/actions.spec.js
new file mode 100644
index 000000000..27a08e279
--- /dev/null
+++ b/app/javascript/dashboard/store/modules/specs/customRole/actions.spec.js
@@ -0,0 +1,97 @@
+import axios from 'axios';
+import { actions } from '../../customRole';
+import * as types from '../../../mutation-types';
+import { customRoleList } from './fixtures';
+
+const commit = vi.fn();
+global.axios = axios;
+vi.mock('axios');
+
+describe('#actions', () => {
+ describe('#getCustomRole', () => {
+ it('sends correct actions if API is success', async () => {
+ axios.get.mockResolvedValue({ data: customRoleList });
+ await actions.getCustomRole({ commit });
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { fetchingList: true }],
+ [types.default.SET_CUSTOM_ROLE, customRoleList],
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { fetchingList: false }],
+ ]);
+ });
+ it('sends correct actions if API is error', async () => {
+ axios.get.mockRejectedValue({ message: 'Incorrect header' });
+ await actions.getCustomRole({ commit });
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { fetchingList: true }],
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { fetchingList: false }],
+ ]);
+ });
+ });
+
+ describe('#createCustomRole', () => {
+ it('sends correct actions if API is success', async () => {
+ axios.post.mockResolvedValue({ data: customRoleList[0] });
+ await actions.createCustomRole({ commit }, customRoleList[0]);
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { creatingItem: true }],
+ [types.default.ADD_CUSTOM_ROLE, customRoleList[0]],
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { creatingItem: false }],
+ ]);
+ });
+ it('sends correct actions if API is error', async () => {
+ axios.post.mockRejectedValue({ message: 'Incorrect header' });
+ await expect(actions.createCustomRole({ commit })).rejects.toThrow(Error);
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { creatingItem: true }],
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { creatingItem: false }],
+ ]);
+ });
+ });
+
+ describe('#updateCustomRole', () => {
+ it('sends correct actions if API is success', async () => {
+ axios.patch.mockResolvedValue({ data: customRoleList[0] });
+ await actions.updateCustomRole(
+ { commit },
+ { id: 1, ...customRoleList[0] }
+ );
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { updatingItem: true }],
+ [types.default.EDIT_CUSTOM_ROLE, customRoleList[0]],
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { updatingItem: false }],
+ ]);
+ });
+ it('sends correct actions if API is error', async () => {
+ axios.patch.mockRejectedValue({ message: 'Incorrect header' });
+ await expect(
+ actions.updateCustomRole({ commit }, { id: 1 })
+ ).rejects.toThrow(Error);
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { updatingItem: true }],
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { updatingItem: false }],
+ ]);
+ });
+ });
+
+ describe('#deleteCustomRole', () => {
+ it('sends correct actions if API is success', async () => {
+ axios.delete.mockResolvedValue({ data: customRoleList[0] });
+ await actions.deleteCustomRole({ commit }, 1);
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { deletingItem: true }],
+ [types.default.DELETE_CUSTOM_ROLE, 1],
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { deletingItem: true }],
+ ]);
+ });
+ it('sends correct actions if API is error', async () => {
+ axios.delete.mockRejectedValue({ message: 'Incorrect header' });
+ await expect(actions.deleteCustomRole({ commit }, 1)).rejects.toThrow(
+ Error
+ );
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { deletingItem: true }],
+ [types.default.SET_CUSTOM_ROLE_UI_FLAG, { deletingItem: true }],
+ ]);
+ });
+ });
+});
diff --git a/app/javascript/dashboard/store/modules/specs/customRole/fixtures.js b/app/javascript/dashboard/store/modules/specs/customRole/fixtures.js
new file mode 100644
index 000000000..dafe77a48
--- /dev/null
+++ b/app/javascript/dashboard/store/modules/specs/customRole/fixtures.js
@@ -0,0 +1,77 @@
+export const customRoleList = [
+ {
+ id: 1,
+ name: 'Super Custom Role',
+ description: 'Role with all available custom role permissions',
+ permissions: [
+ 'conversation_participating_manage',
+ 'conversation_unassigned_manage',
+ 'conversation_manage',
+ 'contact_manage',
+ 'report_manage',
+ 'knowledge_base_manage',
+ ],
+ created_at: '2024-09-04T05:30:22.282Z',
+ updated_at: '2024-09-05T09:21:02.844Z',
+ },
+ {
+ id: 2,
+ name: 'Conversation Manager Role',
+ description: 'Role for managing all aspects of conversations',
+ permissions: [
+ 'conversation_unassigned_manage',
+ 'conversation_participating_manage',
+ 'conversation_manage',
+ ],
+ created_at: '2024-09-05T09:21:38.692Z',
+ updated_at: '2024-09-05T09:21:38.692Z',
+ },
+ {
+ id: 3,
+ name: 'Participating Agent Role',
+ description: 'Role for agents participating in conversations',
+ permissions: ['conversation_participating_manage'],
+ created_at: '2024-09-06T08:03:14.550Z',
+ updated_at: '2024-09-06T08:03:14.550Z',
+ },
+ {
+ id: 4,
+ name: 'Contact Manager Role',
+ description: 'Role for managing contacts only',
+ permissions: ['contact_manage'],
+ created_at: '2024-09-06T08:15:56.877Z',
+ updated_at: '2024-09-06T09:53:28.103Z',
+ },
+ {
+ id: 5,
+ name: 'Report Analyst Role',
+ description: 'Role for accessing and managing reports',
+ permissions: ['report_manage'],
+ created_at: '2024-09-06T09:53:58.277Z',
+ updated_at: '2024-09-06T09:53:58.277Z',
+ },
+ {
+ id: 6,
+ name: 'Knowledge Base Editor Role',
+ description: 'Role for managing the knowledge base',
+ permissions: ['knowledge_base_manage'],
+ created_at: '2024-09-06T09:54:27.649Z',
+ updated_at: '2024-09-06T09:54:27.649Z',
+ },
+ {
+ id: 7,
+ name: 'Unassigned Queue Manager Role',
+ description: 'Role for managing unassigned conversations',
+ permissions: ['conversation_unassigned_manage'],
+ created_at: '2024-09-06T09:55:00.503Z',
+ updated_at: '2024-09-06T09:55:00.503Z',
+ },
+ {
+ id: 8,
+ name: 'Basic Conversation Handler Role',
+ description: 'Role for basic conversation management',
+ permissions: ['conversation_manage'],
+ created_at: '2024-09-06T09:55:19.519Z',
+ updated_at: '2024-09-06T09:55:19.519Z',
+ },
+];
diff --git a/app/javascript/dashboard/store/modules/specs/customRole/getters.spec.js b/app/javascript/dashboard/store/modules/specs/customRole/getters.spec.js
new file mode 100644
index 000000000..96a782e28
--- /dev/null
+++ b/app/javascript/dashboard/store/modules/specs/customRole/getters.spec.js
@@ -0,0 +1,26 @@
+import { getters } from '../../customRole';
+import { customRoleList } from './fixtures';
+
+describe('#getters', () => {
+ it('getCustomRoles', () => {
+ const state = { records: customRoleList };
+ expect(getters.getCustomRoles(state)).toEqual(customRoleList);
+ });
+
+ it('getUIFlags', () => {
+ const state = {
+ uiFlags: {
+ fetchingList: true,
+ creatingItem: false,
+ updatingItem: false,
+ deletingItem: false,
+ },
+ };
+ expect(getters.getUIFlags(state)).toEqual({
+ fetchingList: true,
+ creatingItem: false,
+ updatingItem: false,
+ deletingItem: false,
+ });
+ });
+});
diff --git a/app/javascript/dashboard/store/modules/specs/customRole/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/customRole/mutations.spec.js
new file mode 100644
index 000000000..1a099dd02
--- /dev/null
+++ b/app/javascript/dashboard/store/modules/specs/customRole/mutations.spec.js
@@ -0,0 +1,48 @@
+import types from '../../../mutation-types';
+import { mutations } from '../../customRole';
+import { customRoleList } from './fixtures';
+
+describe('#mutations', () => {
+ describe('#SET_CUSTOM_ROLE', () => {
+ it('set custom role records', () => {
+ const state = { records: [] };
+ mutations[types.SET_CUSTOM_ROLE](state, customRoleList);
+ expect(state.records).toEqual(customRoleList);
+ });
+ });
+
+ describe('#ADD_CUSTOM_ROLE', () => {
+ it('push newly created custom role to the store', () => {
+ const state = { records: [customRoleList[0]] };
+ mutations[types.ADD_CUSTOM_ROLE](state, customRoleList[1]);
+ expect(state.records).toEqual([customRoleList[0], customRoleList[1]]);
+ });
+ });
+
+ describe('#EDIT_CUSTOM_ROLE', () => {
+ it('update custom role record', () => {
+ const state = { records: [customRoleList[0]] };
+ const updatedRole = { ...customRoleList[0], name: 'Updated Role' };
+ mutations[types.EDIT_CUSTOM_ROLE](state, updatedRole);
+ expect(state.records).toEqual([updatedRole]);
+ });
+ });
+
+ describe('#DELETE_CUSTOM_ROLE', () => {
+ it('delete custom role record', () => {
+ const state = { records: [customRoleList[0], customRoleList[1]] };
+ mutations[types.DELETE_CUSTOM_ROLE](state, customRoleList[0].id);
+ expect(state.records).toEqual([customRoleList[1]]);
+ });
+ });
+
+ describe('#SET_CUSTOM_ROLE_UI_FLAG', () => {
+ it('set custom role UI flags', () => {
+ const state = { uiFlags: {} };
+ mutations[types.SET_CUSTOM_ROLE_UI_FLAG](state, {
+ fetchingList: true,
+ });
+ expect(state.uiFlags).toEqual({ fetchingList: true });
+ });
+ });
+});
diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js
index ef87a87d8..2a6bcacb8 100644
--- a/app/javascript/dashboard/store/mutation-types.js
+++ b/app/javascript/dashboard/store/mutation-types.js
@@ -98,6 +98,13 @@ export default {
EDIT_CANNED: 'EDIT_CANNED',
DELETE_CANNED: 'DELETE_CANNED',
+ // Custom Role
+ SET_CUSTOM_ROLE_UI_FLAG: 'SET_CUSTOM_ROLE_UI_FLAG',
+ SET_CUSTOM_ROLE: 'SET_CUSTOM_ROLE',
+ ADD_CUSTOM_ROLE: 'ADD_CUSTOM_ROLE',
+ EDIT_CUSTOM_ROLE: 'EDIT_CUSTOM_ROLE',
+ DELETE_CUSTOM_ROLE: 'DELETE_CUSTOM_ROLE',
+
// Labels
SET_LABEL_UI_FLAG: 'SET_LABEL_UI_FLAG',
SET_LABELS: 'SET_LABELS',
diff --git a/app/javascript/shared/components/FluentIcon/dashboard-icons.json b/app/javascript/shared/components/FluentIcon/dashboard-icons.json
index 04255c560..918d012be 100644
--- a/app/javascript/shared/components/FluentIcon/dashboard-icons.json
+++ b/app/javascript/shared/components/FluentIcon/dashboard-icons.json
@@ -271,15 +271,9 @@
"M11.1667 17.8333C14.8486 17.8333 17.8333 14.8486 17.8333 11.1667C17.8333 7.48477 14.8486 4.5 11.1667 4.5C7.48477 4.5 4.5 7.48477 4.5 11.1667C4.5 14.8486 7.48477 17.8333 11.1667 17.8333Z",
"M19.5001 19.5001L15.9167 15.9167"
],
- "chevrons-left-outline": [
- "m11 17-5-5 5-5",
- "m18 17-5-5 5-5"
- ],
+ "chevrons-left-outline": ["m11 17-5-5 5-5", "m18 17-5-5 5-5"],
"chevron-left-single-outline": "m15 18-6-6 6-6",
- "chevrons-right-outline": [
- "m6 17 5-5-5-5",
- "m13 17 5-5-5-5"
- ],
+ "chevrons-right-outline": ["m6 17 5-5-5-5", "m13 17 5-5-5-5"],
"chevron-right-single-outline": "m9 18 6-6-6-6",
"avatar-upload-outline": "M19.754 11a.75.75 0 0 1 .743.648l.007.102v7a3.25 3.25 0 0 1-3.065 3.246l-.185.005h-11a3.25 3.25 0 0 1-3.244-3.066l-.006-.184V11.75a.75.75 0 0 1 1.494-.102l.006.102v7a1.75 1.75 0 0 0 1.607 1.745l.143.006h11A1.75 1.75 0 0 0 19 18.894l.005-.143V11.75a.75.75 0 0 1 .75-.75ZM6.22 7.216l4.996-4.996a.75.75 0 0 1 .976-.073l.084.072l5.005 4.997a.75.75 0 0 1-.976 1.134l-.084-.073l-3.723-3.716l.001 11.694a.75.75 0 0 1-.648.743l-.102.007a.75.75 0 0 1-.743-.648L11 16.255V4.558L7.28 8.277a.75.75 0 0 1-.976.073l-.084-.073a.75.75 0 0 1-.073-.977l.073-.084l4.996-4.996L6.22 7.216Z",
"text-copy-outline": "M5.503 4.627L5.5 6.75v10.504a3.25 3.25 0 0 0 3.25 3.25h8.616a2.25 2.25 0 0 1-2.122 1.5H8.75A4.75 4.75 0 0 1 4 17.254V6.75c0-.98.627-1.815 1.503-2.123M17.75 2A2.25 2.25 0 0 1 20 4.25v13a2.25 2.25 0 0 1-2.25 2.25h-9a2.25 2.25 0 0 1-2.25-2.25v-13A2.25 2.25 0 0 1 8.75 2zm0 1.5h-9a.75.75 0 0 0-.75.75v13c0 .414.336.75.75.75h9a.75.75 0 0 0 .75-.75v-13a.75.75 0 0 0-.75-.75",
@@ -290,5 +284,6 @@
"M6.49573 9.20645C6.49573 8.67008 6.93058 8.23523 7.46695 8.23523C8.00337 8.23523 8.43817 8.67008 8.43817 9.20645V11.4511C8.43817 11.9875 8.00337 12.4223 7.46695 12.4223C6.93058 12.4223 6.49573 11.9875 6.49573 11.4511V9.20645Z",
"M9.60364 9.20645C9.60364 8.67008 10.0385 8.23523 10.5749 8.23523C11.1113 8.23523 11.5461 8.67008 11.5461 9.20645V11.4511C11.5461 11.9875 11.1113 12.4223 10.5749 12.4223C10.0385 12.4223 9.60364 11.9875 9.60364 11.4511V9.20645Z",
"M17.1442 5.57049C13.5275 5.06019 10.5793 5.04007 6.88135 5.56825C5.9466 5.70176 5.32812 5.79197 4.85654 5.92976C4.41928 6.05757 4.17061 6.20994 3.96492 6.43984C3.539 6.91583 3.48286 7.45419 3.4248 9.33184C3.36775 11.1772 3.48076 12.831 3.69481 14.6918C3.80887 15.6834 3.88736 16.3526 4.01268 16.8613C4.13155 17.3439 4.27532 17.6034 4.47513 17.802C4.67654 18.0023 4.93467 18.1435 5.40841 18.2581C5.90952 18.3793 6.56702 18.4526 7.5442 18.5592C10.7045 18.904 13.0702 18.9022 16.2423 18.561C17.2313 18.4546 17.8995 18.3813 18.4081 18.2609C18.8913 18.1465 19.1511 18.0063 19.3497 17.8118C19.5442 17.6213 19.6928 17.3587 19.8217 16.852C19.9561 16.3234 20.0476 15.624 20.18 14.5966C20.4162 12.7633 20.5863 11.1533 20.5929 9.3896C20.5999 7.50391 20.5613 6.96737 20.1306 6.46971C19.9226 6.22932 19.6696 6.0713 19.2224 5.93968C18.7395 5.79754 18.1042 5.70594 17.1442 5.57049ZM6.65555 3.98715C10.5078 3.43695 13.6072 3.45849 17.3674 3.98902L17.4224 3.99678C18.3127 4.12235 19.0648 4.22844 19.6733 4.40753C20.33 4.60078 20.8792 4.89417 21.3382 5.4245C22.2041 6.42482 22.1984 7.6117 22.1909 9.18858C22.1905 9.25686 22.1902 9.32584 22.19 9.3956C22.183 11.2604 22.0026 12.949 21.764 14.8006L21.7577 14.8496C21.6332 15.8159 21.5307 16.6121 21.3695 17.2458C21.2 17.9121 20.9467 18.4833 20.4672 18.9529C19.9919 19.4183 19.4302 19.6602 18.776 19.8151C18.1582 19.9613 17.3895 20.044 16.4629 20.1436L16.4131 20.149C13.1283 20.5023 10.6472 20.5043 7.37097 20.1469L7.32043 20.1414C6.40679 20.0417 5.64604 19.9587 5.03292 19.8104C4.38112 19.6527 3.82317 19.406 3.34911 18.9347C2.87346 18.4618 2.62363 17.8999 2.46191 17.2433C2.30938 16.6241 2.22071 15.8531 2.11393 14.9246L2.10815 14.8743C1.88863 12.9659 1.76823 11.23 1.82845 9.28246C1.83063 9.2118 1.83272 9.14191 1.83479 9.07281C1.8816 7.50776 1.91671 6.33374 2.7747 5.37486C3.22992 4.86612 3.76798 4.58399 4.40853 4.39678C5.00257 4.22316 5.73505 4.11858 6.60207 3.99479C6.61981 3.99225 6.63764 3.9897 6.65555 3.98715Z"
- ]
+ ],
+ "scan-person-outline": "M5.25 3.5A1.75 1.75 0 0 0 3.5 5.25v3a.75.75 0 0 1-1.5 0v-3A3.25 3.25 0 0 1 5.25 2h3a.75.75 0 0 1 0 1.5zm0 17a1.75 1.75 0 0 1-1.75-1.75v-3a.75.75 0 0 0-1.5 0v3A3.25 3.25 0 0 0 5.25 22h3a.75.75 0 0 0 .707-1l-.005-.015a.75.75 0 0 0-.702-.485zM20.5 5.25a1.75 1.75 0 0 0-1.75-1.75h-3a.75.75 0 0 1 0-1.5h3A3.25 3.25 0 0 1 22 5.25v3a.75.75 0 0 1-1.5 0zM18.75 20.5a1.75 1.75 0 0 0 1.75-1.75v-3a.75.75 0 0 1 1.5 0v3A3.25 3.25 0 0 1 18.75 22h-3a.75.75 0 0 1 0-1.5zM6.5 18.616q0 .465.258.884H5.25a1 1 0 0 1-.129-.011A3.1 3.1 0 0 1 5 18.616v-.366A2.25 2.25 0 0 1 7.25 16h9.5A2.25 2.25 0 0 1 19 18.25v.366c0 .31-.047.601-.132.875a1 1 0 0 1-.118.009h-1.543a1.56 1.56 0 0 0 .293-.884v-.366a.75.75 0 0 0-.75-.75h-9.5a.75.75 0 0 0-.75.75zm8.25-8.866a2.75 2.75 0 1 0-5.5 0a2.75 2.75 0 0 0 5.5 0m1.5 0a4.25 4.25 0 1 1-8.5 0a4.25 4.25 0 0 1 8.5 0"
}
diff --git a/app/policies/article_policy.rb b/app/policies/article_policy.rb
index 4bfa7825e..e48442337 100644
--- a/app/policies/article_policy.rb
+++ b/app/policies/article_policy.rb
@@ -33,3 +33,5 @@ class ArticlePolicy < ApplicationPolicy
@record.first.portal.members.include?(@user)
end
end
+
+ArticlePolicy.prepend_mod_with('Enterprise::ArticlePolicy')
diff --git a/app/policies/category_policy.rb b/app/policies/category_policy.rb
index 017906b55..3dca6b847 100644
--- a/app/policies/category_policy.rb
+++ b/app/policies/category_policy.rb
@@ -29,3 +29,5 @@ class CategoryPolicy < ApplicationPolicy
@record.first.portal.members.include?(@user)
end
end
+
+CategoryPolicy.prepend_mod_with('Enterprise::CategoryPolicy')
diff --git a/app/policies/portal_policy.rb b/app/policies/portal_policy.rb
index 588fd82e8..c52b44cac 100644
--- a/app/policies/portal_policy.rb
+++ b/app/policies/portal_policy.rb
@@ -37,3 +37,5 @@ class PortalPolicy < ApplicationPolicy
@record.first.members.include?(@user)
end
end
+
+PortalPolicy.prepend_mod_with('Enterprise::PortalPolicy')
diff --git a/app/policies/report_policy.rb b/app/policies/report_policy.rb
index 7e7eeffdf..a52607a67 100644
--- a/app/policies/report_policy.rb
+++ b/app/policies/report_policy.rb
@@ -3,3 +3,5 @@ class ReportPolicy < ApplicationPolicy
@account_user.administrator?
end
end
+
+ReportPolicy.prepend_mod_with('Enterprise::ReportPolicy')
diff --git a/app/views/api/v1/models/_user.json.jbuilder b/app/views/api/v1/models/_user.json.jbuilder
index 3d8131f60..b6a448050 100644
--- a/app/views/api/v1/models/_user.json.jbuilder
+++ b/app/views/api/v1/models/_user.json.jbuilder
@@ -30,5 +30,6 @@ json.accounts do
# availability derived from presence
json.availability_status account_user.availability_status
json.auto_offline account_user.auto_offline
+ json.partial! 'api/v1/models/account_user', account_user: account_user if ChatwootApp.enterprise?
end
end
diff --git a/config/features.yml b/config/features.yml
index 42cf3460d..4f62694f8 100644
--- a/config/features.yml
+++ b/config/features.yml
@@ -89,3 +89,6 @@
enabled: false
- name: captain_integration
enabled: false
+- name: custom_roles
+ enabled: false
+ premium: true
diff --git a/enterprise/app/controllers/enterprise/api/v1/accounts/agents_controller.rb b/enterprise/app/controllers/enterprise/api/v1/accounts/agents_controller.rb
index c8a68c427..b3a27c4ad 100644
--- a/enterprise/app/controllers/enterprise/api/v1/accounts/agents_controller.rb
+++ b/enterprise/app/controllers/enterprise/api/v1/accounts/agents_controller.rb
@@ -1,9 +1,17 @@
module Enterprise::Api::V1::Accounts::AgentsController
- def account_user_attributes
- super + [:custom_role_id]
+ def create
+ super
+ associate_agent_with_custom_role
end
- def allowed_agent_params
- super + [:custom_role_id]
+ def update
+ super
+ associate_agent_with_custom_role
+ end
+
+ private
+
+ def associate_agent_with_custom_role
+ @agent.current_account_user.update!(custom_role_id: params[:custom_role_id])
end
end
diff --git a/enterprise/app/controllers/enterprise/api/v2/accounts/reports_controller.rb b/enterprise/app/controllers/enterprise/api/v2/accounts/reports_controller.rb
new file mode 100644
index 000000000..b5e2a9efe
--- /dev/null
+++ b/enterprise/app/controllers/enterprise/api/v2/accounts/reports_controller.rb
@@ -0,0 +1,7 @@
+module Enterprise::Api::V2::Accounts::ReportsController
+ def check_authorization
+ return if Current.account_user.custom_role&.permissions&.include?('report_manage')
+
+ super
+ end
+end
diff --git a/enterprise/app/policies/enterprise/article_policy.rb b/enterprise/app/policies/enterprise/article_policy.rb
new file mode 100644
index 000000000..745db0fc9
--- /dev/null
+++ b/enterprise/app/policies/enterprise/article_policy.rb
@@ -0,0 +1,29 @@
+module Enterprise::ArticlePolicy
+ def index?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def update?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def show?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def edit?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def create?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def destroy?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def reorder?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+end
diff --git a/enterprise/app/policies/enterprise/category_policy.rb b/enterprise/app/policies/enterprise/category_policy.rb
new file mode 100644
index 000000000..b58a3806d
--- /dev/null
+++ b/enterprise/app/policies/enterprise/category_policy.rb
@@ -0,0 +1,25 @@
+module Enterprise::CategoryPolicy
+ def index?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def update?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def show?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def edit?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def create?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def destroy?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+end
diff --git a/enterprise/app/policies/enterprise/portal_policy.rb b/enterprise/app/policies/enterprise/portal_policy.rb
new file mode 100644
index 000000000..45c3ea140
--- /dev/null
+++ b/enterprise/app/policies/enterprise/portal_policy.rb
@@ -0,0 +1,33 @@
+module Enterprise::PortalPolicy
+ def index?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def update?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def show?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def edit?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def create?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def destroy?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def add_members?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+
+ def logo?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || super
+ end
+end
diff --git a/enterprise/app/policies/enterprise/report_policy.rb b/enterprise/app/policies/enterprise/report_policy.rb
new file mode 100644
index 000000000..0ad1fec6a
--- /dev/null
+++ b/enterprise/app/policies/enterprise/report_policy.rb
@@ -0,0 +1,5 @@
+module Enterprise::ReportPolicy
+ def view?
+ @account_user.custom_role&.permissions&.include?('report_manage') || super
+ end
+end
diff --git a/enterprise/app/views/api/v1/models/_account_user.json.jbuilder b/enterprise/app/views/api/v1/models/_account_user.json.jbuilder
new file mode 100644
index 000000000..8a822e65a
--- /dev/null
+++ b/enterprise/app/views/api/v1/models/_account_user.json.jbuilder
@@ -0,0 +1,2 @@
+json.custom_role_id account_user&.custom_role_id
+json.custom_role account_user&.custom_role&.as_json(only: [:id, :name, :description, :permissions])
diff --git a/spec/enterprise/controllers/api/v1/profiles_controller_spec.rb b/spec/enterprise/controllers/api/v1/profiles_controller_spec.rb
new file mode 100644
index 000000000..aa75451ad
--- /dev/null
+++ b/spec/enterprise/controllers/api/v1/profiles_controller_spec.rb
@@ -0,0 +1,28 @@
+require 'rails_helper'
+
+RSpec.describe 'Profile API', type: :request do
+ describe 'GET /api/v1/profile' do
+ let(:account) { create(:account) }
+ let!(:custom_role_account) { create(:account, name: 'Custom Role Account') }
+ let!(:custom_role) { create(:custom_role, name: 'Custom Role', account: custom_role_account) }
+ let!(:agent) { create(:user, account: account, custom_attributes: { test: 'test' }, role: :agent) }
+
+ before do
+ create(:account_user, account: custom_role_account, user: agent, custom_role: custom_role)
+ end
+
+ context 'when it is an authenticated user' do
+ it 'returns user custom role information' do
+ get '/api/v1/profile',
+ headers: agent.create_new_auth_token,
+ as: :json
+
+ parsed_response = response.parsed_body
+ # map accounts object and make sure custom role id and name are present
+ role_account = parsed_response['accounts'].find { |account| account['id'] == custom_role_account.id }
+ expect(role_account['custom_role']['id']).to eq(custom_role.id)
+ expect(role_account['custom_role']['name']).to eq(custom_role.name)
+ end
+ end
+ end
+end
diff --git a/spec/enterprise/controllers/enterprise/api/v1/accounts/agents_controller_spec.rb b/spec/enterprise/controllers/enterprise/api/v1/accounts/agents_controller_spec.rb
index aa1a99c5d..fbffe088e 100644
--- a/spec/enterprise/controllers/enterprise/api/v1/accounts/agents_controller_spec.rb
+++ b/spec/enterprise/controllers/enterprise/api/v1/accounts/agents_controller_spec.rb
@@ -3,10 +3,25 @@ require 'rails_helper'
RSpec.describe 'Enterprise Agents API', type: :request do
let(:account) { create(:account) }
let(:admin) { create(:user, account: account, role: :administrator) }
+ let!(:custom_role) { create(:custom_role, account: account) }
+
+ describe 'POST /api/v1/accounts/{account.id}/agents' do
+ let(:params) { { email: 'test@example.com', name: 'Test User', role: 'agent', custom_role_id: custom_role.id } }
+
+ context 'when it is an authenticated administrator' do
+ it 'creates an agent with the specified custom role' do
+ post "/api/v1/accounts/#{account.id}/agents", headers: admin.create_new_auth_token, params: params, as: :json
+
+ expect(response).to have_http_status(:success)
+ agent = account.agents.last
+ expect(agent.account_users.first.custom_role_id).to eq(custom_role.id)
+ expect(JSON.parse(response.body)['custom_role_id']).to eq(custom_role.id)
+ end
+ end
+ end
describe 'PUT /api/v1/accounts/{account.id}/agents/:id' do
let(:other_agent) { create(:user, account: account, role: :agent) }
- let!(:custom_role) { create(:custom_role, account: account) }
context 'when it is an authenticated administrator' do
it 'modified the custom role of the agent' do
|