Merge branch 'develop' into feat/github-integration
This commit is contained in:
@@ -1,21 +1,34 @@
|
||||
<script setup>
|
||||
import { watch, computed } from 'vue';
|
||||
import { watch, computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import ContactNoteItem from 'next/Contacts/ContactsSidebar/components/ContactNoteItem.vue';
|
||||
import Spinner from 'next/spinner/Spinner.vue';
|
||||
|
||||
const { contactId } = defineProps({
|
||||
contactId: { type: String, required: true },
|
||||
import Editor from 'dashboard/components-next/Editor/Editor.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import ContactNoteItem from 'next/Contacts/ContactsSidebar/components/ContactNoteItem.vue';
|
||||
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||
|
||||
const props = defineProps({
|
||||
contactId: { type: [String, Number], required: true },
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
const currentUser = useMapGetter('getCurrentUser');
|
||||
const uiFlags = useMapGetter('contactNotes/getUIFlags');
|
||||
const notesByContact = useMapGetter('contactNotes/getAllNotesByContactId');
|
||||
const isFetchingNotes = computed(() => uiFlags.value.isFetching);
|
||||
const notGetterFn = useMapGetter('contactNotes/getAllNotesByContactId');
|
||||
const notes = computed(() => notGetterFn.value(contactId));
|
||||
const isCreatingNote = computed(() => uiFlags.value.isCreating);
|
||||
const contactId = computed(() => props.contactId);
|
||||
const noteContent = ref('');
|
||||
const shouldShowCreateModal = ref(false);
|
||||
const notes = computed(() => {
|
||||
if (!contactId.value) {
|
||||
return [];
|
||||
}
|
||||
return notesByContact.value(contactId.value) || [];
|
||||
});
|
||||
|
||||
const getWrittenBy = ({ user } = {}) => {
|
||||
const currentUserId = currentUser.value?.id;
|
||||
@@ -24,28 +37,130 @@ const getWrittenBy = ({ user } = {}) => {
|
||||
: user?.name || t('CONVERSATION.BOT');
|
||||
};
|
||||
|
||||
const openCreateModal = () => {
|
||||
if (!contactId.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
noteContent.value = '';
|
||||
shouldShowCreateModal.value = true;
|
||||
};
|
||||
|
||||
const closeCreateModal = () => {
|
||||
shouldShowCreateModal.value = false;
|
||||
noteContent.value = '';
|
||||
};
|
||||
|
||||
const onAdd = async () => {
|
||||
if (!contactId.value || !noteContent.value || isCreatingNote.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await store.dispatch('contactNotes/create', {
|
||||
content: noteContent.value,
|
||||
contactId: contactId.value,
|
||||
});
|
||||
noteContent.value = '';
|
||||
closeCreateModal();
|
||||
};
|
||||
|
||||
const onDelete = noteId => {
|
||||
if (!contactId.value || !noteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
store.dispatch('contactNotes/delete', {
|
||||
noteId,
|
||||
contactId: contactId.value,
|
||||
});
|
||||
};
|
||||
|
||||
const keyboardEvents = {
|
||||
'$mod+Enter': {
|
||||
action: onAdd,
|
||||
allowOnFocusedInput: true,
|
||||
},
|
||||
};
|
||||
|
||||
useKeyboardEvents(keyboardEvents);
|
||||
|
||||
watch(
|
||||
() => contactId,
|
||||
() => store.dispatch('contactNotes/get', { contactId }),
|
||||
contactId,
|
||||
newContactId => {
|
||||
closeCreateModal();
|
||||
if (newContactId) {
|
||||
store.dispatch('contactNotes/get', { contactId: newContactId });
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isFetchingNotes" class="p-8 grid place-content-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
<div v-else-if="!notes.length" class="p-8 grid place-content-center">
|
||||
<p class="text-center">{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.NO_NOTES') }}</p>
|
||||
</div>
|
||||
<div v-else class="max-h-[300px] overflow-scroll">
|
||||
<ContactNoteItem
|
||||
v-for="note in notes"
|
||||
:key="note.id"
|
||||
class="p-4 last-of-type:border-b-0"
|
||||
:note="note"
|
||||
collapsible
|
||||
:written-by="getWrittenBy(note)"
|
||||
/>
|
||||
<div>
|
||||
<div class="px-4 pt-3 pb-2">
|
||||
<NextButton
|
||||
ghost
|
||||
xs
|
||||
icon="i-lucide-plus"
|
||||
:label="$t('CONTACTS_LAYOUT.SIDEBAR.NOTES.ADD_NOTE')"
|
||||
:disabled="!contactId || isFetchingNotes"
|
||||
@click="openCreateModal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isFetchingNotes"
|
||||
class="flex items-center justify-center py-8 text-n-slate-11"
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="notes.length"
|
||||
class="flex flex-col max-h-[300px] overflow-y-auto"
|
||||
>
|
||||
<ContactNoteItem
|
||||
v-for="note in notes"
|
||||
:key="note.id"
|
||||
class="py-4 last-of-type:border-b-0 px-4"
|
||||
:note="note"
|
||||
:written-by="getWrittenBy(note)"
|
||||
allow-delete
|
||||
collapsible
|
||||
@delete="onDelete"
|
||||
/>
|
||||
</div>
|
||||
<p v-else class="px-6 py-6 text-sm leading-6 text-center text-n-slate-11">
|
||||
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.CONVERSATION_EMPTY_STATE') }}
|
||||
</p>
|
||||
|
||||
<woot-modal
|
||||
v-model:show="shouldShowCreateModal"
|
||||
:on-close="closeCreateModal"
|
||||
:close-on-backdrop-click="false"
|
||||
class="!items-start [&>div]:!top-12 [&>div]:sticky"
|
||||
>
|
||||
<div class="flex w-full flex-col gap-6 px-6 py-6">
|
||||
<h3 class="text-lg font-semibold text-n-slate-12">
|
||||
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.ADD_NOTE') }}
|
||||
</h3>
|
||||
<Editor
|
||||
v-model="noteContent"
|
||||
focus-on-mount
|
||||
:placeholder="t('CONTACTS_LAYOUT.SIDEBAR.NOTES.PLACEHOLDER')"
|
||||
class="[&>div]:!border-transparent [&>div]:px-4 [&>div]:py-4"
|
||||
/>
|
||||
<div class="flex items-center justify-end gap-3">
|
||||
<NextButton
|
||||
solid
|
||||
blue
|
||||
:label="t('CONTACTS_LAYOUT.SIDEBAR.NOTES.SAVE')"
|
||||
:is-loading="isCreatingNote"
|
||||
:disabled="!noteContent || isCreatingNote"
|
||||
@click="onAdd"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+12
-1
@@ -1,10 +1,14 @@
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
defineProps({
|
||||
title: { type: String, required: true },
|
||||
description: { type: String, required: true },
|
||||
withBorder: { type: Boolean, default: false },
|
||||
hideContent: { type: Boolean, default: false },
|
||||
beta: { type: Boolean, default: false },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -17,8 +21,15 @@ defineProps({
|
||||
>
|
||||
<header class="grid grid-cols-4">
|
||||
<div class="col-span-3">
|
||||
<h4 class="text-lg font-medium text-n-slate-12">
|
||||
<h4 class="text-lg font-medium text-n-slate-12 flex items-center gap-2">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
<div
|
||||
v-if="beta"
|
||||
v-tooltip.top="t('GENERAL.BETA_DESCRIPTION')"
|
||||
class="text-xs uppercase text-n-iris-11 border border-1 border-n-iris-10 leading-none rounded-lg px-1 py-0.5"
|
||||
>
|
||||
{{ t('GENERAL.BETA') }}
|
||||
</div>
|
||||
</h4>
|
||||
<p class="text-n-slate-11 text-sm mt-2">
|
||||
<slot name="description">{{ description }}</slot>
|
||||
|
||||
@@ -30,6 +30,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
provider: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
customRoleId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
@@ -203,6 +207,7 @@ const resetPassword = async () => {
|
||||
<div class="flex flex-row justify-start w-full gap-2 px-0 py-2">
|
||||
<div class="w-[50%] ltr:text-left rtl:text-right">
|
||||
<Button
|
||||
v-if="provider !== 'saml'"
|
||||
ghost
|
||||
type="button"
|
||||
icon="i-lucide-lock-keyhole"
|
||||
|
||||
@@ -261,6 +261,7 @@ const confirmDeletion = () => {
|
||||
v-if="showEditPopup"
|
||||
:id="currentAgent.id"
|
||||
:name="currentAgent.name"
|
||||
:provider="currentAgent.provider"
|
||||
:type="currentAgent.role"
|
||||
:email="currentAgent.email"
|
||||
:availability="currentAgent.availability_status"
|
||||
|
||||
@@ -206,7 +206,8 @@ export default {
|
||||
this.isASmsInbox ||
|
||||
this.isAWhatsAppChannel ||
|
||||
this.isAFacebookInbox ||
|
||||
this.isAPIInbox
|
||||
this.isAPIInbox ||
|
||||
this.isATelegramChannel
|
||||
);
|
||||
},
|
||||
inboxNameLabel() {
|
||||
@@ -341,7 +342,7 @@ export default {
|
||||
try {
|
||||
const payload = {
|
||||
id: this.currentInboxId,
|
||||
name: this.selectedInboxName,
|
||||
name: this.selectedInboxName?.trim(),
|
||||
enable_email_collect: this.emailCollectEnabled,
|
||||
allow_messages_after_resolved: this.allowMessagesAfterResolved,
|
||||
greeting_enabled: this.greetingEnabled,
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ export default {
|
||||
const whatsappChannel = await this.$store.dispatch(
|
||||
'inboxes/createChannel',
|
||||
{
|
||||
name: this.inboxName,
|
||||
name: this.inboxName?.trim(),
|
||||
channel: {
|
||||
type: 'whatsapp',
|
||||
phone_number: this.phoneNumber,
|
||||
|
||||
@@ -42,7 +42,7 @@ export default {
|
||||
|
||||
try {
|
||||
const apiChannel = await this.$store.dispatch('inboxes/createChannel', {
|
||||
name: this.channelName,
|
||||
name: this.channelName?.trim(),
|
||||
channel: {
|
||||
type: 'api',
|
||||
webhook_url: this.webhookUrl,
|
||||
|
||||
@@ -48,7 +48,7 @@ export default {
|
||||
|
||||
try {
|
||||
const smsChannel = await this.$store.dispatch('inboxes/createChannel', {
|
||||
name: this.inboxName,
|
||||
name: this.inboxName?.trim(),
|
||||
channel: {
|
||||
type: 'sms',
|
||||
phone_number: this.phoneNumber,
|
||||
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
const whatsappChannel = await this.$store.dispatch(
|
||||
'inboxes/createChannel',
|
||||
{
|
||||
name: this.inboxName,
|
||||
name: this.inboxName?.trim(),
|
||||
channel: {
|
||||
type: 'whatsapp',
|
||||
phone_number: this.phoneNumber,
|
||||
|
||||
@@ -179,7 +179,7 @@ export default {
|
||||
user_access_token: this.user_access_token,
|
||||
page_access_token: this.selectedPage.access_token,
|
||||
page_id: this.selectedPage.id,
|
||||
inbox_name: this.selectedPage.name,
|
||||
inbox_name: this.selectedPage.name?.trim(),
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
const lineChannel = await this.$store.dispatch(
|
||||
'inboxes/createChannel',
|
||||
{
|
||||
name: this.channelName,
|
||||
name: this.channelName?.trim(),
|
||||
channel: {
|
||||
type: 'line',
|
||||
line_channel_id: this.lineChannelId,
|
||||
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
'inboxes/createTwilioChannel',
|
||||
{
|
||||
twilio_channel: {
|
||||
name: this.channelName,
|
||||
name: this.channelName?.trim(),
|
||||
medium: this.medium,
|
||||
account_sid: this.accountSID,
|
||||
api_key_sid: this.apiKeySID,
|
||||
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
const website = await this.$store.dispatch(
|
||||
'inboxes/createWebsiteChannel',
|
||||
{
|
||||
name: this.inboxName,
|
||||
name: this.inboxName?.trim(),
|
||||
greeting_enabled: this.greetingEnabled,
|
||||
greeting_message: this.greetingMessage,
|
||||
channel: {
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ export default {
|
||||
const emailChannel = await this.$store.dispatch(
|
||||
'inboxes/createChannel',
|
||||
{
|
||||
name: this.channelName,
|
||||
name: this.channelName?.trim(),
|
||||
channel: {
|
||||
type: 'email',
|
||||
email: this.email,
|
||||
|
||||
+52
@@ -7,7 +7,9 @@ import SmtpSettings from '../SmtpSettings.vue';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required } from '@vuelidate/validators';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import TextArea from 'next/textarea/TextArea.vue';
|
||||
import WhatsappReauthorize from '../channels/whatsapp/Reauthorize.vue';
|
||||
import { sanitizeAllowedDomains } from 'dashboard/helper/URLHelper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -15,6 +17,7 @@ export default {
|
||||
ImapSettings,
|
||||
SmtpSettings,
|
||||
NextButton,
|
||||
TextArea,
|
||||
WhatsappReauthorize,
|
||||
},
|
||||
mixins: [inboxMixin],
|
||||
@@ -33,6 +36,8 @@ export default {
|
||||
whatsAppInboxAPIKey: '',
|
||||
isRequestingReauthorization: false,
|
||||
isSyncingTemplates: false,
|
||||
allowedDomains: '',
|
||||
isUpdatingAllowedDomains: false,
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
@@ -57,6 +62,7 @@ export default {
|
||||
methods: {
|
||||
setDefaults() {
|
||||
this.hmacMandatory = this.inbox.hmac_mandatory || false;
|
||||
this.allowedDomains = this.inbox.allowed_domains || '';
|
||||
},
|
||||
handleHmacFlag() {
|
||||
this.updateInbox();
|
||||
@@ -76,6 +82,28 @@ export default {
|
||||
useAlert(this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
|
||||
}
|
||||
},
|
||||
async updateAllowedDomains() {
|
||||
this.isUpdatingAllowedDomains = true;
|
||||
const sanitizedAllowedDomains = sanitizeAllowedDomains(
|
||||
this.allowedDomains
|
||||
);
|
||||
try {
|
||||
const payload = {
|
||||
id: this.inbox.id,
|
||||
formData: false,
|
||||
channel: {
|
||||
allowed_domains: sanitizedAllowedDomains,
|
||||
},
|
||||
};
|
||||
await this.$store.dispatch('inboxes/updateInbox', payload);
|
||||
this.allowedDomains = sanitizedAllowedDomains;
|
||||
useAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
|
||||
} catch (error) {
|
||||
useAlert(this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
|
||||
} finally {
|
||||
this.isUpdatingAllowedDomains = false;
|
||||
}
|
||||
},
|
||||
async updateWhatsAppInboxAPIKey() {
|
||||
try {
|
||||
const payload = {
|
||||
@@ -180,6 +208,30 @@ export default {
|
||||
/>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
:title="$t('INBOX_MGMT.SETTINGS_POPUP.ALLOWED_DOMAINS.TITLE')"
|
||||
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.ALLOWED_DOMAINS.SUBTITLE')"
|
||||
>
|
||||
<div class="flex flex-col w-full max-w-3xl gap-4">
|
||||
<TextArea
|
||||
v-model="allowedDomains"
|
||||
:placeholder="
|
||||
$t('INBOX_MGMT.SETTINGS_POPUP.ALLOWED_DOMAINS.PLACEHOLDER')
|
||||
"
|
||||
auto-height
|
||||
min-height="8rem"
|
||||
class="w-full"
|
||||
/>
|
||||
<div>
|
||||
<NextButton
|
||||
:label="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
|
||||
:is-loading="isUpdatingAllowedDomains"
|
||||
@click="updateAllowedDomains"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
:title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_VERIFICATION')"
|
||||
>
|
||||
|
||||
+1
@@ -171,6 +171,7 @@ onMounted(() => {
|
||||
<SectionLayout
|
||||
:title="t('SECURITY_SETTINGS.SAML.TITLE')"
|
||||
:description="t('SECURITY_SETTINGS.SAML.NOTE')"
|
||||
beta
|
||||
:hide-content="!hasFeature || !isEnabled || isLoading"
|
||||
>
|
||||
<template #headerActions>
|
||||
|
||||
Reference in New Issue
Block a user