iachat/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue
2023-04-19 23:09:37 +05:30

425 lines
12 KiB
Vue

<template>
<form class="conversation--form" @submit.prevent="onFormSubmit">
<div v-if="showNoInboxAlert" class="callout warning">
<p>
{{ $t('NEW_CONVERSATION.NO_INBOX') }}
</p>
</div>
<div v-else>
<div class="row gutter-small">
<div class="columns">
<label>
{{ $t('NEW_CONVERSATION.FORM.INBOX.LABEL') }}
</label>
<div class="multiselect-wrap--small">
<multiselect
v-model="targetInbox"
track-by="id"
label="name"
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
selected-label=""
select-label=""
deselect-label=""
:max-height="160"
:close-on-select="true"
:options="[...inboxes]"
>
<template slot="singleLabel" slot-scope="{ option }">
<inbox-dropdown-item
v-if="option.name"
:name="option.name"
:inbox-identifier="computedInboxSource(option)"
:channel-type="option.channel_type"
/>
<span v-else>
{{ $t('NEW_CONVERSATION.FORM.INBOX.PLACEHOLDER') }}
</span>
</template>
<template slot="option" slot-scope="{ option }">
<inbox-dropdown-item
:name="option.name"
:inbox-identifier="computedInboxSource(option)"
:channel-type="option.channel_type"
/>
</template>
</multiselect>
</div>
<label :class="{ error: $v.targetInbox.$error }">
<span v-if="$v.targetInbox.$error" class="message">
{{ $t('NEW_CONVERSATION.FORM.INBOX.ERROR') }}
</span>
</label>
</div>
<div class="columns">
<label>
{{ $t('NEW_CONVERSATION.FORM.TO.LABEL') }}
<div class="contact-input">
<thumbnail
:src="contact.thumbnail"
size="24px"
:username="contact.name"
:status="contact.availability_status"
/>
<h4 class="text-block-title contact-name">
{{ contact.name }}
</h4>
</div>
</label>
</div>
</div>
<div v-if="isAnEmailInbox" class="row">
<div class="columns">
<label :class="{ error: $v.subject.$error }">
{{ $t('NEW_CONVERSATION.FORM.SUBJECT.LABEL') }}
<input
v-model="subject"
type="text"
:placeholder="$t('NEW_CONVERSATION.FORM.SUBJECT.PLACEHOLDER')"
@input="$v.subject.$touch"
/>
<span v-if="$v.subject.$error" class="message">
{{ $t('NEW_CONVERSATION.FORM.SUBJECT.ERROR') }}
</span>
</label>
</div>
</div>
<div class="row">
<div class="columns">
<div class="canned-response">
<canned-response
v-if="showCannedResponseMenu && hasSlashCommand"
:search-key="cannedResponseSearchKey"
@click="replaceTextWithCannedResponse"
/>
</div>
<div v-if="isEmailOrWebWidgetInbox">
<label>
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.LABEL') }}
<reply-email-head
v-if="isAnEmailInbox"
:cc-emails.sync="ccEmails"
:bcc-emails.sync="bccEmails"
/>
<label class="editor-wrap">
<woot-message-editor
v-model="message"
class="message-editor"
:class="{ editor_warning: $v.message.$error }"
:enable-variables="true"
:placeholder="$t('NEW_CONVERSATION.FORM.MESSAGE.PLACEHOLDER')"
@toggle-canned-menu="toggleCannedMenu"
@blur="$v.message.$touch"
/>
<span v-if="$v.message.$error" class="editor-warning__message">
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.ERROR') }}
</span>
</label>
</label>
</div>
<whatsapp-templates
v-else-if="hasWhatsappTemplates"
:inbox-id="selectedInbox.inbox.id"
@on-select-template="toggleWaTemplate"
@on-send="onSendWhatsAppReply"
/>
<label v-else :class="{ error: $v.message.$error }">
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.LABEL') }}
<textarea
v-model="message"
class="message-input"
type="text"
:placeholder="$t('NEW_CONVERSATION.FORM.MESSAGE.PLACEHOLDER')"
@input="$v.message.$touch"
/>
<span v-if="$v.message.$error" class="message">
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.ERROR') }}
</span>
</label>
</div>
</div>
</div>
<div v-if="!hasWhatsappTemplates" class="modal-footer">
<button class="button clear" @click.prevent="onCancel">
{{ $t('NEW_CONVERSATION.FORM.CANCEL') }}
</button>
<woot-button type="submit" :is-loading="conversationsUiFlags.isCreating">
{{ $t('NEW_CONVERSATION.FORM.SUBMIT') }}
</woot-button>
</div>
</form>
</template>
<script>
import { mapGetters } from 'vuex';
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
import ReplyEmailHead from 'dashboard/components/widgets/conversation/ReplyEmailHead';
import CannedResponse from 'dashboard/components/widgets/conversation/CannedResponse.vue';
import InboxDropdownItem from 'dashboard/components/widgets/InboxDropdownItem';
import WhatsappTemplates from './WhatsappTemplates.vue';
import alertMixin from 'shared/mixins/alertMixin';
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
import { getInboxSource } from 'dashboard/helper/inbox';
import { required, requiredIf } from 'vuelidate/lib/validators';
export default {
components: {
Thumbnail,
WootMessageEditor,
ReplyEmailHead,
CannedResponse,
WhatsappTemplates,
InboxDropdownItem,
},
mixins: [alertMixin],
props: {
contact: {
type: Object,
default: () => ({}),
},
onSubmit: {
type: Function,
default: () => {},
},
},
data() {
return {
name: '',
subject: '',
message: '',
showCannedResponseMenu: false,
cannedResponseSearchKey: '',
bccEmails: '',
ccEmails: '',
targetInbox: {},
whatsappTemplateSelected: false,
};
},
validations: {
subject: {
required: requiredIf('isAnEmailInbox'),
},
message: {
required,
},
targetInbox: {
required,
},
},
computed: {
...mapGetters({
uiFlags: 'contacts/getUIFlags',
conversationsUiFlags: 'contactConversations/getUIFlags',
currentUser: 'getCurrentUser',
}),
emailMessagePayload() {
const payload = {
inboxId: this.targetInbox.id,
sourceId: this.targetInbox.sourceId,
contactId: this.contact.id,
message: { content: this.message },
mailSubject: this.subject,
assigneeId: this.currentUser.id,
};
if (this.ccEmails) {
payload.message.cc_emails = this.ccEmails;
}
if (this.bccEmails) {
payload.message.bcc_emails = this.bccEmails;
}
return payload;
},
selectedInbox: {
get() {
const inboxList = this.contact.contactableInboxes || [];
return (
inboxList.find(inbox => inbox.inbox.id === this.targetInbox.id) || {
inbox: {},
}
);
},
set(value) {
this.targetInbox = value.inbox;
},
},
showNoInboxAlert() {
if (!this.contact.contactableInboxes) {
return false;
}
return this.inboxes.length === 0 && !this.uiFlags.isFetchingInboxes;
},
inboxes() {
const inboxList = this.contact.contactableInboxes || [];
return inboxList.map(inbox => ({
...inbox.inbox,
sourceId: inbox.source_id,
}));
},
isAnEmailInbox() {
return (
this.selectedInbox &&
this.selectedInbox.inbox.channel_type === INBOX_TYPES.EMAIL
);
},
isAnWebWidgetInbox() {
return (
this.selectedInbox &&
this.selectedInbox.inbox.channel_type === INBOX_TYPES.WEB
);
},
isEmailOrWebWidgetInbox() {
return this.isAnEmailInbox || this.isAnWebWidgetInbox;
},
hasWhatsappTemplates() {
return !!this.selectedInbox.inbox?.message_templates;
},
},
watch: {
message(value) {
this.hasSlashCommand = value[0] === '/' && !this.isEmailOrWebWidgetInbox;
const hasNextWord = value.includes(' ');
const isShortCodeActive = this.hasSlashCommand && !hasNextWord;
if (isShortCodeActive) {
this.cannedResponseSearchKey = value.substring(1);
this.showCannedResponseMenu = true;
} else {
this.cannedResponseSearchKey = '';
this.showCannedResponseMenu = false;
}
},
},
methods: {
onCancel() {
this.$emit('cancel');
},
onSuccess() {
this.$emit('success');
},
replaceTextWithCannedResponse(message) {
this.message = message;
},
toggleCannedMenu(value) {
this.showCannedMenu = value;
},
prepareWhatsAppMessagePayload({ message: content, templateParams }) {
const payload = {
inboxId: this.targetInbox.id,
sourceId: this.targetInbox.sourceId,
contactId: this.contact.id,
message: { content, template_params: templateParams },
assigneeId: this.currentUser.id,
};
return payload;
},
onFormSubmit() {
this.$v.$touch();
if (this.$v.$invalid) {
return;
}
this.createConversation(this.emailMessagePayload);
},
async createConversation(payload) {
try {
const data = await this.onSubmit(payload);
const action = {
type: 'link',
to: `/app/accounts/${data.account_id}/conversations/${data.id}`,
message: this.$t('NEW_CONVERSATION.FORM.GO_TO_CONVERSATION'),
};
this.onSuccess();
this.showAlert(
this.$t('NEW_CONVERSATION.FORM.SUCCESS_MESSAGE'),
action
);
} catch (error) {
if (error instanceof ExceptionWithMessage) {
this.showAlert(error.data);
} else {
this.showAlert(this.$t('NEW_CONVERSATION.FORM.ERROR_MESSAGE'));
}
}
},
toggleWaTemplate(val) {
this.whatsappTemplateSelected = val;
},
async onSendWhatsAppReply(messagePayload) {
const payload = this.prepareWhatsAppMessagePayload(messagePayload);
await this.createConversation(payload);
},
inboxReadableIdentifier(inbox) {
return `${inbox.name} (${inbox.channel_type})`;
},
computedInboxSource(inbox) {
if (!inbox.channel_type) return '';
const classByType = getInboxSource(
inbox.channel_type,
inbox.phone_number,
inbox
);
return classByType;
},
},
};
</script>
<style scoped lang="scss">
.conversation--form {
padding: var(--space-normal) var(--space-large) var(--space-large);
}
.canned-response {
position: relative;
}
.input-group-label {
font-size: var(--font-size-small);
}
.contact-input {
display: flex;
align-items: center;
height: 3.9rem;
background: var(--color-background-light);
border: 1px solid var(--color-border);
padding: var(--space-smaller) var(--space-small);
border-radius: var(--border-radius-small);
.contact-name {
margin: 0;
margin-left: var(--space-small);
margin-right: var(--space-small);
}
}
.message-input {
min-height: 8rem;
}
.row.gutter-small {
gap: var(--space-small);
}
::v-deep {
.mention--box {
left: 0;
margin: auto;
right: 0;
top: unset;
height: fit-content;
}
/* TODO: Remove when have standardized a component out of multiselect */
.multiselect .multiselect__content .multiselect__option span {
display: inline-flex;
width: var(--space-medium);
color: var(--s-600);
}
.multiselect .multiselect__content .multiselect__option {
padding: var(--space-micro) var(--space-smaller);
}
}
</style>