Allows contact managers to export and import contacts from the Contacts page while keeping plain agents blocked. The contacts action menu now mirrors backend permissions for both export and import. ## Closes - https://linear.app/chatwoot/issue/CW-4438/contact-export-is-broken ## What changed - Allows Enterprise custom roles with `contact_manage` to pass `ContactPolicy#export?` and `ContactPolicy#import?`. - Shows Export and Import to admins and contact managers only. - Adds Enterprise policy coverage for contact export and import. ## Screenshots Admin: Export and Import are available. <img width="3840" height="2160" alt="Admin contact actions with Export and Import visible" src="https://github.com/user-attachments/assets/2b2cdaf2-ca8f-470d-be34-31cba68b9dce" /> Contact manager: Export and Import are available. <img width="3840" height="2160" alt="Contact manager contact actions with Export and Import visible" src="https://github.com/user-attachments/assets/48fc038b-2e78-4d0c-ba17-a5965641bd88" /> Regular agent: Export and Import are hidden. <img width="3840" height="2160" alt="Regular agent contact actions with Export and Import hidden" src="https://github.com/user-attachments/assets/a63b5731-743a-4223-8dab-ce58383067fe" /> ## How to test - Sign in as an administrator and open Contacts; the action menu shows Export and Import. - Sign in as a custom-role user with `contact_manage`; the action menu shows Export and Import. - Sign in as a plain agent; Export and Import are not available and both APIs remain unauthorized.
77 lines
2.0 KiB
Vue
77 lines
2.0 KiB
Vue
<script setup>
|
|
import { computed, ref } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import Button from 'dashboard/components-next/button/Button.vue';
|
|
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
|
import { usePolicy } from 'dashboard/composables/usePolicy';
|
|
|
|
const emit = defineEmits(['add', 'import', 'export']);
|
|
|
|
const { t } = useI18n();
|
|
const { checkPermissions } = usePolicy();
|
|
|
|
const contactMenuItems = computed(() => [
|
|
{
|
|
label: t('CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.ADD_CONTACT'),
|
|
action: 'add',
|
|
value: 'add',
|
|
icon: 'i-lucide-plus',
|
|
},
|
|
...(checkPermissions(['administrator', 'contact_manage'])
|
|
? [
|
|
{
|
|
label: t(
|
|
'CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.EXPORT_CONTACT'
|
|
),
|
|
action: 'export',
|
|
value: 'export',
|
|
icon: 'i-lucide-upload',
|
|
},
|
|
]
|
|
: []),
|
|
...(checkPermissions(['administrator', 'contact_manage'])
|
|
? [
|
|
{
|
|
label: t(
|
|
'CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.IMPORT_CONTACT'
|
|
),
|
|
action: 'import',
|
|
value: 'import',
|
|
icon: 'i-lucide-download',
|
|
},
|
|
]
|
|
: []),
|
|
]);
|
|
const showActionsDropdown = ref(false);
|
|
|
|
const handleContactAction = ({ action }) => {
|
|
if (action === 'add') {
|
|
emit('add');
|
|
} else if (action === 'import') {
|
|
emit('import');
|
|
} else if (action === 'export') {
|
|
emit('export');
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div v-on-clickaway="() => (showActionsDropdown = false)" class="relative">
|
|
<Button
|
|
icon="i-lucide-ellipsis-vertical"
|
|
color="slate"
|
|
variant="ghost"
|
|
size="sm"
|
|
:class="showActionsDropdown ? 'bg-n-alpha-2' : ''"
|
|
@click="showActionsDropdown = !showActionsDropdown"
|
|
/>
|
|
<DropdownMenu
|
|
v-if="showActionsDropdown"
|
|
:menu-items="contactMenuItems"
|
|
class="ltr:right-0 rtl:left-0 mt-1 w-52 top-full"
|
|
@action="handleContactAction($event)"
|
|
/>
|
|
</div>
|
|
</template>
|