feat: Add support for Whatsapp template messages in the UI (#4711)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
parent
56f668db6b
commit
bad24f97ab
22 changed files with 733 additions and 54 deletions
|
@ -73,6 +73,10 @@ class Messages::MessageBuilder
|
|||
@params[:campaign_id].present? ? { additional_attributes: { campaign_id: @params[:campaign_id] } } : {}
|
||||
end
|
||||
|
||||
def template_params
|
||||
@params[:template_params].present? ? { additional_attributes: { template_params: JSON.parse(@params[:template_params].to_json) } } : {}
|
||||
end
|
||||
|
||||
def message_sender
|
||||
return if @params[:sender_type] != 'AgentBot'
|
||||
|
||||
|
@ -91,6 +95,6 @@ class Messages::MessageBuilder
|
|||
items: @items,
|
||||
in_reply_to: @in_reply_to,
|
||||
echo_id: @params[:echo_id]
|
||||
}.merge(external_created_at).merge(automation_rule_id).merge(campaign_id)
|
||||
}.merge(external_created_at).merge(automation_rule_id).merge(campaign_id).merge(template_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ export const buildCreatePayload = ({
|
|||
files,
|
||||
ccEmails = '',
|
||||
bccEmails = '',
|
||||
templateParams,
|
||||
}) => {
|
||||
let payload;
|
||||
if (files && files.length !== 0) {
|
||||
|
@ -32,6 +33,7 @@ export const buildCreatePayload = ({
|
|||
content_attributes: contentAttributes,
|
||||
cc_emails: ccEmails,
|
||||
bcc_emails: bccEmails,
|
||||
template_params: templateParams,
|
||||
};
|
||||
}
|
||||
return payload;
|
||||
|
@ -51,6 +53,7 @@ class MessageApi extends ApiClient {
|
|||
files,
|
||||
ccEmails = '',
|
||||
bccEmails = '',
|
||||
templateParams,
|
||||
}) {
|
||||
return axios({
|
||||
method: 'post',
|
||||
|
@ -63,6 +66,7 @@ class MessageApi extends ApiClient {
|
|||
files,
|
||||
ccEmails,
|
||||
bccEmails,
|
||||
templateParams,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -98,4 +98,7 @@ export default {
|
|||
width: 48rem;
|
||||
}
|
||||
}
|
||||
.modal-big {
|
||||
width: 60%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -79,6 +79,16 @@
|
|||
:title="signatureToggleTooltip"
|
||||
@click="toggleMessageSignature"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="showWhatsappTemplatesButton"
|
||||
v-tooltip.top-end="'Whatsapp Templates'"
|
||||
icon="whatsapp"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
:title="'Whatsapp Templates'"
|
||||
@click="$emit('selectWhatsappTemplate')"
|
||||
/>
|
||||
<transition name="modal-fade">
|
||||
<div
|
||||
v-show="$refs.upload && $refs.upload.dropActive"
|
||||
|
@ -218,6 +228,10 @@ export default {
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hasWhatsappTemplates: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isNote() {
|
||||
|
@ -261,6 +275,9 @@ export default {
|
|||
showMessageSignatureButton() {
|
||||
return !this.isPrivate && this.isAnEmailChannel;
|
||||
},
|
||||
showWhatsappTemplatesButton() {
|
||||
return !this.isOnPrivateNote && this.hasWhatsappTemplates;
|
||||
},
|
||||
sendWithSignature() {
|
||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||
return isEnabled;
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
:toggle-audio-recorder="toggleAudioRecorder"
|
||||
:toggle-audio-recorder-play-pause="toggleAudioRecorderPlayPause"
|
||||
:show-emoji-picker="showEmojiPicker"
|
||||
:on-send="sendMessage"
|
||||
:on-send="onSendReply"
|
||||
:is-send-disabled="isReplyButtonDisabled"
|
||||
:recording-audio-duration-text="recordingAudioDurationText"
|
||||
:recording-audio-state="recordingAudioState"
|
||||
|
@ -112,7 +112,16 @@
|
|||
:enable-rich-editor="isRichEditorEnabled"
|
||||
:enter-to-send-enabled="enterToSendEnabled"
|
||||
:enable-multiple-file-upload="enableMultipleFileUpload"
|
||||
:has-whatsapp-templates="hasWhatsappTemplates"
|
||||
@toggleEnterToSend="toggleEnterToSend"
|
||||
@selectWhatsappTemplate="openWhatsappTemplateModal"
|
||||
/>
|
||||
<whatsapp-templates
|
||||
:inbox-id="inbox.id"
|
||||
:show="showWhatsAppTemplatesModal"
|
||||
@close="hideWhatsappTemplatesModal"
|
||||
@on-send="onSendWhatsAppReply"
|
||||
@cancel="hideWhatsappTemplatesModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -137,7 +146,7 @@ import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
|||
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
||||
import { MAXIMUM_FILE_UPLOAD_SIZE } from 'shared/constants/messages';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
|
||||
import WhatsappTemplates from './WhatsappTemplates/Modal.vue';
|
||||
import {
|
||||
isEscape,
|
||||
isEnter,
|
||||
|
@ -162,6 +171,7 @@ export default {
|
|||
WootMessageEditor,
|
||||
WootAudioRecorder,
|
||||
Banner,
|
||||
WhatsappTemplates,
|
||||
},
|
||||
mixins: [
|
||||
clickaway,
|
||||
|
@ -201,6 +211,7 @@ export default {
|
|||
hasSlashCommand: false,
|
||||
bccEmails: '',
|
||||
ccEmails: '',
|
||||
showWhatsAppTemplatesModal: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -212,7 +223,6 @@ export default {
|
|||
globalConfig: 'globalConfig/get',
|
||||
accountId: 'getCurrentAccountId',
|
||||
}),
|
||||
|
||||
showRichContentEditor() {
|
||||
if (this.isOnPrivateNote) {
|
||||
return true;
|
||||
|
@ -256,7 +266,9 @@ export default {
|
|||
|
||||
return false;
|
||||
},
|
||||
|
||||
hasWhatsappTemplates() {
|
||||
return !!this.inbox.message_templates;
|
||||
},
|
||||
enterToSendEnabled() {
|
||||
return !!this.uiSettings.enter_to_send_enabled;
|
||||
},
|
||||
|
@ -484,7 +496,7 @@ export default {
|
|||
hasSendOnEnterEnabled && !hasPressedShift(e) && this.isFocused;
|
||||
if (shouldSendMessage) {
|
||||
e.preventDefault();
|
||||
this.sendMessage();
|
||||
this.onSendReply();
|
||||
}
|
||||
} else if (hasPressedCommandPlusKKey(e)) {
|
||||
this.openCommandBar();
|
||||
|
@ -497,6 +509,12 @@ export default {
|
|||
toggleEnterToSend(enterToSendEnabled) {
|
||||
this.updateUISettings({ enter_to_send_enabled: enterToSendEnabled });
|
||||
},
|
||||
openWhatsappTemplateModal() {
|
||||
this.showWhatsAppTemplatesModal = true;
|
||||
},
|
||||
hideWhatsappTemplatesModal() {
|
||||
this.showWhatsAppTemplatesModal = false;
|
||||
},
|
||||
onClickSelfAssign() {
|
||||
const {
|
||||
account_id,
|
||||
|
@ -520,7 +538,7 @@ export default {
|
|||
};
|
||||
this.assignedAgent = selfAssign;
|
||||
},
|
||||
async sendMessage() {
|
||||
async onSendReply() {
|
||||
if (this.isReplyButtonDisabled) {
|
||||
return;
|
||||
}
|
||||
|
@ -531,6 +549,12 @@ export default {
|
|||
}
|
||||
const messagePayload = this.getMessagePayload(newMessage);
|
||||
this.clearMessage();
|
||||
this.sendMessage(messagePayload);
|
||||
this.hideEmojiPicker();
|
||||
this.$emit('update:popoutReplyBox', false);
|
||||
}
|
||||
},
|
||||
async sendMessage(messagePayload) {
|
||||
try {
|
||||
await this.$store.dispatch(
|
||||
'createPendingMessageAndSend',
|
||||
|
@ -539,13 +563,16 @@ export default {
|
|||
bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error?.response?.data?.error ||
|
||||
this.$t('CONVERSATION.MESSAGE_ERROR');
|
||||
error?.response?.data?.error || this.$t('CONVERSATION.MESSAGE_ERROR');
|
||||
this.showAlert(errorMessage);
|
||||
}
|
||||
this.hideEmojiPicker();
|
||||
this.$emit('update:popoutReplyBox', false);
|
||||
}
|
||||
},
|
||||
async onSendWhatsAppReply(messagePayload) {
|
||||
this.sendMessage({
|
||||
conversationId: this.currentChat.id,
|
||||
...messagePayload,
|
||||
});
|
||||
this.hideWhatsappTemplatesModal();
|
||||
},
|
||||
replaceText(message) {
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<woot-modal :show.sync="show" :on-close="onClose" size="modal-big">
|
||||
<woot-modal-header
|
||||
:header-title="$t('WHATSAPP_TEMPLATES.MODAL.TITLE')"
|
||||
:header-content="modalHeaderContent"
|
||||
/>
|
||||
<div class="row modal-content">
|
||||
<templates-picker
|
||||
v-if="!selectedWaTemplate"
|
||||
:inbox-id="inboxId"
|
||||
@onSelect="pickTemplate"
|
||||
/>
|
||||
<template-parser
|
||||
v-else
|
||||
:template="selectedWaTemplate"
|
||||
@resetTemplate="onResetTemplate"
|
||||
@sendMessage="onSendMessage"
|
||||
/>
|
||||
</div>
|
||||
</woot-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TemplatesPicker from './TemplatesPicker.vue';
|
||||
import TemplateParser from './TemplateParser.vue';
|
||||
export default {
|
||||
components: {
|
||||
TemplatesPicker,
|
||||
TemplateParser,
|
||||
},
|
||||
props: {
|
||||
inboxId: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedWaTemplate: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
modalHeaderContent() {
|
||||
return this.selectedWaTemplate
|
||||
? this.$t('WHATSAPP_TEMPLATES.MODAL.TEMPLATE_SELECTED_SUBTITLE', {
|
||||
templateName: this.selectedWaTemplate.name,
|
||||
})
|
||||
: this.$t('WHATSAPP_TEMPLATES.MODAL.SUBTITLE');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pickTemplate(template) {
|
||||
this.selectedWaTemplate = template;
|
||||
},
|
||||
onResetTemplate() {
|
||||
this.selectedWaTemplate = null;
|
||||
},
|
||||
onSendMessage(message) {
|
||||
this.$emit('on-send', message);
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('cancel');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-content {
|
||||
padding: 2.5rem 3.2rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,183 @@
|
|||
<template>
|
||||
<div class="medium-12 columns">
|
||||
<textarea
|
||||
v-model="processedString"
|
||||
rows="4"
|
||||
readonly
|
||||
class="template-input"
|
||||
></textarea>
|
||||
<div>
|
||||
<div class="template__variables-container">
|
||||
<p class="variables-label">
|
||||
{{ $t('WHATSAPP_TEMPLATES.PARSER.VARIABLES_LABEL') }}
|
||||
</p>
|
||||
<div
|
||||
v-for="(variable, key) in processedParams"
|
||||
:key="key"
|
||||
class="template__variable-item"
|
||||
>
|
||||
<span class="variable-label">
|
||||
{{ key }}
|
||||
</span>
|
||||
<woot-input
|
||||
v-model="processedParams[key]"
|
||||
type="text"
|
||||
class="variable-input"
|
||||
:styles="{ marginBottom: 0 }"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="showRequiredMessage" class="error">
|
||||
{{ $t('WHATSAPP_TEMPLATES.PARSER.FORM_ERROR_MESSAGE') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<woot-button variant="smooth" @click="$emit('resetTemplate')">
|
||||
{{ $t('WHATSAPP_TEMPLATES.PARSER.GO_BACK_LABEL') }}
|
||||
</woot-button>
|
||||
<woot-button @click="sendMessage">
|
||||
{{ $t('WHATSAPP_TEMPLATES.PARSER.SEND_MESSAGE_LABEL') }}
|
||||
</woot-button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
|
||||
const allKeysRequired = value => {
|
||||
const keys = Object.keys(value);
|
||||
return keys.every(key => value[key]);
|
||||
};
|
||||
export default {
|
||||
props: {
|
||||
template: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
processedParams: {
|
||||
required,
|
||||
allKeysRequired,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
message: this.template.message,
|
||||
processedParams: {},
|
||||
showRequiredMessage: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
variables() {
|
||||
const variables = this.templateString.match(/{{([^}]+)}}/g);
|
||||
return variables;
|
||||
},
|
||||
templateString() {
|
||||
return this.template.components.find(
|
||||
component => component.type === 'BODY'
|
||||
).text;
|
||||
},
|
||||
processedString() {
|
||||
return this.templateString.replace(/{{([^}]+)}}/g, (match, variable) => {
|
||||
const variableKey = this.processVariable(variable);
|
||||
return this.processedParams[variableKey] || `{{${variable}}}`;
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.generateVariables();
|
||||
},
|
||||
methods: {
|
||||
sendMessage() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) {
|
||||
this.showRequiredMessage = true;
|
||||
return;
|
||||
}
|
||||
const message = {
|
||||
message: this.processedString,
|
||||
templateParams: {
|
||||
name: this.template.name,
|
||||
category: this.template.category,
|
||||
language: this.template.language,
|
||||
namespace: this.template.namespace,
|
||||
processed_params: this.processedParams,
|
||||
},
|
||||
};
|
||||
this.$emit('sendMessage', message);
|
||||
},
|
||||
processVariable(str) {
|
||||
return str.replace(/{{|}}/g, '');
|
||||
},
|
||||
generateVariables() {
|
||||
const templateString = this.template.components.find(
|
||||
component => component.type === 'BODY'
|
||||
).text;
|
||||
const variables = templateString.match(/{{([^}]+)}}/g).map(variable => {
|
||||
return this.processVariable(variable);
|
||||
});
|
||||
this.processedParams = variables.reduce((acc, variable) => {
|
||||
acc[variable] = '';
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.template__variables-container {
|
||||
padding: var(--space-one);
|
||||
}
|
||||
|
||||
.variables-label {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--space-one);
|
||||
}
|
||||
|
||||
.template__variable-item {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: var(--space-one);
|
||||
|
||||
.label {
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
|
||||
.variable-input {
|
||||
flex: 1;
|
||||
font-size: var(--font-size-small);
|
||||
margin-left: var(--space-one);
|
||||
}
|
||||
|
||||
.variable-label {
|
||||
background-color: var(--s-75);
|
||||
border-radius: var(--border-radius-normal);
|
||||
display: inline-block;
|
||||
font-size: var(--font-size-mini);
|
||||
padding: var(--space-one) var(--space-medium);
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
button {
|
||||
margin-left: var(--space-one);
|
||||
}
|
||||
}
|
||||
.error {
|
||||
background-color: var(--r-100);
|
||||
border-radius: var(--border-radius-normal);
|
||||
color: var(--r-800);
|
||||
padding: var(--space-one);
|
||||
text-align: center;
|
||||
}
|
||||
.template-input {
|
||||
background-color: var(--s-25);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,163 @@
|
|||
<template>
|
||||
<div class="medium-12 columns">
|
||||
<div class="templates__list-search">
|
||||
<fluent-icon icon="search" class="search-icon" size="16" />
|
||||
<input
|
||||
ref="search"
|
||||
v-model="query"
|
||||
type="search"
|
||||
:placeholder="$t('WHATSAPP_TEMPLATES.PICKER.SEARCH_PLACEHOLDER')"
|
||||
class="templates__search-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="template__list-container">
|
||||
<div v-for="(template, i) in filteredTemplateMessages" :key="template.id">
|
||||
<button
|
||||
class="template__list-item"
|
||||
@click="$emit('onSelect', template)"
|
||||
>
|
||||
<div>
|
||||
<div class="flex-between">
|
||||
<p class="label-title">
|
||||
{{ template.name }}
|
||||
</p>
|
||||
<span class="label-lang label">
|
||||
{{ $t('WHATSAPP_TEMPLATES.PICKER.LABELS.LANGUAGE') }} :
|
||||
{{ template.language }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="strong">
|
||||
{{ $t('WHATSAPP_TEMPLATES.PICKER.LABELS.TEMPLATE_BODY') }}
|
||||
</p>
|
||||
<p class="label-body">{{ getTemplatebody(template) }}</p>
|
||||
</div>
|
||||
<div class="label-category">
|
||||
<p class="strong">
|
||||
{{ $t('WHATSAPP_TEMPLATES.PICKER.LABELS.CATEGORY') }}
|
||||
</p>
|
||||
<p>{{ template.category }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<hr v-if="i != filteredTemplateMessages.length - 1" :key="`hr-${i}`" />
|
||||
</div>
|
||||
<div v-if="!filteredTemplateMessages.length">
|
||||
<p>
|
||||
{{ $t('WHATSAPP_TEMPLATES.PICKER.NO_TEMPLATES_FOUND') }}
|
||||
<strong>{{ query }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
inboxId: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
query: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
whatsAppTemplateMessages() {
|
||||
return this.$store.getters['inboxes/getWhatsAppTemplates'](this.inboxId);
|
||||
},
|
||||
filteredTemplateMessages() {
|
||||
return this.whatsAppTemplateMessages.filter(template =>
|
||||
template.name.toLowerCase().includes(this.query.toLowerCase())
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getTemplatebody(template) {
|
||||
return template.components.find(component => component.type === 'BODY')
|
||||
.text;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.flex-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--space-one);
|
||||
}
|
||||
|
||||
.templates__list-search {
|
||||
align-items: center;
|
||||
background-color: var(--s-25);
|
||||
border-radius: var(--border-radius-medium);
|
||||
border: 1px solid var(--s-100);
|
||||
display: flex;
|
||||
margin-bottom: var(--space-one);
|
||||
padding: 0 var(--space-one);
|
||||
|
||||
.search-icon {
|
||||
color: var(--s-400);
|
||||
}
|
||||
|
||||
.templates__search-input {
|
||||
background-color: transparent;
|
||||
border: var(--space-large);
|
||||
font-size: var(--font-size-mini);
|
||||
height: unset;
|
||||
margin: var(--space-zero);
|
||||
}
|
||||
}
|
||||
.template__list-container {
|
||||
background-color: var(--s-25);
|
||||
border-radius: var(--border-radius-medium);
|
||||
max-height: 30rem;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-one);
|
||||
|
||||
.template__list-item {
|
||||
border-radius: var(--border-radius-medium);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding: var(--space-one);
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--w-50);
|
||||
}
|
||||
|
||||
.label-title {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.label-category {
|
||||
margin-top: var(--space-two);
|
||||
|
||||
span {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
}
|
||||
|
||||
.label-body {
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.strong {
|
||||
font-size: var(--font-size-mini);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
hr {
|
||||
border-bottom: 1px solid var(--s-100);
|
||||
margin: var(--space-one) auto;
|
||||
max-width: 95%;
|
||||
}
|
||||
</style>
|
|
@ -7,7 +7,7 @@
|
|||
<fluent-icon
|
||||
v-tooltip.top-start="$t('CHAT_LIST.SENT')"
|
||||
icon="checkmark"
|
||||
size="16"
|
||||
size="14"
|
||||
/>
|
||||
</span>
|
||||
<fluent-icon
|
||||
|
@ -165,7 +165,11 @@ export default {
|
|||
return `https://www.instagram.com/stories/${storySender}/${storyId}`;
|
||||
},
|
||||
showSentIndicator() {
|
||||
return this.isOutgoing && this.sourceId && this.isAnEmailChannel;
|
||||
return (
|
||||
this.isOutgoing &&
|
||||
this.sourceId &&
|
||||
(this.isAnEmailChannel || this.isAWhatsappChannel)
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:readonly="readonly"
|
||||
:style="styles"
|
||||
@input="onChange"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
|
@ -47,6 +48,10 @@ export default {
|
|||
type: Boolean,
|
||||
deafaut: false,
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange(e) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import { default as _setNewPassword } from './setNewPassword.json';
|
|||
import { default as _settings } from './settings.json';
|
||||
import { default as _signup } from './signup.json';
|
||||
import { default as _teamsSettings } from './teamsSettings.json';
|
||||
import { default as _whatsappTemplates } from './whatsappTemplates.json';
|
||||
import { default as _bulkActions } from './bulkActions.json';
|
||||
|
||||
export default {
|
||||
|
@ -47,5 +48,6 @@ export default {
|
|||
..._settings,
|
||||
..._signup,
|
||||
..._teamsSettings,
|
||||
..._whatsappTemplates,
|
||||
..._bulkActions,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"WHATSAPP_TEMPLATES": {
|
||||
"MODAL": {
|
||||
"TITLE": "Whatsapp Templates",
|
||||
"SUBTITLE": "Select the whatsapp template you want to send",
|
||||
"TEMPLATE_SELECTED_SUBTITLE": "Process %{templateName}"
|
||||
},
|
||||
"PICKER": {
|
||||
"SEARCH_PLACEHOLDER": "Search Templates",
|
||||
"NO_TEMPLATES_FOUND": "No templates found for",
|
||||
"LABELS": {
|
||||
"LANGUAGE": "Language",
|
||||
"TEMPLATE_BODY": "Template Body",
|
||||
"CATEGORY": "Category"
|
||||
}
|
||||
},
|
||||
"PARSER": {
|
||||
"VARIABLES_LABEL": "Variables",
|
||||
"VARIABLE_PLACEHOLDER": "Enter %{variable} value",
|
||||
"GO_BACK_LABEL": "Go Back",
|
||||
"SEND_MESSAGE_LABEL": "Send Message",
|
||||
"FORM_ERROR_MESSAGE": "Please fill all variables before sending"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<form class="conversation--form" @submit.prevent="handleSubmit">
|
||||
<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">
|
||||
<div class="row gutter-small">
|
||||
<div class="columns">
|
||||
<label :class="{ error: $v.targetInbox.$error }">
|
||||
{{ $t('NEW_CONVERSATION.FORM.INBOX.LABEL') }}
|
||||
|
@ -88,6 +88,12 @@
|
|||
</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
|
||||
|
@ -104,7 +110,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div v-if="!hasWhatsappTemplates" class="modal-footer">
|
||||
<button class="button clear" @click.prevent="onCancel">
|
||||
{{ $t('NEW_CONVERSATION.FORM.CANCEL') }}
|
||||
</button>
|
||||
|
@ -121,7 +127,7 @@ 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 WhatsappTemplates from './WhatsappTemplates.vue';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
||||
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
||||
|
@ -133,6 +139,7 @@ export default {
|
|||
WootMessageEditor,
|
||||
ReplyEmailHead,
|
||||
CannedResponse,
|
||||
WhatsappTemplates,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
|
@ -155,6 +162,7 @@ export default {
|
|||
selectedInbox: '',
|
||||
bccEmails: '',
|
||||
ccEmails: '',
|
||||
whatsappTemplateSelected: false,
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
|
@ -174,7 +182,7 @@ export default {
|
|||
conversationsUiFlags: 'contactConversations/getUIFlags',
|
||||
currentUser: 'getCurrentUser',
|
||||
}),
|
||||
getNewConversation() {
|
||||
emailMessagePayload() {
|
||||
const payload = {
|
||||
inboxId: this.targetInbox.inbox.id,
|
||||
sourceId: this.targetInbox.source_id,
|
||||
|
@ -194,7 +202,7 @@ export default {
|
|||
},
|
||||
targetInbox: {
|
||||
get() {
|
||||
return this.selectedInbox || '';
|
||||
return this.selectedInbox || {};
|
||||
},
|
||||
set(value) {
|
||||
this.selectedInbox = value;
|
||||
|
@ -221,6 +229,9 @@ export default {
|
|||
this.selectedInbox.inbox.channel_type === INBOX_TYPES.WEB
|
||||
);
|
||||
},
|
||||
hasWhatsappTemplates() {
|
||||
return !!this.selectedInbox.inbox?.message_templates;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
message(value) {
|
||||
|
@ -243,13 +254,30 @@ export default {
|
|||
onSuccess() {
|
||||
this.$emit('success');
|
||||
},
|
||||
async handleSubmit() {
|
||||
replaceTextWithCannedResponse(message) {
|
||||
setTimeout(() => {
|
||||
this.message = message;
|
||||
}, 50);
|
||||
},
|
||||
prepareWhatsAppMessagePayload({ message: content, templateParams }) {
|
||||
const payload = {
|
||||
inboxId: this.targetInbox.inbox.id,
|
||||
sourceId: this.targetInbox.source_id,
|
||||
contactId: this.contact.id,
|
||||
message: { content, 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 payload = this.getNewConversation;
|
||||
const data = await this.onSubmit(payload);
|
||||
const action = {
|
||||
type: 'link',
|
||||
|
@ -269,10 +297,13 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
replaceTextWithCannedResponse(message) {
|
||||
setTimeout(() => {
|
||||
this.message = message;
|
||||
}, 50);
|
||||
|
||||
toggleWaTemplate(val) {
|
||||
this.whatsappTemplateSelected = val;
|
||||
},
|
||||
async onSendWhatsAppReply(messagePayload) {
|
||||
const payload = this.prepareWhatsAppMessagePayload(messagePayload);
|
||||
await this.createConversation(payload);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -321,4 +352,7 @@ export default {
|
|||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.row.gutter-small {
|
||||
gap: var(--space-small);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<templates-picker
|
||||
v-if="!selectedWaTemplate"
|
||||
:inbox-id="inboxId"
|
||||
@onSelect="pickTemplate"
|
||||
/>
|
||||
<template-parser
|
||||
v-else
|
||||
:template="selectedWaTemplate"
|
||||
@resetTemplate="onResetTemplate"
|
||||
@sendMessage="onSendMessage"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TemplatesPicker from 'dashboard/components/widgets/conversation/WhatsappTemplates/TemplatesPicker.vue';
|
||||
import TemplateParser from 'dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue';
|
||||
export default {
|
||||
components: {
|
||||
TemplatesPicker,
|
||||
TemplateParser,
|
||||
},
|
||||
props: {
|
||||
inboxId: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedWaTemplate: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
pickTemplate(template) {
|
||||
this.$emit('pickTemplate', true);
|
||||
this.selectedWaTemplate = template;
|
||||
},
|
||||
onResetTemplate() {
|
||||
this.$emit('pickTemplate', false);
|
||||
this.selectedWaTemplate = null;
|
||||
},
|
||||
onSendMessage(message) {
|
||||
this.$emit('on-send', message);
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('cancel');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -426,7 +426,7 @@ export default {
|
|||
return this.$store.getters['inboxes/getInbox'](this.currentInboxId);
|
||||
},
|
||||
inboxName() {
|
||||
if (this.isATwilioSMSChannel || this.isATwilioWhatsappChannel) {
|
||||
if (this.isATwilioSMSChannel || this.isAWhatsappChannel) {
|
||||
return `${this.inbox.name} (${this.inbox.phone_number})`;
|
||||
}
|
||||
if (this.isAnEmailChannel) {
|
||||
|
|
|
@ -47,6 +47,20 @@ export const getters = {
|
|||
getInboxes($state) {
|
||||
return $state.records;
|
||||
},
|
||||
getWhatsAppTemplates: $state => inboxId => {
|
||||
const [inbox] = $state.records.filter(
|
||||
record => record.id === Number(inboxId)
|
||||
);
|
||||
// filtering out the whatsapp templates with media
|
||||
if (inbox.message_templates) {
|
||||
return inbox.message_templates.filter(template => {
|
||||
return !template.components.some(
|
||||
i => i.format === 'IMAGE' || i.format === 'VIDEO'
|
||||
);
|
||||
});
|
||||
}
|
||||
return [];
|
||||
},
|
||||
getNewConversationInboxes($state) {
|
||||
return $state.records.filter(inbox => {
|
||||
const {
|
||||
|
|
|
@ -121,6 +121,7 @@
|
|||
"video-outline": "M13.75 4.5A3.25 3.25 0 0 1 17 7.75v.173l3.864-2.318A.75.75 0 0 1 22 6.248V17.75a.75.75 0 0 1-1.136.643L17 16.075v.175a3.25 3.25 0 0 1-3.25 3.25h-8.5A3.25 3.25 0 0 1 2 16.25v-8.5A3.25 3.25 0 0 1 5.25 4.5h8.5Zm0 1.5h-8.5A1.75 1.75 0 0 0 3.5 7.75v8.5c0 .966.784 1.75 1.75 1.75h8.5a1.75 1.75 0 0 0 1.75-1.75v-8.5A1.75 1.75 0 0 0 13.75 6Zm6.75 1.573L17 9.674v4.651l3.5 2.1V7.573Z",
|
||||
"warning-outline": "M10.91 2.782a2.25 2.25 0 0 1 2.975.74l.083.138 7.759 14.009a2.25 2.25 0 0 1-1.814 3.334l-.154.006H4.243a2.25 2.25 0 0 1-2.041-3.197l.072-.143L10.031 3.66a2.25 2.25 0 0 1 .878-.878Zm9.505 15.613-7.76-14.008a.75.75 0 0 0-1.254-.088l-.057.088-7.757 14.008a.75.75 0 0 0 .561 1.108l.095.006h15.516a.75.75 0 0 0 .696-1.028l-.04-.086-7.76-14.008 7.76 14.008ZM12 16.002a.999.999 0 1 1 0 1.997.999.999 0 0 1 0-1.997ZM11.995 8.5a.75.75 0 0 1 .744.647l.007.102.004 4.502a.75.75 0 0 1-1.494.103l-.006-.102-.004-4.502a.75.75 0 0 1 .75-.75Z",
|
||||
"wifi-off-outline": "m12.858 14.273 7.434 7.434a1 1 0 0 0 1.414-1.414l-17.999-18a1 1 0 1 0-1.414 1.414L5.39 6.804c-.643.429-1.254.927-1.821 1.495a12.382 12.382 0 0 0-1.39 1.683 1 1 0 0 0 1.644 1.14c.363-.524.761-1.01 1.16-1.41a9.94 9.94 0 0 1 1.855-1.46L7.99 9.405a8.14 8.14 0 0 0-3.203 3.377 1 1 0 0 0 1.784.903 6.08 6.08 0 0 1 1.133-1.563 6.116 6.116 0 0 1 1.77-1.234l1.407 1.407A5.208 5.208 0 0 0 8.336 13.7a5.25 5.25 0 0 0-1.09 1.612 1 1 0 0 0 1.832.802c.167-.381.394-.722.672-1a3.23 3.23 0 0 1 3.108-.841Zm-1.332-5.93 2.228 2.229a6.1 6.1 0 0 1 2.616 1.55c.444.444.837.995 1.137 1.582a1 1 0 1 0 1.78-.911 8.353 8.353 0 0 0-1.503-2.085 8.108 8.108 0 0 0-6.258-2.365ZM8.51 5.327l1.651 1.651a9.904 9.904 0 0 1 10.016 4.148 1 1 0 1 0 1.646-1.136A11.912 11.912 0 0 0 8.51 5.327Zm4.552 11.114a1.501 1.501 0 1 1-2.123 2.123 1.501 1.501 0 0 1 2.123-2.123Z",
|
||||
"whatsapp-outline": "M19.05 4.91A9.816 9.816 0 0 0 12.04 2c-5.46 0-9.91 4.45-9.91 9.91c0 1.75.46 3.45 1.32 4.95L2.05 22l5.25-1.38c1.45.79 3.08 1.21 4.74 1.21c5.46 0 9.91-4.45 9.91-9.91c0-2.65-1.03-5.14-2.9-7.01zm-7.01 15.24c-1.48 0-2.93-.4-4.2-1.15l-.3-.18l-3.12.82l.83-3.04l-.2-.31a8.264 8.264 0 0 1-1.26-4.38c0-4.54 3.7-8.24 8.24-8.24c2.2 0 4.27.86 5.82 2.42a8.183 8.183 0 0 1 2.41 5.83c.02 4.54-3.68 8.23-8.22 8.23zm4.52-6.16c-.25-.12-1.47-.72-1.69-.81c-.23-.08-.39-.12-.56.12c-.17.25-.64.81-.78.97c-.14.17-.29.19-.54.06c-.25-.12-1.05-.39-1.99-1.23c-.74-.66-1.23-1.47-1.38-1.72c-.14-.25-.02-.38.11-.51c.11-.11.25-.29.37-.43s.17-.25.25-.41c.08-.17.04-.31-.02-.43s-.56-1.34-.76-1.84c-.2-.48-.41-.42-.56-.43h-.48c-.17 0-.43.06-.66.31c-.22.25-.86.85-.86 2.07c0 1.22.89 2.4 1.01 2.56c.12.17 1.75 2.67 4.23 3.74c.59.26 1.05.41 1.41.52c.59.19 1.13.16 1.56.1c.48-.07 1.47-.6 1.67-1.18c.21-.58.21-1.07.14-1.18s-.22-.16-.47-.28z",
|
||||
"brand-facebook-outline": "M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z",
|
||||
"brand-line-outline": "M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63h2.386c.346 0 .627.285.627.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63.346 0 .628.285.628.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 24 10.314",
|
||||
"brand-linkedin-outline": "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",
|
||||
|
|
|
@ -61,9 +61,13 @@ class Channel::Whatsapp < ApplicationRecord
|
|||
true
|
||||
end
|
||||
|
||||
def message_templates
|
||||
sync_templates
|
||||
super
|
||||
def sync_templates
|
||||
# to prevent too many api calls
|
||||
last_updated = message_templates_last_updated || 1.day.ago
|
||||
return if Time.current < (last_updated + 12.hours)
|
||||
|
||||
response = HTTParty.get("#{api_base_path}/configs/templates", headers: api_headers)
|
||||
update(message_templates: response['waba_templates'], message_templates_last_updated: Time.now.utc) if response.success?
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -79,7 +83,7 @@ class Channel::Whatsapp < ApplicationRecord
|
|||
}.to_json
|
||||
)
|
||||
|
||||
response.success? ? response['messages'].first['id'] : nil
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def send_attachment_message(phone_number, message)
|
||||
|
@ -99,7 +103,7 @@ class Channel::Whatsapp < ApplicationRecord
|
|||
}.to_json
|
||||
)
|
||||
|
||||
response.success? ? response['messages'].first['id'] : nil
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def send_template_message(phone_number, template_info)
|
||||
|
@ -113,7 +117,16 @@ class Channel::Whatsapp < ApplicationRecord
|
|||
}.to_json
|
||||
)
|
||||
|
||||
response.success? ? response['messages'].first['id'] : nil
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def process_response(response)
|
||||
if response.success?
|
||||
response['messages'].first['id']
|
||||
else
|
||||
Rails.logger.error response.body
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def template_body_parameters(template_info)
|
||||
|
@ -131,15 +144,6 @@ class Channel::Whatsapp < ApplicationRecord
|
|||
}
|
||||
end
|
||||
|
||||
def sync_templates
|
||||
# to prevent too many api calls
|
||||
last_updated = message_templates_last_updated || 1.day.ago
|
||||
return if Time.current < (last_updated + 12.hours)
|
||||
|
||||
response = HTTParty.get("#{api_base_path}/configs/templates", headers: api_headers)
|
||||
update(message_templates: response['waba_templates'], message_templates_last_updated: Time.now.utc) if response.success?
|
||||
end
|
||||
|
||||
# Extract later into provider Service
|
||||
def validate_provider_config
|
||||
response = HTTParty.post(
|
||||
|
|
|
@ -92,6 +92,10 @@ class Inbox < ApplicationRecord
|
|||
channel_type == 'Channel::TwitterProfile'
|
||||
end
|
||||
|
||||
def whatsapp?
|
||||
channel_type == 'Channel::Whatsapp'
|
||||
end
|
||||
|
||||
def inbox_type
|
||||
channel.name
|
||||
end
|
||||
|
|
|
@ -6,16 +6,18 @@ class Whatsapp::SendOnWhatsappService < Base::SendOnChannelService
|
|||
end
|
||||
|
||||
def perform_reply
|
||||
# can reply checks if 24 hour limit has passed.
|
||||
if message.conversation.can_reply?
|
||||
send_on_whatsapp
|
||||
else
|
||||
should_send_template_message = template_params.present? || !message.conversation.can_reply?
|
||||
if should_send_template_message
|
||||
send_template_message
|
||||
else
|
||||
send_session_message
|
||||
end
|
||||
end
|
||||
|
||||
def send_template_message
|
||||
channel.sync_templates
|
||||
name, namespace, lang_code, processed_parameters = processable_channel_message_template
|
||||
|
||||
return if name.blank?
|
||||
|
||||
message_id = channel.send_template(message.conversation.contact_inbox.source_id, {
|
||||
|
@ -28,6 +30,16 @@ class Whatsapp::SendOnWhatsappService < Base::SendOnChannelService
|
|||
end
|
||||
|
||||
def processable_channel_message_template
|
||||
if template_params.present?
|
||||
return [
|
||||
template_params['name'],
|
||||
template_params['namespace'],
|
||||
template_params['language'],
|
||||
template_params['processed_params'].map { |_, value| { type: 'text', text: value } }
|
||||
]
|
||||
end
|
||||
|
||||
# Delete the following logic once the update for template_params is stable
|
||||
# see if we can match the message content to a template
|
||||
# An example template may look like "Your package has been shipped. It will be delivered in {{1}} business days.
|
||||
# We want to iterate over these templates with our message body and see if we can fit it to any of the templates
|
||||
|
@ -78,8 +90,12 @@ class Whatsapp::SendOnWhatsappService < Base::SendOnChannelService
|
|||
template['components'].find { |obj| obj['type'] == 'BODY' && obj.key?('text') }
|
||||
end
|
||||
|
||||
def send_on_whatsapp
|
||||
def send_session_message
|
||||
message_id = channel.send_message(message.conversation.contact_inbox.source_id, message)
|
||||
message.update!(source_id: message_id) if message_id.present?
|
||||
end
|
||||
|
||||
def template_params
|
||||
message.additional_attributes && message.additional_attributes['template_params']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,3 +78,6 @@ if resource.api?
|
|||
json.webhook_url resource.channel.try(:webhook_url)
|
||||
json.inbox_identifier resource.channel.try(:identifier)
|
||||
end
|
||||
|
||||
### WhatsApp Channel
|
||||
json.message_templates resource.channel.try(:message_templates) if resource.whatsapp?
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe Whatsapp::SendOnWhatsappService do
|
||||
template_params = {
|
||||
name: 'sample_shipping_confirmation',
|
||||
namespace: '23423423_2342423_324234234_2343224',
|
||||
language: 'en_US',
|
||||
processed_params: { '1' => '3' }
|
||||
}
|
||||
|
||||
describe '#perform' do
|
||||
before do
|
||||
stub_request(:post, 'https://waba.360dialog.io/v1/configs/webhook')
|
||||
|
@ -54,6 +61,30 @@ describe Whatsapp::SendOnWhatsappService do
|
|||
expect(message.reload.source_id).to eq('123456789')
|
||||
end
|
||||
|
||||
it 'calls channel.send_template if template_params are present' do
|
||||
message = create(:message, additional_attributes: { template_params: template_params },
|
||||
content: 'Your package will be delivered in 3 business days.', conversation: conversation, message_type: :outgoing)
|
||||
allow(HTTParty).to receive(:post).and_return(whatsapp_request)
|
||||
allow(whatsapp_request).to receive(:success?).and_return(true)
|
||||
allow(whatsapp_request).to receive(:[]).with('messages').and_return([{ 'id' => '123456789' }])
|
||||
expect(HTTParty).to receive(:post).with(
|
||||
'https://waba.360dialog.io/v1/messages',
|
||||
headers: { 'D360-API-KEY' => 'test_key', 'Content-Type' => 'application/json' },
|
||||
body: {
|
||||
to: '123456789',
|
||||
template: {
|
||||
name: 'sample_shipping_confirmation',
|
||||
namespace: '23423423_2342423_324234234_2343224',
|
||||
language: { 'policy': 'deterministic', 'code': 'en_US' },
|
||||
components: [{ 'type': 'body', 'parameters': [{ 'type': 'text', 'text': '3' }] }]
|
||||
},
|
||||
type: 'template'
|
||||
}.to_json
|
||||
)
|
||||
described_class.new(message: message).perform
|
||||
expect(message.reload.source_id).to eq('123456789')
|
||||
end
|
||||
|
||||
it 'calls channel.send_template when template has regexp characters' do
|
||||
message = create(
|
||||
:message,
|
||||
|
|
Loading…
Reference in a new issue