Files
chatwoot/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleModal.vue
Sojan Jose 58e78621ba chore: Custom Roles to manage permissions [ UI ] (#9865)
In admin settings, this Pr will add the UI for managing custom roles (
ref: https://github.com/chatwoot/chatwoot/pull/9995 ). It also handles
the routing logic changes to accommodate fine-tuned permissions.

---------

Co-authored-by: Pranav <pranavrajs@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-09-17 11:40:11 -07:00

249 lines
7.1 KiB
Vue

<script setup>
import { ref, reactive, computed, onMounted, watch } from 'vue';
import { useStore } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import { useVuelidate } from '@vuelidate/core';
import { required, minLength } from '@vuelidate/validators';
import { useAlert } from 'dashboard/composables';
import {
AVAILABLE_CUSTOM_ROLE_PERMISSIONS,
MANAGE_ALL_CONVERSATION_PERMISSIONS,
CONVERSATION_UNASSIGNED_PERMISSIONS,
CONVERSATION_PARTICIPATING_PERMISSIONS,
} from 'dashboard/constants/permissions.js';
import WootSubmitButton from 'dashboard/components/buttons/FormSubmitButton.vue';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
const props = defineProps({
mode: {
type: String,
default: 'add',
validator: value => ['add', 'edit'].includes(value),
},
selectedRole: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(['close']);
const store = useStore();
const { t } = useI18n();
const name = ref('');
const description = ref('');
const selectedPermissions = ref([]);
const nameInput = ref(null);
const addCustomRole = reactive({
showLoading: false,
message: '',
});
const rules = computed(() => ({
name: { required, minLength: minLength(2) },
description: { required },
selectedPermissions: { required, minLength: minLength(1) },
}));
const v$ = useVuelidate(rules, { name, description, selectedPermissions });
const resetForm = () => {
name.value = '';
description.value = '';
selectedPermissions.value = [];
v$.value.$reset();
};
const populateEditForm = () => {
name.value = props.selectedRole.name || '';
description.value = props.selectedRole.description || '';
selectedPermissions.value = props.selectedRole.permissions || [];
};
watch(
selectedPermissions,
(newValue, oldValue) => {
// Check if manage all conversation permission is added or removed
const hasAddedManageAllConversation =
newValue.includes(MANAGE_ALL_CONVERSATION_PERMISSIONS) &&
!oldValue.includes(MANAGE_ALL_CONVERSATION_PERMISSIONS);
const hasRemovedManageAllConversation =
oldValue.includes(MANAGE_ALL_CONVERSATION_PERMISSIONS) &&
!newValue.includes(MANAGE_ALL_CONVERSATION_PERMISSIONS);
if (hasAddedManageAllConversation) {
// If manage all conversation permission is added,
// then add unassigned and participating permissions automatically
selectedPermissions.value = [
...new Set([
...selectedPermissions.value,
CONVERSATION_UNASSIGNED_PERMISSIONS,
CONVERSATION_PARTICIPATING_PERMISSIONS,
]),
];
} else if (hasRemovedManageAllConversation) {
// If manage all conversation permission is removed,
// then only remove manage all conversation permission
selectedPermissions.value = selectedPermissions.value.filter(
p => p !== MANAGE_ALL_CONVERSATION_PERMISSIONS
);
}
},
{ deep: true }
);
onMounted(() => {
if (props.mode === 'edit') {
populateEditForm();
}
// Focus the name input when mounted
nameInput.value?.focus();
});
const getTranslationKey = base => {
return props.mode === 'edit'
? `CUSTOM_ROLE.EDIT.${base}`
: `CUSTOM_ROLE.ADD.${base}`;
};
const modalTitle = computed(() => t(getTranslationKey('TITLE')));
const modalDescription = computed(() => t(getTranslationKey('DESC')));
const submitButtonText = computed(() => t(getTranslationKey('SUBMIT')));
const handleCustomRole = async () => {
v$.value.$touch();
if (v$.value.$invalid) return;
addCustomRole.showLoading = true;
try {
const roleData = {
name: name.value,
description: description.value,
permissions: selectedPermissions.value,
};
if (props.mode === 'edit') {
await store.dispatch('customRole/updateCustomRole', {
id: props.selectedRole.id,
...roleData,
});
useAlert(t('CUSTOM_ROLE.EDIT.API.SUCCESS_MESSAGE'));
} else {
await store.dispatch('customRole/createCustomRole', roleData);
useAlert(t('CUSTOM_ROLE.ADD.API.SUCCESS_MESSAGE'));
}
resetForm();
emit('close');
} catch (error) {
const errorMessage =
error?.message || t(`CUSTOM_ROLE.FORM.API.ERROR_MESSAGE`);
useAlert(errorMessage);
} finally {
addCustomRole.showLoading = false;
}
};
const isSubmitDisabled = computed(
() => v$.value.$invalid || addCustomRole.showLoading
);
</script>
<template>
<div class="flex flex-col h-auto overflow-auto">
<woot-modal-header
:header-title="modalTitle"
:header-content="modalDescription"
/>
<form class="flex flex-col w-full" @submit.prevent="handleCustomRole">
<div class="w-full">
<label :class="{ 'text-red-500': v$.name.$error }">
{{ $t('CUSTOM_ROLE.FORM.NAME.LABEL') }}
<input
ref="nameInput"
v-model.trim="name"
type="text"
:class="{ '!border-red-500': v$.name.$error }"
:placeholder="$t('CUSTOM_ROLE.FORM.NAME.PLACEHOLDER')"
@blur="v$.name.$touch"
/>
</label>
</div>
<div class="w-full">
<label :class="{ 'text-red-500': v$.description.$error }">
{{ $t('CUSTOM_ROLE.FORM.DESCRIPTION.LABEL') }}
</label>
<div class="editor-wrap">
<WootMessageEditor
v-model="description"
class="message-editor [&>div]:px-1 h-28"
:class="{ editor_warning: v$.description.$error }"
enable-variables
:focus-on-mount="false"
:enable-canned-responses="false"
:placeholder="$t('CUSTOM_ROLE.FORM.DESCRIPTION.PLACEHOLDER')"
@blur="v$.description.$touch"
/>
</div>
</div>
<div class="w-full">
<label :class="{ 'text-red-500': v$.selectedPermissions.$error }">
{{ $t('CUSTOM_ROLE.FORM.PERMISSIONS.LABEL') }}
</label>
<div class="flex flex-col gap-2.5 mb-4">
<div
v-for="permission in AVAILABLE_CUSTOM_ROLE_PERMISSIONS"
:key="permission"
class="flex items-center"
>
<input
:id="permission"
v-model="selectedPermissions"
type="checkbox"
:value="permission"
name="permissions"
class="ltr:mr-2 rtl:ml-2"
/>
<label :for="permission" class="text-sm">
{{ $t(`CUSTOM_ROLE.PERMISSIONS.${permission.toUpperCase()}`) }}
</label>
</div>
</div>
</div>
<div class="flex flex-row justify-end w-full gap-2 px-0 py-2">
<WootSubmitButton
:disabled="isSubmitDisabled"
:button-text="submitButtonText"
:loading="addCustomRole.showLoading"
/>
<button class="button clear" @click.prevent="emit('close')">
{{ $t('CUSTOM_ROLE.FORM.CANCEL_BUTTON_TEXT') }}
</button>
</div>
</form>
</div>
</template>
<style scoped lang="scss">
::v-deep {
.ProseMirror-menubar {
@apply hidden;
}
.ProseMirror-woot-style {
@apply max-h-[110px];
p {
@apply text-base;
}
}
}
</style>